前言
构建大型 SPA 应用时,代码分割和懒加载是比较常用的优化手段,在 Vue 生态下,使用 vue-router 很容易实现组件的懒加载。
但应用里除了组件,还有庞大的业务逻辑,这部分如何分割和懒加载比较合适呢?
使用 Vuex 管理状态的话,其提供了方法 registerModule
用于动态注册 Module。
因此某个页面独有的业务逻辑和状态管理,在初始化全局 store 的时候可以不用引入,之后在该页面路由组件中再引入和注册 Vuex 模块。
简单的示例
|
简单的 Vuex 模块:
|
实践时遭遇了几个问题:
问题 1:服务器/客户端 在尚未注册 Module 时,调用其下的 action/mutation ,Vuex 因找不到对应函数而出错
|
考虑服务器端预取数据注入给客户端的时候
客户(浏览器)端初始化代码,在初始化 router 之前,给 Vuex 全局 store 注入数据:
|
此处的 __INITIAL_STATE__
是 Vue SSR 提供的一个功能,使得浏览器端可以复用服务器端已经预取过的数据。
|
此处的 asyncData
与 Vue SSR 文档中的例子类似,与 Nuxt.js 中的同名函数用法略有不同。
prepareVuex
为自定义的组件钩子函数,会先于 asyncData 调用,具体过程之后探讨。
|
此时会遇见
问题2: 客户端没有用上服务器端预取的数据
解决方式:
|
注册 Vuex 模块的时候使用了 preserveState
,若启用此选项,注册 Module 时若 store.state[namespace]
下已存在数据,便不会使用声明 vuex 模块时的初始 state 覆盖已有数据。但需要注意,若 state 中没有 namespace 相应数据却开启了此选项,Vuex 还是会报错。因此此处添加了一个输入参数 isClientInitialRoute
, 只有在客户端初次进入页面(可以使用服务器预取数据)时才开启 preserveState
选项。
问题3: 组件热更新时,Vuex 模块被销毁
开发期间使用 HotModuleReplacementPlugin 和 vue-loader,若改变了 PageA.js 中的代码,会触发热更新。在 vue-hot-reload-api 中,当使用 vue-hot-reload-api 的 reload
方法处理组件实例时,该实例会被销毁而后重新创建。beforeDestroy
中销毁了 Vuex 的 page-a
模块,却没有调用 prepareVuex
方法重新注册,因此热更新之后,使用该模块也会报错。
解决方案:
|
仔细想想,注册模块的时机是与路由相关的(进入页面之前),那么销毁的时机也可以与路由相关。不过并不适合在 beforeRouteLeave
钩子中立刻销毁模块。因为根据以下 vue-router 文档内容,在此钩子被调用完成时,整个页面还是在正常工作的(第2步到第11步中间),仍未进入组件的 destroy 过程,此时销毁模块会导致依赖其的所有组件异常。
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
因此安全的模块销毁时机需要在 DOM 更新中或后,旧的页面组件实例销毁过程调用时。
相关代码
最后的 PageA.js:
|
两端的入口文件中相关代码如下:
|
|
|