初探 React Router 4.0

96
_minooo_
0.8 2017.04.04 14:39* 字数 3142

React Router 4.0 (以下简称 RR4) 已经正式发布,它遵循React的设计理念,即万物皆组件。所以 RR4 只是一堆 提供了导航功能的组件(还有若干对象和方法),具有声明式(声明式编程简单来讲就是你只需要关心做什么,而无需关心如何去做,好比你写 React 组件,只需要 render 出你想要的组件,至于组件是如何实现的是 React 要处理的事情。),可组合性的特点。

v4 中文博客介绍
v3 如何迁徙到 v4
v4 代码分割(异步加载)
v4 代码分割辅助库 react-loadable
v4 完全指南

RR4 本次采用单代码仓库模型架构(monorepo),这意味者这个仓库里面有若干相互独立的包,分别是:

  • 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 静态路由配置的小助手

本文主要讨论在 web app 中如何使用使用 RR4。

引用

react-router 还是 react-router-dom?

在 React 的使用中,我们一般要引入两个包,reactreact-dom,那么 react-routerreact-router-dom 是不是两个都要引用呢?
非也,坑就在这里。他们两个只要引用一个就行了,不同之处就是后者比前者多出了 <Link> <BrowserRouter> 这样的 DOM 类组件。
因此我们只需引用 react-router-dom 这个包就行了。当然,如果搭配 redux ,你还需要使用 react-router-redux
what is the diff between react-router-dom & react-router?

组件

<BrowserRouter>

一个使用了 HTML5 history API 的高阶路由组件,保证你的 UI 界面和 URL 保持同步。此组件拥有以下属性:

basename: string
作用:为所有位置添加一个基准URL
使用场景:假如你需要把页面部署到服务器的二级目录,你可以使用 basename 设置到此目录。

<BrowserRouter basename="/minooo" />
<Link to="/react" /> // 最终渲染为 <a href="/minooo/react">

getUserConfirmation: func
作用:导航到此页面前执行的函数,默认使用 window.confirm
使用场景:当需要用户进入页面前执行什么操作时可用,不过一般用到的不多。

const getConfirmation = (message, callback) => {
  const allowTransition = window.confirm(message)
  callback(allowTransition)
}

<BrowserRouter getUserConfirmation={getConfirmation('Are you sure?', yourCallBack)} />

forceRefresh: bool
作用:当浏览器不支持 HTML5 的 history API 时强制刷新页面。
使用场景:同上。

const supportsHistory = 'pushState' in window.history
<BrowserRouter forceRefresh={!supportsHistory} />

keyLength: number
作用:设置它里面路由的 location.key 的长度。默认是6。(key的作用:点击同一个链接时,每次该路由下的 location.key都会改变,可以通过 key 的变化来刷新页面。)
使用场景:按需设置。

<BrowserRouter keyLength={12} />

children: node
作用:渲染唯一子元素。
使用场景:作为一个 Reac t组件,天生自带 children 属性。

尝试一下


<HashRouter>

Hash history 不支持 location.keylocation.state。另外由于该技术只是用来支持旧版浏览器,因此更推荐大家使用 BrowserRouter,此API不再作多余介绍。


<Route>

<Route> 也许是 RR4 中最重要的组件了,重要到你必须理解它,学会它,用好它。它最基本的职责就是当页面的访问地址与 Route 上的 path 匹配时,就渲染出对应的 UI 界面。

<Route> 自带三个 render method 和三个 props 。

render methods 分别是:

  • <Route component>
  • <Route render>
  • <Route children>
    每种 render method 都有不同的应用场景,同一个<Route> 应该只使用一种 render method ,大部分情况下你将使用 component

props 分别是:

  • match
  • location
  • history
    所有的 render method 无一例外都将被传入这些 props。

component
只有当访问地址和路由匹配时,一个 React component 才会被渲染,此时此组件接受 route props (match, location, history)。
当使用 component 时,router 将使用 React.createElement 根据给定的 component 创建一个新的 React 元素。这意味着如果你使用内联函数(inline function)传值给 component 将会产生不必要的重复装载。对于内联渲染(inline rendering), 建议使用 render prop。

<Route path="/user/:username" component={User} />
const User = ({ match }) => {
  return <h1>Hello {match.params.username}!</h1>
}

render: func
此方法适用于内联渲染,而且不会产生上文说的重复装载问题。

// 内联渲染
<Route path="/home" render={() => <h1>Home</h1} />

// 包装 组合
const FadingRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    <FadeIn>
      <Component {...props} />
    </FaseIn>
  )} />
)

