基本使用

创建历史

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

createHashHistory

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

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

createBrowserHistory

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

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

路由配置

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

  1. const router = (
  2. <Router>
  3. <Route path="/">
  4. <Route path="about" component={About} />
  5. <Route path="inbox" component={Inbox} />
  6. </Route>
  7. </Router>
  8. )
  9. render(router, document.body)

嵌套路由

  1. import React from 'react'
  2. import { render } from 'react-dom'
  3. import { Router, Route, Link } from 'yo-router'
  4. const App = () => (
  5. <div>
  6. <h1>App</h1>
  7. <ul>
  8. <li><Link to="/about">About</Link></li>
  9. <li><Link to="/inbox">Inbox</Link></li>
  10. </ul>
  11. {this.props.children}
  12. </div>
  13. )
  14. const About = () => <h3>About</h3>
  15. const Inbox = () => (
  16. <div>
  17. <h2>Inbox</h2>
  18. {this.props.children || "Welcome to your Inbox"}
  19. </div>
  20. )
  21. const Message = () => <h3>Message {this.props.params.id}</h3>
  22. render((
  23. <Router>
  24. <Route path="/" component={App}>
  25. <Route path="about" component={About} />
  26. <Route path="inbox" component={Inbox}>
  27. <Route path="messages/:id" component={Message} />
  28. </Route>
  29. </Route>
  30. </Router>
  31. ), document.body)

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

URL组件
/App
/aboutApp -> About
/inboxApp -> Inbox
/inbox/messages/:idApp -> Inbox -> Message

添加首页

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

  1. import { IndexRoute } from 'react-router'
  2. const Dashboard = () => <div>Welcome to the app!</div>
  3. render((
  4. <Router>
  5. <Route path="/" component={App}>
  6. {/* 在首页默认显示 Dashboard */}
  7. <IndexRoute component={Dashboard} />
  8. <Route path="about" component={About} />
  9. <Route path="inbox" component={Inbox}>
  10. <Route path="messages/:id" component={Message} />
  11. </Route>
  12. </Route>
  13. </Router>
  14. ), document.body)

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

URL组件
/App -> Dashboard
/aboutApp -> About
/inboxApp -> Inbox
/inbox/messages/:idApp -> Inbox -> Message

解耦 UI 与 URL

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

  1. render((
  2. <Router>
  3. <Route path="/" component={App}>
  4. <IndexRoute component={Dashboard} />
  5. <Route path="about" component={About} />
  6. <Route path="inbox" component={Inbox} />
  7. {/* 使用 /messages/:id 替代 /inbox/messages/:id */}
  8. <Route component={Inbox}>
  9. <Route path="messages/:id" component={Message} />
  10. </Route>
  11. </Route>
  12. </Router>
  13. ), document.body)

路由配置如下所示:

URL组件
/App -> Dashboard
/aboutApp -> About
/inboxApp -> Inbox
/messages/:idApp -> Inbox -> Message

URL 跳转

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

  1. import { Redirect } from 'yo-router'
  2. render((
  3. <Router>
  4. <Route path="/" component={App}>
  5. <IndexRoute component={Dashboard} />
  6. <Route path="about" component={About} />
  7. <Route path="inbox" component={Inbox}>
  8. {/* 重定向 /inbox/messages/:id 到 /messages/:id */}
  9. <Redirect from="messages/:id" to="/messages/:id" />
  10. </Route>
  11. <Route component={Inbox}>
  12. <Route path="messages/:id" component={Message} />
  13. </Route>
  14. </Route>
  15. </Router>
  16. ), document.body)

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

路由控制

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

  1. const history = createHashHistory()
  2. <Router history={history} />
  3. history.push('/hello')

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

  1. import { withRouter } from 'yo-router'
  2. const A = () => <div onClick={this.props.router.push('/hello')}>a</div>
  3. 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 来接收:

  1. router.push('/user/123')
  2. // 需要在配置路由规则时标明参数名称
  3. <Route path="/user/:userId" component={User} />
  4. // 通过 params 的方式接受参数
  5. const User = ({ params }) => <div>{params.userId}</div>

query 参数传递

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

  1. router.push({ pathanme: '/user/123', query: { hello: 'world' } })
  2. // 通过 location.query 的方式接受参数
  3. const User = ({ location }) => <div>{JSON.stringify(location.query)}</div>

生命周期传递

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

可以参见:生命周期

  1. // 传递打开参数
  2. router.push('/user', { hello: 'push' })
  3. // 传递回退参数
  4. router.pop('/user', { hello: 'pop' })
  5. // 通过生命周期接收参数
  6. class _User extends Component {
  7. inited(data) {
  8. console.log(data.payload)
  9. // { hello: 'push' }
  10. }
  11. received(data) {
  12. console.log(data.payload)
  13. // { hello: 'pop' }
  14. }
  15. }
  16. // 需要注入生命周期
  17. const User = withRouter(_User, { lifecycle: true })

路由匹配

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

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

嵌套

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

路径语法

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

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

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

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

优先级

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

  1. <Route path="/comments" />
  2. <Redirect from="/comments" />