进阶使用

生命周期

自动注入

对于普通的业务逻辑,可以直接使用 withRouter(Component, { lifecycle: true }) 的配置来装饰组件,让组件自动注入如下四个生命周期:

  • inited:对应 hy 的 getInitData 方法,在页面被打开且带有数据时触发。获取参数内容为:{ type: 'push', payload: data }
  • actived:对应 hy 的 onShow 方法,在页面激活时触发,不管是 push 打开还是 pop 返回都会触发。获取参数内容为:{ type: 'show' }
  • received:对应 hy 的 onReceiveData 方法,在页面从后面的页面 pop 返回且带有数据时触发。获取参数内容为:{ type: 'pop', payload: data }
  • deactived:对应 hy 的 onHide 方法,在页面失活的时候触发。
  1. import { withRouter } from '$router'; // $router为hy2项目特有的别名配置,也可以直接引入node_modules/yo-router
  2. class A extends Component {
  3. inited(data) {
  4. console.log(data)
  5. }
  6. received(data) {
  7. // 这里默认绑定了 this
  8. this.setState({ })
  9. }
  10. render() {
  11. return (
  12. <div>hello</div>
  13. )
  14. }
  15. }
  16. export default withRouter(A, { lifecycle: true })

手动注入

自动注入在大部分场景下都能正常使用,如果要自定义激活配置,可以采用手动注入的方法。这里依然需要使用 withRouter 装饰器,该装饰器会给组件的 props 注入 router 对象,该对象拥有 setRouteLifecycleHook 方法,可以自行设置生命周期。

我们设置的生命周期主要分两个生命周期:activedeactive,其中 active 生命周期分三种:showpushpop,分别表示页面激活、页面接受打开时数据和页面接受上一个页面关闭回传的数据。

  1. class A extends Component {
  2. componentDidMount() {
  3. router.setRouteLifecycleHook(
  4. props.route, // 传入本组件对应的当前路由
  5. 'active', // 传入生命周期名称,支持两种生命周期:'active' 和 'deactive'
  6. (data) => { // 生命周期回调,其中 active 的生命周期会带一个 type,表示该页面的数据是通过 push 还是 pop 获得的,已便数据的接收
  7. switch (data.type) {
  8. case 'push':
  9. this.getInitData()
  10. return
  11. case 'pop':
  12. this.onReceiveData()
  13. return
  14. default:
  15. this.onActive()
  16. return
  17. }
  18. }
  19. )
  20. router.setRouteLifecycleHook(
  21. props.route,
  22. 'deactive',
  23. this.onDeactive.bind(this)
  24. )
  25. }
  26. getInitData() {
  27. // 获取页面初始数据逻辑
  28. }
  29. onReceiveData() {
  30. // 获取页面返回数据逻辑
  31. }
  32. onActive() {
  33. // 页面加载激活监听
  34. }
  35. onDeactive() {
  36. // 页面失活监听
  37. }
  38. }
  39. export default withRouter(A)

代码分割与异步路由加载

对于大型单页应用、多页应用以及 hy 上的应用来说,在一个页面中加载全部的静态资源都是一种浪费,因此我们在 router 中支持了根据页面按需引入页面内所需的资源。

代码分割

代码分割 是 webpack 提供的一种异步加载静态资源的方式,每次 require.ensure 的调用会生成一个加载点,被异步引入的资源会生成一个非入口分块(non-entry chunks)。

代码分割让我们保证了页面入口资源大小可以得到保证,对于单页应用来说,页面的资源是异步递增的过程;对于多页应用来说,每个页面的资源也可以得到控制,仅包含自身页面的业务逻辑。这种方式与之前的全部资源全量引入相比,各有其优劣,但是对于大型项目来说,使用代码分割绝对是最好的选择。

由于代码分割可能会导致资源过于分散的问题,因此找到一个控制代码分割的点就尤为重要;而 router 本身就是一个视图控制工具,用来控制代码分割的方式和位置最为合适。

代码分割的基本场景如下:

  1. require.ensure(['some'], function() {
  2. var a = require('module-a');
  3. // ...
  4. });

这里的 require.ensure 是告诉 webpack/ykit,让其构建分块代码,而不是将资源直接打包到 bundle 中去,其中第一个参数是分块代码依赖的资源,主要用于分块代码内部没有引入依赖、或多个引入代码的公共依赖,在我们路由场景中基本不会用到,所以写个空数组就行。

也许有同学会关心加载的多个 chunk 之间,如果存在公共代码应该怎么处理?这里可以使用 webpack 的 CommonsChunkPlugin 插件,或者在 hy2 项目里使用 ykit dll 进行公共依赖的构建。

异步路由加载

在 router 里,我们不仅支持 页面组件的异步加载,同时也支持 整个子路由的动态加载。当然,这样的加载方式会给之前 JSX 的声明式的路由构建方式带来一些不便,这种场景下使用纯对象的路由构建更加方便,当然对于纯组建的异步加载,可以尝试我们提供的 require.async 语法糖。

组件可以定义 getChildRoutesgetIndexRoutegetComponents 方法,这些获取子路由和组件的方法是按需且异步的。

异步加载组件

使用异步方法加载组件,应该是项目初期最常见的一种需求,我们先看下同步加载的写法:

  1. import User from './User'
  2. <Route path="/user" component={User} />