<FadingRoute path="/cool" component={Something} />

children: func
有时候你可能只想知道访问地址是否被匹配,然后改变下别的东西,而不仅仅是对应的页面。

<ul>
  <ListItemLink to="/somewhere" />
  <ListItemLink to="/somewhere-ele" />
</ul>

const ListItemLink = ({ to, ...rest }) => (
  <Route path={to} children={({ match }) => (
    <li className={match ? 'active' : ''}>
      <Link to={to} {...rest} />
    </li>
  )}
)

path: string
任何可以被 path-to-regexp解析的有效 URL 路径

<Route path="/users/:id" component={User} />

如果不给path,那么路由将总是匹配。

exact: bool
如果为 true,path 为 '/one' 的路由将不能匹配 '/one/two',反之,亦然。

strict: bool
对路径末尾斜杠的匹配。如果为 true。path 为 '/one/' 将不能匹配 '/one' 但可以匹配 '/one/two'。

如果要确保路由没有末尾斜杠,那么 strict 和
exact 都必须同时为 true

尝试一下


<Link>

为你的应用提供声明式,无障碍导航。

to: string
作用:跳转到指定路径
使用场景:如果只是单纯的跳转就直接用字符串形式的路径。

<Link to="/courses" />

to: object
作用:携带参数跳转到指定路径
作用场景:比如你点击的这个链接将要跳转的页面需要展示此链接对应的内容,又比如这是个支付跳转,需要把商品的价格等信息传递过去。

<Link to={{
  pathname: '/course',
  search: '?sort=name',
  state: { price: 18 }
}} />

replace: bool
为 true 时,点击链接后将使用新地址替换掉上一次访问的地址,什么意思呢,比如:你依次访问 '/one' '/two' '/three' ’/four' 这四个地址,如果回退,将依次回退至 '/three' '/two' '/one' ,这符合我们的预期,假如我们把链接 '/three' 中的 replace 设为 true 时。依次点击 one two three four 然后再回退会发生什么呢?会依次退至 '/three' '/one'! 为此我做了个在线 demo,大家可以调试体会一下 !

另外你能想到这个 prop 的用途是什么呢?有人说在用 路由 做选项卡时候会用到。欢迎留言讨论!

尝试一下


<NavLink>

这是 <Link> 的特殊版,顾名思义这就是为页面导航准备的。因为导航需要有 “激活状态”。

activeClassName: string
导航选中激活时候应用的样式名,默认样式名为 active

<NavLink
  to="/about"
  activeClassName="selected"
>MyBlog</NavLink>

activeStyle: object
如果不想使用样式名就直接写style

<NavLink
  to="/about"
  activeStyle={{ color: 'green', fontWeight: 'bold' }}
>MyBlog</NavLink>

exact: bool
若为 true,只有当访问地址严格匹配时激活样式才会应用

strict: bool
若为 true,只有当访问地址后缀斜杠严格匹配(有或无)时激活样式才会应用

isActive: func
决定导航是否激活,或者在导航激活时候做点别的事情。不管怎样,它不能决定对应页面是否可以渲染。

尝试一下


<Switch>

只渲染出第一个与当前访问地址匹配的 <Route><Redirect>

思考如下代码,如果你访问 /about,那么组件 About User Nomatch 都将被渲染出来,因为他们对应的路由与访问的地址 /about 匹配。这显然不是我们想要的,我们只想渲染出第一个匹配的路由就可以了,于是 <Switch> 应运而生!

<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>

也许你会问,为什么 RR4 机制里不默认匹配第一个符合要求的呢,答:这种设计允许我们将多个 <Route> 组合到应用程序中,例如侧边栏(sidebars),面包屑 等等。

另外,<Switch> 对于转场动画也非常适用,因为被渲染的路由和前一个被渲染的路由处于同一个节点位置!

<Fade>
  <Switch>
    {/* 用了Switch 这里每次只匹配一个路由,所有只有一个节点。 */}
    <Route/>
    <Route/>
  </Switch>
</Fade>

<Fade>
  <Route/>
  <Route/>
  {/* 不用 Switch 这里可能就会匹配多个路由了,即便匹配不到,也会返回一个null,使动画计算增加了一些麻烦。 */}
</Fade>

children: node
<Switch> 下的子节点只能是 <Route><Redirect> 元素。只有与当前访问地址匹配的第一个子节点才会被渲染。<Route> 元素用它们的 path 属性匹配,<Redirect> 元素使用它们的 from 属性匹配。如果没有对应的 pathfrom,那么它们将匹配任何当前访问地址。

