基本使用
创建历史
在配置路由之前,首先要创建历史的管理方式。我们支持两种历史的创建方式,分别为 createHashHistory
和 createBrowserHistory
,其中前者是带 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 匹配,且设置在匹配时执行的逻辑。可以参加下面的例子了解其使用方式。对于轻量级的业务需求来说,我们仅需要管理平级的路由即可,那我们的项目就可以这样配置:
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,也没有什么别的,大概三件事:
- 一个,就是嵌套;
- 第二个,就是路径(
path
); - 第三个,就是优先级。
嵌套
本路由使用嵌套路由的概念,在匹配到对应的 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" />