基本使用 #

创建历史 #

在配置路由之前,首先要创建历史的管理方式。我们支持两种历史的创建方式,分别为 createHashHistorycreateBrowserHistory,其中前者是带 hash 的历史,后者是需要后端支持的不带 hash 的历史。

createHashHistory #

创建通过 hash 管理的历史,在浏览器前进后退时监听 hash 的变动来判断历史的更新,采用 sessionStorage 记录历史堆栈。支持传入的参数如下:

  1. spa:是否为单页应用,默认为 false(注意,在 Hy 场景下永远是新开 webview);
  2. basename:基础历史,在所有 url 前都会加上这一前缀,例如 basename 设置为 'hello',那么在 push('/world') 时,就会打开 '#/hello/world'。
  3. wechatSupport:是否支持微信,默认为 false。开启之后可以在微信中回退时关闭当前页面(注意,即使不开启,在大多数场景下都是可以支持微信的,这里设置为 true 可以开启对微信 api 的支持)。
  4. hashType:hash 的类型,分为 hashbang('#!/'),noslash('#')和 slash('#/')三种,默认是 slash。快来选择你喜欢的 hash 类型吧。
  5. queryKey:用于标示 url 唯一值的 key,用于记录浏览器历史时进行标识。默认为 _k
  6. keyLength:用于设置 queryKey 的长度,默认为 6。
  7. uniqueKey:用于在 sessionStorage 里进行存储时生成的唯一 key,这里填一个业务能唯一区分的 key 就好。

createBrowserHistory #

创建通过 history-api 管理的历史,需要后端的支持,不然会出现直接访问某一页面获取不到后端资源的情况。支持传入的参数如下:

  1. spa:是否为单页应用,默认为 false(注意,在 Hy 场景下永远是新开 webview);
  2. basename:基础历史,在所有 url 前都会加上这一前缀,例如 basename 设置为 'hello',那么在 push('/world') 时,就会打开 '/hello/world'。
  3. postfix:后缀,在所有 url 后都会加上这一后缀,例如 postfix 设置为 '.jsp',那么那么在 push('/world') 时,就会打开 '/world.jsp'。
  4. wechatSupport:是否支持微信,默认为 false。开启之后可以在微信中回退时关闭当前页面(注意,即使不开启,在大多数场景下都是可以支持微信的,这里设置为 true 可以开启对微信 api 的支持)。
  5. uniqueKey:用于在 sessionStorage 里进行存储时生成的唯一 key,这里填一个业务能唯一区分的 key 就好。
  6. queryKey:用于标示 url 唯一值的 key,用于记录浏览器历史时进行标识(在多页场景下会用到)。默认为 _k
  7. keyLength:用于设置 queryKey 的长度,默认为 6。

路由配置 #

路由配置就是设置路由让其知道如何进行 URL 匹配,且设置在匹配时执行的逻辑。可以参加下面的例子了解其使用方式。对于轻量级的业务需求来说,我们仅需要管理平级的路由即可,那我们的项目就可以这样配置:

const router = (
  <Router>
    <Route path="/">
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox} />
    </Route>
  </Router>
)

render(router, document.body)

嵌套路由 #

import React from 'react'
import { render } from 'react-dom'
import { Router, Route, Link } from 'yo-router'

const App = () => (
  <div>
    <h1>App</h1>
    <ul>
      <li><Link to="/about">About</Link></li>
      <li><Link to="/inbox">Inbox</Link></li>
    </ul>
    {this.props.children}
  </div>
)

const About = () => <h3>About</h3>

const Inbox = () => (
  <div>
    <h2>Inbox</h2>
    {this.props.children || "Welcome to your Inbox"}
  </div>
)

const Message = () => <h3>Message {this.props.params.id}</h3>

render((
  <Router>
    <Route path="/" component={App}>
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>
), document.body)

经过配置之后,这个 App 就可以渲染如下四个 URL:

URL 组件
/ App
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

添加首页 #

由于页面的嵌套关系,因此在首页 / 路由下,我们只能显示 App 组件,而 App 组件的默认子元素为空,如果想展示一个默认首页的话,需要使用 <IndexRoute> 组件给其指定一个默认子元素。

import { IndexRoute } from 'react-router'

const Dashboard = () => <div>Welcome to the app!</div>

render((
  <Router>
    <Route path="/" component={App}>
      {/* 在首页默认显示 Dashboard */}
      <IndexRoute component={Dashboard} />
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>
), document.body)

现在,App 的默认子元素就设定为 Dashboard 了,页面的路由配置如下所示:

URL 组件
/ App -> Dashboard
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

解耦 UI 与 URL #

我们的路由支持解耦,只要嵌套关系正确,路径不是强制嵌套。例如,如果我们在 URL 路径 /inbox/messages/:id 中移除 /inbox,也依然可以让 Message 组件渲染在 App -> Inbox UI 的内部。

render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Dashboard} />
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox} />

      {/* 使用 /messages/:id 替代 /inbox/messages/:id */}
      <Route component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>
), document.body)

路由配置如下所示:

URL 组件
/ App -> Dashboard
/about App -> About
/inbox App -> Inbox
/messages/:id App -> Inbox -> Message

URL 跳转 #

上面的方式令我们改变了路径,这对旧用户很不友好。如果有用户想访问 /inbox/messages/5 就会访问不到。为了解决这个问题,我们可以使用 <Redirect> 组件。

import { Redirect } from 'yo-router'

