进阶使用 #

生命周期 #

自动注入 #

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

  1. inited:对应 hy 的 getInitData 方法,在页面被打开且带有数据时触发。获取参数内容为:{ type: 'push', payload: data }
  2. actived:对应 hy 的 onShow 方法,在页面激活时触发,不管是 push 打开还是 pop 返回都会触发。获取参数内容为:{ type: 'show' }
  3. received:对应 hy 的 onReceiveData 方法,在页面从后面的页面 pop 返回且带有数据时触发。获取参数内容为:{ type: 'pop', payload: data }
  4. deactived:对应 hy 的 onHide 方法,在页面失活的时候触发。
import { withRouter } from '$router'; // $router为hy2项目特有的别名配置,也可以直接引入node_modules/yo-router

class A extends Component {
  inited(data) {
    console.log(data)
  }

  received(data) {
    // 这里默认绑定了 this
    this.setState({ })
  }

  render() {
    return (
      <div>hello</div>
    )
  }
}

export default withRouter(A, { lifecycle: true })

手动注入 #

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

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

class A extends Component {
  componentDidMount() {
    router.setRouteLifecycleHook(
      props.route, // 传入本组件对应的当前路由
      'active',    // 传入生命周期名称,支持两种生命周期:'active' 和 'deactive'
      (data) => {  // 生命周期回调,其中 active 的生命周期会带一个 type,表示该页面的数据是通过 push 还是 pop 获得的,已便数据的接收
        switch (data.type) {
          case 'push':
            this.getInitData()
            return
          case 'pop':
            this.onReceiveData()
            return
          default:
            this.onActive()
            return
        }
      }
    )

    router.setRouteLifecycleHook(
      props.route,
      'deactive',
      this.onDeactive.bind(this)
    )
  }

  getInitData() {
    // 获取页面初始数据逻辑
  }

  onReceiveData() {
    // 获取页面返回数据逻辑
  }

  onActive() {
    // 页面加载激活监听
  }

  onDeactive() {
    // 页面失活监听
  }
}

export default withRouter(A)

代码分割与异步路由加载 #

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

代码分割 #

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

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

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

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

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

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

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

异步路由加载 #

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

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

异步加载组件 #

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

import User from './User'

<Route path="/user" component={User} />

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

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

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

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

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

即可以这样写:

// file - User
export default User

<Route path="/user" getComponent={(nextState, cb) => {
  // 注意这里是 default
  require.ensure([], () => cb(null, require('./User').default))
}} />

也可以这样写:

// file - User
module.exports = User

<Route path="/user" getComponent={(nextState, cb) => {
  // 注意这里是 default
  require.ensure([], () => cb(null, require('./User')))
}} />

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

const User = require.async('./User');

<Route path="/user" getComponent={User} />

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

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

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

import A from './routes/A'
import B from './routes/B'
import Index from './routes/Index'

<Route path="course/:courseId">
  <IndexRoute component={Index}>
  <Route path="/a" component={A} />
  <Route path="/b" component={B} />
</Route>

而异步配置的方式如下:

<Route
  path="course/:courseId"
  getIndexRoute={(nextState, callback) => {
    require.ensure([], function (require) {
      callback(null, {
        component: require('./routes/Index'),
      })
    })
  }}
  getChildRoutes={(nextState, callback) => {
    require.ensure([], function (require) {
      callback(null, [
        require('./routes/A'),
        require('./routes/B')
      ])
    })
  }}
/>

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

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

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

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,实现的效果如下图:

/**
 * +-----------------------+
 * |     yorouter-root     |
 * |                       |
 * |   +---------------+   |  
 * |   | yorouter-view |   |
 * |   |               |   |
 * |   |  +---------+  |   |
 * |   |  |    A    |  |   |
 * |   |  +---------+  |   |
 * |   +---------------+   |
 * |                       |
 * |   +---------------+   |
 * |   | yorouter-view |   | 
 * |   |               |   |
 * |   |  +---------+  |   |
 * |   |  |    B    |  |   |
 * |   |  +---------+  |   |
 * |   +---------------+   |
 * |                       |
 * +-----------------------+
 */

<Route path="/">
  <Route path="a" component={A} />
  <Route path="b" component={B} />
</Route>

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