尝试一下


<Redirect>

<Redirect> 渲染时将导航到一个新地址,这个新地址覆盖在访问历史信息里面的本该访问的那个地址。

to: string
重定向的 URL 字符串

to: object
重定向的 location 对象

push: bool
若为真,重定向操作将会把新地址加入到访问历史记录里面,并且无法回退到前面的页面。

from: string
需要匹配的将要被重定向路径。

尝试一下


Prompt

当用户离开当前页面前做出一些提示。

message: string
当用户离开当前页面时,设置的提示信息。

<Prompt message="确定要离开?" />

message: func
当用户离开当前页面时,设置的回掉函数

<Prompt message={location => (
  `Are you sue you want to go to ${location.pathname}?` 
)} />

when: bool
通过设置一定条件要决定是否启用 Prompt

尝试一下


对象和方法

history

histoty 是 RR4 的两大重要依赖之一(另一个当然是 React 了),在不同的 javascript 环境中, history 以多种能够行驶实现了对会话(session)历史的管理。

我们会经常使用以下术语:

  • "browser history" - history 在 DOM 上的实现,用于支持 HTML5 history API 的浏览器
  • "hash history" - history 在 DOM 上的实现,用于旧版浏览器。
  • "memory history" - history 在内存上的实现,用于测试或非 DOM 环境(例如 React Native)。

history 对象通常具有以下属性和方法:

  • length: number 浏览历史堆栈中的条目数
  • action: string 路由跳转到当前页面执行的动作,分为 PUSH, REPLACE, POP
  • location: object 当前访问地址信息组成的对象,具有如下属性:
  • pathname: string URL路径
  • search: string URL中的查询字符串
  • hash: string URL的 hash 片段
  • state: string 例如执行 push(path, state) 操作时,location 的 state 将被提供到堆栈信息里,state 只有在 browser 和 memory history 有效。
  • push(path, [state]) 在历史堆栈信息里加入一个新条目。
  • replace(path, [state]) 在历史堆栈信息里替换掉当前的条目
  • go(n) 将 history 堆栈中的指针向前移动 n。
  • goBack() 等同于 go(-1)
  • goForward 等同于 go(1)
  • block(prompt) 阻止跳转

history 对象是可变的,因为建议从 <Route> 的 prop 里来获取 location,而不是从 history.location 直接获取。这样可以保证 React 在生命周期中的钩子函数正常执行,例如以下代码:

class Comp extends React.Component {
  componentWillReceiveProps(nextProps) {
    // locationChanged
    const locationChanged = nextProps.location !== this.props.location

    // 错误方式,locationChanged 永远为 false,因为history 是可变的
    const locationChanged = nextProps.history.location !== this.props.history.location
  }
}

location

location 是指你当前的位置,将要去的位置,或是之前所在的位置

{
  key: 'sdfad1'
  pathname: '/about',
  search: '?name=minooo'
  hash: '#sdfas',
  state: {
    price: 123
  }
}

在以下情境中可以获取 location 对象

  • Route component 中,以 this.props.location 获取
  • Route render 中,以 ({location}) => () 方式获取
  • Route children 中,以 ({location}) => () 方式获取
  • withRouter 中,以 this.props.location 的方式获取

location 对象不会发生改变,因此可以在生命周期的回调函数中使用 location 对象来查看当前页面的访问地址是否发生改变。这种技巧在获取远程数据以及使用动画时非常有用

componentWillReceiveProps(nextProps) {
  if (nextProps.location !== this.props.location) {
    // 已经跳转了!
  }
}

可以在不同情境中使用 location:

  • <Link to={location} />
  • <NaviveLink to={location} />
  • <Redirect to={location />
  • history.push(location)
  • history.replace(location)

match

match 对象包含了 <Route path> 如何与 URL 匹配的信息,具有以下属性:

  • params: object 路径参数,通过解析 URL 中的动态部分获得键值对
  • isExact: bool 为 true 时,整个 URL 都需要匹配
  • path: string 用来匹配的路径模式,用于创建嵌套的 <Route>
  • url: string URL 匹配的部分,用于嵌套的 <Link>

在以下情境中可以获取 match 对象

  • Route component 中,以 this.props.match获取
  • Route render 中,以 ({match}) => () 方式获取
  • Route children 中,以 ({match}) => () 方式获取
  • withRouter 中,以 this.props.match的方式获取
  • matchPath 的返回值

当一个 Route 没有 path 时,它会匹配一切路径。

2017,前端之路。
Web note ad 1