render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Dashboard} />
      <Route path="about" component={About} />

      <Route path="inbox" component={Inbox}>
        {/* 重定向 /inbox/messages/:id 到 /messages/:id */}
        <Redirect from="messages/:id" to="/messages/:id" />
      </Route>

      <Route component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>
), document.body)

当点击链接 /inbox/messages/5 时,会自动跳转到 /messages/5

路由控制 #

使用 JS 路由控制有两种方式,一个是直接使用路由配置的 history 来进行控制:

const history = createHashHistory()

<Router history={history} />

history.push('/hello')

第二个是使用组件内部 context 注入的 router 方法,可以使用 withRouter 将该方法挂到组件上。例如:

import { withRouter } from 'yo-router'

const A = () => <div onClick={this.props.router.push('/hello')}>a</div>

export default withRouter(A)

路由控制一共有 6 中方法,分别为:

  • push(path, data):用于跳转到新路径,给浏览器历史推入新实例,并传递新开页面的数据;
  • replace(path):使用新路由替换当前路由,不会推入新实例;
  • goto(path, data):进行页面跳转,根据历史堆栈的情况选择是前进还是后退,并传递数据;
  • pop(path, data):进行页面回退,退到历史堆栈已有的某个路由,并传递数据,如果没找到就放弃;
  • go(n, data):与浏览器原生 history.go() 方法类似,不过可以携带数据;
  • goBack(data)go(-1) 的快捷方式;
  • goForward(data)go(1) 的快捷方式。

详见 API

数据传递 #

传递数据有三种方式:

url 路径传递 #

推荐使用通过 url 路径传递,即使用 RESTful 的形式来传递,通过 this.props.params 来接收:

router.push('/user/123')

// 需要在配置路由规则时标明参数名称
<Route path="/user/:userId" component={User} />

// 通过 params 的方式接受参数
const User = ({ params }) => <div>{params.userId}</div>

query 参数传递 #

通过 query 来传递,这样会给 url 添加 query 参数。通过 this.props.location 来接收:

router.push({ pathanme: '/user/123', query: { hello: 'world' } })

// 通过 location.query 的方式接受参数
const User = ({ location }) => <div>{JSON.stringify(location.query)}</div>

生命周期传递 #

通过生命周期传递,获取起来比较麻烦,除非是大规模跨页传参数,或是必须要执行 hy 的返回触发这种以上两种方式无法完成的操作,一般不推荐使用。

可以参见:生命周期

// 传递打开参数
router.push('/user', { hello: 'push' })

// 传递回退参数
router.pop('/user', { hello: 'pop' })

// 通过生命周期接收参数
class _User extends Component {
  inited(data) {
    console.log(data.payload)
    // { hello: 'push' }
  }

  received(data) {
    console.log(data.payload)
    // { hello: 'pop' }
  }
}

// 需要注入生命周期
const User = withRouter(_User, { lifecycle: true })

路由匹配 #

决定一个路由是否匹配到当前 URL,也没有什么别的,大概三件事:

  1. 一个,就是嵌套;
  2. 第二个,就是路径(path);
  3. 第三个,就是优先级。

嵌套 #

本路由使用嵌套路由的概念,在匹配到对应的 URL 时呈现配置好的嵌套的视图集。 嵌套路由以树状结构进行配置,Router 会通过深度优先搜索的方式搜索与当前 URL 匹配的路由。

路径语法 #

路由路径是一个字符串模式,用于匹配全部或部分的 URL。 路由路径大部分就是字面意思,但是有如下几个特殊字符:

  • :paramName:匹配除了 /?# 之外的 URL 内容,匹配到的字符串被看做是参数;
  • ():表示这部分 URL 是可选的;
  • *:非贪婪匹配所有字符,将匹配的内容存放到 splat 参数中。
  • ** 贪婪匹配所有的字符,直到遇到下一个 /?#。将匹配到的内容丢到 splat 参数中。
<Route path="/hello/:name">         // 可匹配 /hello/michael 和 /hello/ryan,生成参数 { name: michael } 和 { name: ryan }
<Route path="/hello(/:name)">       // 可匹配 /hello、/hello/michael 和 /hello/ryan,匹配后面两个可以生成参数 { name: michael } 和 { name: ryan }
<Route path="/files/*.*">           // 可匹配 /files/hello.jpg 和 /files/hello.html,生成参数 { splat: [ 'hello', 'jpg' ] } 和 { splat: [ 'hello', 'html' ] }
<Route path="/**/*.jpg">            // 可匹配 /excited/hello.jpg and /files/path/to/file.jpg,生成参数 { splat: [ 'excited', 'hello' ] } 和 { splat: [ 'files/path/to', 'file' ] }

如果一个路由是相对路径,其会基于祖先元素而进行创建。当然,如果不想继承祖先,可以直接写绝对路径来避免路径继承。

<Route path="/" component={App} navigation={nav('链接高亮')}>
  <IndexRoute component={Index}/>
  {/* users 就是一个嵌套路径了,继承自 /,所以绝对路径是 /users */}
  <Route path="users" component={Users}>
    <IndexRoute component={UsersIndex}/>
    {/* 这个路径就是直接嵌套在 users 里,它的绝对路径应该是:/users/:id */}
    <Route path=":id" component={User}/>
    {/* 使用绝对路径退出嵌套,它的路径就是 /about */}
    <Route path="/about" component={About}/>
  </Route>
</Route>

优先级 #

优先级就是从上到下,先遇到的先匹配。例如下面这种写法,第二个路由就不会生效:

<Route path="/comments" />
<Redirect from="/comments" />