这里表示在跳转到路由 /user 时,进行组件 User 的渲染,但实际上由于我们是实现 import 了该组件,因此它的全部 js 逻辑实际上已经引入到页面内部了。

如果要将其转换为动态加载,可以这样编写:

  1. <Route path="/user" getComponent={(nextState, cb) => {
  2. require.ensure([], () => cb(null, require('./User')))
  3. }} />

这里要注意的一点是,getComponent 方法传入回调的第一参数是 nextState,是路由中通过历史传递的 location.state,这个历史 state 目前仅在单页的 browserHistory 中存在,在多页场景和 hash 场景中都不推荐使用,我们推荐使用通过 生命周期 的方式跨页面传递数据,因此不推荐使用这个参数。

第二个参数 cb 就是用来异步获取组件的回调,其中该回调第一个参数是错误参数,第二个参数是组件。如果使用原生的方式进行组件动态加载,那么要注意在导出组件时不要使用 ES6 的 export default 语法,或者在 require 的时候带上 default 属性:

即可以这样写:

  1. // file - User
  2. export default User
  3. <Route path="/user" getComponent={(nextState, cb) => {
  4. // 注意这里是 default
  5. require.ensure([], () => cb(null, require('./User').default))
  6. }} />

也可以这样写:

  1. // file - User
  2. module.exports = User
  3. <Route path="/user" getComponent={(nextState, cb) => {
  4. // 注意这里是 default
  5. require.ensure([], () => cb(null, require('./User')))
  6. }} />

如果觉得麻烦,在使用了 @qnpm/ykit-config-hy2">ykit-config-hy2 的前提下可以尝试我们提供的 require.async 语法糖,用法如下:

  1. const User = require.async('./User');
  2. <Route path="/user" getComponent={User} />

我们处理了 ES6 模块加载的问题,也隐藏了复杂的异步逻辑。

异步加载首页路由、子路由

异步加载首页路由、子路由的方式同异步加载组件类似,只不过 cb 回调传递的第二个参数为首页路由对象和子路由数组。之前同步配置的方式如下:

  1. import A from './routes/A'
  2. import B from './routes/B'
  3. import Index from './routes/Index'
  4. <Route path="course/:courseId">
  5. <IndexRoute component={Index}>
  6. <Route path="/a" component={A} />
  7. <Route path="/b" component={B} />
  8. </Route>

而异步配置的方式如下:

  1. <Route
  2. path="course/:courseId"
  3. getIndexRoute={(nextState, callback) => {
  4. require.ensure([], function (require) {
  5. callback(null, {
  6. component: require('./routes/Index'),
  7. })
  8. })
  9. }}
  10. getChildRoutes={(nextState, callback) => {
  11. require.ensure([], function (require) {
  12. callback(null, [
  13. require('./routes/A'),
  14. require('./routes/B')
  15. ])
  16. })
  17. }}
  18. />

这样可以保证整个页面的子路由结构都是异步载入的,可以在具体的页面内部再进行路由的配置。

注意,在异步配置首页路由时,需要传入一个路由对象,而配置子路由时,需要传入一个数组对象。

目前在这种场景下我们还没有提供更方便的语法糖配置,在后续的版本中我们会加入便捷的配置方式。

ViewStack

<ViewStack /> 是 yo-router 内置的一个组件,用于管理在单页场景下,每个页面渲染的层级和顺序。react-router 并没有提供视图堆栈的功能,当用户打开 A -> B -> C 三个页面时,A、B 页面会被依次销毁,最终展现的只有 C 页面;而在我们 yo-router 的默认场景下,这三个页面会按照顺序堆叠在我们的视图堆栈中,通过 zIndex 属性控制他们的显示层级。

之所以引入这一概念,是为了要保持 ScrollView 等组件的滚动条位置,而 react-router 并没有很好的解决这一问题(通过其他的插件可以让 router 保持浏览器的滚动条位置,但始终无法记录 ScrollView 或 ListView 组件本身的位置)。

为了实现视图堆栈,我们像 QApp 一样采用了 <yorouter-root> 作为视图根容器,使用 <yorouter-view> 来包裹每一个页面的 component,实现的效果如下图:

  1. /**
  2. * +-----------------------+
  3. * | yorouter-root |
  4. * | |
  5. * | +---------------+ |
  6. * | | yorouter-view | |
  7. * | | | |
  8. * | | +---------+ | |
  9. * | | | A | | |
  10. * | | +---------+ | |
  11. * | +---------------+ |
  12. * | |
  13. * | +---------------+ |
  14. * | | yorouter-view | |
  15. * | | | |
  16. * | | +---------+ | |
  17. * | | | B | | |
  18. * | | +---------+ | |
  19. * | +---------------+ |
  20. * | |
  21. * +-----------------------+
  22. */
  23. <Route path="/">
  24. <Route path="a" component={A} />
  25. <Route path="b" component={B} />
  26. </Route>

如果不想使用这一特性,可以通过在 <Router> 组件上设置 useViewStackfalse 来关闭它,但要注意,一旦关闭使用之后,之前设置的 extraRootClassextraRootStyle 和每个路由单独设置的 extraClassextraStyle 均会失效:因为这些设置实际上是针对 <yorouter-root><yorouter-view> 的设置,当不使用视图堆栈时,就不会在页面上生成 <yorouter-root><yorouter-view> 组件。