React Router 4 笔记
记录了一下学习 React Router 的笔记,学习文章均在 4. 学习资料 这部分。
1. React Router 4 介绍
React Router4 是一个流行的纯 React 重写的包。现在的版本中已不需要路由配置,现在一切皆组件。
据江湖传言,目前官方同时维护 2.x 和 4.x 两个版本。 3.x 版本相比于 2.x 并没有引入任何新的特性,只是将 2.x 版本中部分废弃 API 的 warning 移除掉而已。
按照规划,没有历史包袱的新项目想要使用稳定版的 ReactRouter 时,应该使用 ReactRouter 3.x。目前 3.x 版本也还处于 beta 阶段,不过会先于 4.x 版本正式发布。
如果你已经在使用 2.x 的版本,那么升级 3.x 将不会有任何额外的代码变动。
React Router V4 相较于前面三个版本有根本性变化,首先是遵循 Just Component 的 API 设计理念,其次 API 方面也精简了不少,对新手来说降低了学习难度,但如果是对之前项目的重构,嗯,简直无**可说。本次升级的主要特点如下:
- 声明式(Declarative)
- 可组合 (Composability)
React Router V4 遵循了 React 的理念:万物皆组件。因此 升级之后的 Route、Link、Switch 等都是一个普通的组件。
React Router V4 基于 Lerna 管理多个 Repository。在此代码库包括:
- react-router React Router 核心
- react-router-dom 用于 DOM 绑定的 React Router
- react-router-native 用于 React Native 的 React Router
- react-router-redux React Router 和 Redux 的集成
- react-router-config 静态路由配置帮助助手
2. 安装
React Router 被拆分成三个包:react-router、react-router-dom 和 react-router-native。react-router 提供核心的路由组件与函数。其余两个则提供运行环境(即浏览器与 react-native)所需的特定组件。
通常我们在 React 的使用中,一般要引入两个包,react 和 react-dom,那么 react-router 和 react-router-dom 是不是两个都要引用呢?
注意,前方高能,入门第一坑就在这里。他们两个只要引用一个就行了,不同之处就是后者比前者多出了 <Link> <BrowserRouter>
这样的 DOM 类组件。因此我们只需引用 react-router-dom 这个包就 OK 了。当然,如果搭配 redux,你还需要使用 react-router-redux。
npm install --save react-router-dom
3. react-router-dom 主要组件介绍
- 路由外层容器组件,用于包裹
<Route>
组件、<Redirect>
组件和<Switch>
组件:
-
<Router>
组件,是所有路由组件共用的底层接口,一般我们的应用并不会使用这个接口; -
<BrowserRouter>
组件:使用 HTML5 提供的 history API 来保持 UI 和 URL 的同步; -
<HashRouter>
组件:使用 URL 的 hash (例如:window.location.hash
) 来保持 UI 和 URL 的同步; -
<MemoryRouter>
组件:能在内存保存你 “URL” 的历史纪录(并没有对地址栏读写); -
<NativeRouter>
组件:为使用 React Native 提供路由支持; -
<StaticRouter>
组件:从不会改变地址;
-
只渲染第一个匹配的子
<Route>
组件和<Redirect>
组件的<Switch>
组件。 -
路由组件
<Route>
。 -
重定向组件
<Redirect>
。
<Route>
组件<Redirect>
组件
-
用于在位置跳转之前给予用户一些确认信息的
<Prompt>
组件。 -
提供导航链接的组件:
<Link>
组件<NavLink>
组件
3.1 <Router>
组件
在4.0之前版本的 API 中,<Router>
组件的 children 只能是 React Router 提供的各种组件,如 <Route>
、<IndexRoute>
、<Redirect>
等。
而在 React Router 4 中,你可以将各种组件及标签放进 <Router>
组件中,他的角色也更像是 Redux 中的 <Provider>
。不同的是 <Provider>
是用来保持与 store 的更新,而 <Router>
是用来保持与 location 的同步。示例如下:
// 示例1
<Router>
<div>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/about">关于</Link></li>
<li><Link to="/topics">主题列表</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
Router 是所有路由组件共用的底层接口,一般我们的应用并不会使用这个接口,而是使用高级的路由:
-
<BrowserRouter>
:使用 HTML5 提供的 history API 来保持 UI 和 URL 的同步; -
<HashRouter>
:使用 URL 的 hash (例如:window.location.hash
) 来保持 UI 和 URL 的同步; -
<MemoryRouter>
:能在内存保存你 “URL” 的历史纪录(并没有对地址栏读写); -
<NativeRouter>
:为使用 React Native 提供路由支持; -
<StaticRouter>
:从不会改变地址;
TIPS:算是第二坑吧,和之前的 Router 不一样,这里
<Router>
组件下只允许存在一个子元素,如存在多个则会报错。
反面典型在这里:
// 示例2
<Router>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/about">关于</Link></li>
<li><Link to="/topics">主题列表</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</Router>
没错,示例 2 在没有 <div>
爸爸的保护下,会报如下异常信息:
Uncaught Error: A <Router> may have only one child element
<BrowserRouter>
组件
<BrowserRouter>
使用 HTML5 提供的 history API (pushState
, replaceState
和 popstate
事件) 来保持 UI 和 URL 的同步。
import { BrowserRouter } from 'react-router-dom';
<BrowserRouter
basename={string}
forceRefresh={bool}
getUserConfirmation={func}
keyLength={number}
>
<App />
</BrowserRouter>
basename: string
basename 是所有位置的基准 URL。
如果你的应用程序部署在服务器的子目录,则需要将其设置为子目录。basename 的正确格式是前面有一个前导斜杠,但不能有尾部斜杠。
<BrowserRouter basename="/calendar">
<Link to="/today" />
</BrowserRouter>
上例中的 <Link>
最终将被呈现为:
<a href="/calendar/today" />
forceRefresh: bool
如果为 true
,在导航的过程中整个页面将会刷新。一般情况下,只有在不支持 HTML5 history API 的浏览器中使用此功能。
const supportsHistory = 'pushState' in window.history;
<BrowserRouter forceRefresh={!supportsHistory} />
getUserConfirmation: func
用于确认导航的函数,默认使用 ++window.confirm++。例如,当从 /a
导航至 /b
时,会使用默认的 confirm
函数弹出一个提示,用户点击确定后才进行导航,否则不做任何处理。译注:需要配合 <Prompt>
一起使用。
// 这是默认的确认函数
const getConfirmation = (message, callback) => {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />
keyLength: number
location.key
的长度,默认为 6
。
<BrowserRouter keyLength={12} />
children: node
要呈现的++单个子元素(组件)++。
<HashRouter>
组件
<HashRouter>
使用 URL 的 hash
部分(即 window.location.hash
)来保持 UI 和 URL 的同步。
import { HashRouter } from 'react-router-dom';
<HashRouter>
<App />
</HashRouter>
注意: 使用 hash 记录导航历史不支持 location.key 和 location.state。在以前的版本中,我们视图 shim 这种行为,但是仍有一些问题我们无法解决。任何依赖此行为的代码或插件都将无法正常使用。由于该技术仅用于支持旧式(低版本)浏览器,因此对于一些新式浏览器,我们鼓励你使用
<BrowserHistory>
代替。
basename: string
所有位置的基准 URL。basename
的正确格式是前面有一个前导斜杠,但不能有尾部斜杠。
<HashRouter basename="/calendar">
<Link to="/today" />
</HashRouter>
上例中的 <Link>
最终将被呈现为:
<a href="#/calendar/today" />
getUserConfirmation: func
用于确认导航的函数,默认使用 ++window.confirm++。
// 这是默认的确认函数
const getConfirmation = (message, callback) => {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<HashRouter getUserConfirmation={getConfirmation} />
hashType: string
window.location.hash
使用的 hash
类型,有如下几种:
- slash - 后面跟一个斜杠,例如
#/
和#/sunshine/lollipops
。 - noslash - 后面没有斜杠,例如
#
和#sunshine/lollipops
。 - hashbang - Google 风格的 ajax crawlable,例如
#!/
和#!/sunshine/lollipops
。
默认为 slash
。
children: node
要呈现的++单个子元素(组件)++。
3.2 <Switch>
组件
该组件用来渲染匹配地址的第一个 <Route>
或者 <Redirect>
。那么它与使用一堆 route 又有什么区别呢?
<Switch>
的独特之处是它仅仅渲染一个路由。相反地,每一个包含匹配地址(location)的 <Route>
都会被渲染。思考下面的代码:
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
如果现在的 URL 是 /about
,那么 <About>
, <User>
, 还有 <NoMatch>
都会被渲染,因为它们都与路径(path)匹配。这种设计,允许我们以多种方式将多个 <Route>
组合到我们的应用程序中,例如侧栏(sidebars),面包屑(breadcrumbs),bootstrap tabs 等等。 然而,偶尔我们只想选择一个 <Route>
来渲染。如果我们现在处于 /about
,我们也不希望匹配 /:user
(或者显示我们的 “404” 页面 )。以下是使用 Switch 的方法来实现:
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
现在,如果我们处于 /about
,<Switch>
将开始寻找匹配的 <Route>
。<Route path="/about"/>
将被匹配, <Switch>
将停止寻找匹配并渲染 <About>
。同样,如果我们处于 /michael
,<User>
将被渲染。
3.3 <Route>
组件
我们知道,<Route>
组件主要的作用就是当一个 location 匹配路由的 path 时,渲染某些 UI。示例如下:
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/news" component={NewsFeed}/>
</div>
</Router>
// 如果应用的地址是/,那么相应的UI会类似这个样子:
<div>
<Home/>
</div>
// 如果应用的地址是 /news,那么相应的UI就会成为这个样子:
<div>
<NewsFeed/>
</div>
<Route>
组件有如下属性:
- path(string): 路由匹配路径。(没有 path 属性的 Route 总是会匹配);
- exact(bool):为 true 时,则要求路径与
location.pathname
必须完全匹配; - strict(bool):true 的时候,有结尾斜线的路径只能匹配有斜线的
location.pathname
;
奉上两个鲜活的例子:
exact 配置:
路径 | location.pathname | exact | 是否匹配 |
---|---|---|---|
/one | /one/two | true | 否 |
/one | /one/two | false | 是 |
strict 配置:
路径 | location.pathname | strict | 是否匹配 |
---|---|---|---|
/one/ | /one | true | 否 |
/one/ | /one/ | true | 是 |
/one/ | /one/two | true | 是 |
同时,新版的路由为 <Route>
提供了三种渲染内容的方法:
-
<Route component>
:在地址匹配的时候 React 的组件才会被渲染,route props 也会随着一起被渲染; -
<Route render>
:这种方式对于内联渲染和包装组件却不引起意料之外的重新挂载特别方便; -
<Route children>
:与 render 属性的工作方式基本一样,除了它是不管地址匹配与否都会被调用;
第一种方式没啥可说的,和之前一样,这里我们重点看下 <Route render>
的渲染方式
// 行内渲染示例
<Route path="/home" render={() => <div>Home</div>}/>
// 包装/合成
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)
<FadingRoute path="/cool" component={Something}/>
TIPS: 第三坑!
<Route component>
的优先级要比<Route render>
高,所以不要在同一个<Route>
中同时使用这两个属性。
3.4 <Redirect>
组件
使用 <Redirect>
会导航到一个新的位置。新的位置将覆盖历史堆栈中的当前条目,例如服务器端重定向(HTTP 3xx)。
import { Route, Redirect } from 'react-router-dom';
<Route exact path="/" render={() => (
loggedIn ? (
<Redirect to="/dashboard" />
) : (
<PublicHomePage />
)
)} />
下面介绍其属性
to: string
要重定向到的 URL,可以是 ++path-to-regexp++ 能够理解的任何有效的 URL 路径。所有要使用的 URL 参数必须由 from
提供。
<Redirect to="/somewhere/else" />
to: object
要重定向到的位置,其中 pathname 可以是 ++path-to-regexp++ 能够理解的任何有效的 URL 路径。
<Redirect to={{
pathname: '/login',
search: '?utm=your+face',
state: {
referrer: currentLocation
}
}} />
上例中的 state
对象可以在重定向到的组件中通过 this.props.location.state
进行访问。而 referrer
键(不是特殊名称)将通过路径名 /login
指向的登录组件中的 this.props.location.state.referrer
进行访问。
push: bool
如果为 true
,重定向会将新的位置推入历史记录,而不是替换当前条目。
<Redirect push to="/somewhere/else" />
from: string
要从中进行重定向的路径名,可以是 ++path-to-regexp++ 能够理解的任何有效的 URL 路径。所有匹配的 URL 参数都会提供给 to
,必须包含在 to
中用到的所有参数,to
未使用的其它参数将被忽略。
只能在 <Switch>
组件内使用 <Redirect from>
,以匹配一个位置。有关更多细节,请参阅 ++<Switch children>
++。
<Switch>
<Redirect from='/old-path' to='/new-path' />
<Route path='/new-path' component={Place} />
</Switch>
// 根据匹配参数进行重定向
<Switch>
<Redirect from='/users/:id' to='/users/profile/:id' />
<Route path='/users/profile/:id' component={Profile} />
</Switch>
译注:经过实践,发现以上“根据匹配参数进行重定向”的示例存在bug,没有效果。to 中的 :id 并不会继承 from 中的 :id 匹配的值,而是直接作为字符串显示到浏览器地址栏!!!
exact: bool
完全匹配,相当于 ++Route.exact
++。
strict: bool
严格匹配,相当于 ++Route.strict
++。
3.5 <Prompt>
组件
用于在位置跳转之前给予用户一些确认信息。当你的应用程序进入一个应该阻止用户导航的状态时(比如表单只填写了一半),弹出一个提示。
import { Prompt } from 'react-router-dom';
<Prompt
when={formIsHalfFilledOut}
message="你确定要离开当前页面吗?"
/>
message: string
当用户试图离开某个位置时弹出的提示信息。
<Prompt message="你确定要离开当前页面吗?" />
message: func
将在用户试图导航到下一个位置时调用。需要返回一个字符串以向用户显示提示,或者返回 true
以允许直接跳转。
<Prompt message={location => {
const isApp = location.pathname.startsWith('/app');
return isApp ? `你确定要跳转到${location.pathname}吗?` : true;
}} />
译注:上例中的 location 对象指的是下一个位置(即用户想要跳转到的位置)。你可以基于它包含的一些信息,判断是否阻止导航,或者允许直接跳转。
when: bool
在应用程序中,你可以始终渲染 <Prompt>
组件,并通过设置 when={true}
或 when={false}
以阻止或允许相应的导航,而不是根据某些条件来决定是否渲染 <Prompt>
组件。
<Prompt when={true} message="你确定要离开当前页面吗?" />
译注:when 只有两种情况,当它的值为 true 时,会弹出提示信息。如果为 false 则不会弹出。见++阻止导航示例++。
3.6 <Link>
组件
为你的应用提供声明式的、可访问的导航链接。
和之前版本没太大区别,重点看下组件属性:
to: string
一个字符串形式的链接地址,通过 pathname
、search
和 hash
属性创建。
<Link to='/courses?sort=name' />
to: object
一个对象形式的链接地址,可以具有以下任何属性:
pathname
(要链接到的路径)。search
(查询参数)。hash
(URL 中的 hash,例如#the-hash
)。state
(存储到 location 中的额外状态数据)。
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: {
fromDashboard: true
}
}} />
replace: bool
当设置为 true
时,点击链接后将替换历史堆栈中的当前条目,而不是添加新条目。默认为 false
。
<Link to="/courses" replace />
innerRef: func
允许访问组件的底层引用。
const refCallback = node => {
// node 指向最终挂载的 DOM 元素,在卸载时为 null
}
<Link to="/" innerRef={refCallback} />
others
你还可以传递一些其它属性,例如 title
、id
或 className
等。
<Link to="/" className="nav" title="a title">About</Link>
3.7 <NavLink>
组件
一个特殊版本的 <Link>
,它会在与当前 URL 匹配时为其呈现元素添加样式属性。组件属性:
import { NavLink } from 'react-router-dom';
<NavLink to="/about">About</NavLink>
activeClassName: string
当元素处于激活状态时应用的类,默认为 active
。它将与 className
属性一起使用。
<NavLink to="/faq" activeClassName="selected">FAQs</NavLink>
activeStyle: object
当元素处于激活状态时应用的样式。
const activeStyle = {
fontWeight: 'bold',
color: 'red'
};
<NavLink to="/faq" activeStyle={activeStyle}>FAQs</NavLink>
exact: bool
如果为 true,则只有在位置完全匹配时才应用激活类/
样式。
<NavLink exact to="/profile">Profile</NavLink>
strict: bool
如果为 true,则在确定位置是否与当前 URL 匹配时,将考虑位置的路径名后面的斜杠。有关更多信息,请参阅 ++<Route strict>
文档++。
<NavLink strict to="/events/">Events</NavLink>
isActive: func
添加额外逻辑以确定链接是否处于激活状态的函数。如果你要做的不仅仅是验证链接的路径名与当前 URL 的路径名相匹配,那么应该使用它。
// 只有当事件 id 为奇数时才考虑激活
const oddEvent = (match, location) => {
if (!match) {
return false;
}
const eventID = parseInt(match.params.eventID);
return !isNaN(eventID) && eventID % 2 === 1;
}
<NavLink to="/events/123" isActive={oddEvent}>Event 123</NavLink>
location: object
isActive
默认比较当前历史位置(通常是当前的浏览器 URL)。你也可以传递一个不同的位置进行比较。