## 异步操作 需要先理解为何async函数需要手动传入path lissom并不提供路由的能力,所以在渲染前并不知道哪些组件需要异步处理 在调用async函数注册path后,lissom会在渲染前去调用取值,渲染时再找回取到的值并作为props传入组件 next的异步操作通过顶层组件编写getInitialProps静态方法在服务端调用获取props,之后在render组件时传入上一步获取的props 这里有两个问题,如果任意下层组件有异步操作时不得不其父组件里层层传递getInitialProps方法调用,这样很不方便 其次客户端的路由跳转时只能走客户端的逻辑,需要在组件渲染前先执行异步操作获取props后再渲染 这里采取和dynamic一样的方式,提供react组件支持,这样不用关心路由跳转,只用关心组件是否要被render了 ### 注意事项 由于提供的是静态方法所以是获取不到父组件props中的数据的,服务端实际的情况是先获取异步返回再渲染组件传入刚获取值 async函数依赖在代码初次加载执行时就被调用执行,因此需要写在最外层即全局作用域下执行,同理dynamic的async组件也需要如此 ## 实现 在代码被加载进来时,通过传入的路由和组件获取到的getInitialProps方法注册进pathMap中 每个路由维护一个InitialProps实例,实例维护两个对列queue,dynamicQueue queue保存普通组件的getInitialProps方法,按组件引用顺序排列 dynamicQueue保存动态组件的getInitialProps方法,也是按动态组件引用的顺序排列,但在执行队列获取props时会排列在queue队列的最后被执行,保证次序的准确性和合理化 服务端执行队列方法 ```javascript ... // 合并队列 public getFullQueue() { return [...this.queue, ...this.dynamicQueue]; } public handleValue(value, index) { // 标记成功 return (this.value[index] = { finish: true, error: null, value }); } // 仅服务端调用 public async getValue(ctx, golbalProps, pathname) { // 保存服务端当前异步路由 _ssrPathName = pathname; this.isLock = true; this.value = []; for (const item of this.getFullQueue()) { // 先同步执行并push进value中,并传入保存在value中之前组件的所有异步返回值 // 用于依赖之前组件异步返回值的组件可以await去获取 const resolve = item(ctx, golbalProps, this.value); this.value.push(resolve); } return Promise.all(this.value).then(values => values.map((v, i) => this.handleValue(v, i)) ); } ... ``` ### 客户端同步值 服务端执行队列获取的值会保存在windows全局对象__SSR_DATA__中的asyncProps里,客户端里会获取到再设置会pathMap中 这里有个问题时如果某个路由仅仅只有动态组件注册过,那么在服务端没问题,但客户端并不会提前加载动态组件,导致path并未注册,造成setValue无法执行,所以这里先检测是否在有值时未存在路由实例,不存在则初始化路由再setValue 客户端获取设置队列值 ```javascript import { pathMap } from "../lib/async" ... const { asyncProps, pathname } = window.__SSR_DATA__ ... // 设置回服务端获取的异步值 if (asyncProps && asyncProps.length > 0) { let mathValue = pathMap.get(pathname); // 只有异步模块的路径时需要提前初始化好 if (!mathValue) { InitialProps.init(pathname); mathValue = pathMap.get(pathname); } mathValue.setValue(asyncProps); } ... ```