SSR
简介
首先需要明确本库并没有实现支持 SSR 的功能,因为整体实现方案相对比较复杂,而且我本身也基本不会涉及到 SSR 项目。没有必要增加项目复杂度。
为什么需要数据水合
正常解释是服务器端在渲染页面时,已经获取了一次数据,在客户端运行时没有必要再次获取数据。
如果只有这一个目的,那么在 nuxt 项目中,状态管理工具是不需要特意支持 SSR 的,因为 nuxt 已经提供了 useAsyncData 这个 API,这个 API 会自动缓存接口的数据,这样在客户端再次执行时,就不会重复发送请求了。
我理解 Store 是不需要进行数据水合的。以下是我的分析思路。
因为创建 Store 时,不管是服务端创建,还是客户端创建,Store 的默认数据都是一样的。
服务端因为执行渲染逻辑,会依次创建组件实例,并执行组件的相应钩子方法,在这些过程中可能会修改到 Store 的数据,最终根据修改后的数据完成 html 的渲染。
但是客户端也是会执行这些相同的操作,也是实例化组件以及执行组件的相应的钩子方法,最终得到相同的 Store 对象。所以最终渲染的 html 结果是一致的。
为了避免客户端会再次重复请求相同的接口,可以使用 nuxt 提供的 useAsyncData 这个 API,这个 API 会自动缓存接口数据,避免客户端发送相同的请求。
那么唯一可能导致不一致的地方就是有些特殊代码只会在服务器端执行,这些特殊代码修改了 Store 数据。
因为在客户端不再执行这段特殊代码,所以导致最终数据不一致。
总结起来有以下几点:
- 避免客户端再次发送相同的请求,节省请求时间。
- 有些特殊代码只会在服务器端执行,客户端渲染时并不能完整还原服务器端的 store 数据。
- 就算客户端代码可以完整还原服务器端的 store 数据,客户端渲染也是有可能出现闪烁问题。
因为页面一开始渲染时是按照服务器端返回的 html 渲染的。
接着页面刚初始化时 store 是默认初始值,页面渲染的结果就会变成 store 默认值对应的 html 内容。
只有等到整个应用初始化完成之后,store 数据才是最终的数据,页面渲染的结果又会变成和服务器端返回的 html 内容保持一致。
从整个渲染流程来看,中间会出现一次不一样的渲染结果,所以可能会造成用户页面闪烁的问题。
参考 Pinia 的思路
以下实现思路是仿照 pinia 的逻辑,实现一个类似pinia.state.value
的变量。
需要研究 container.snapshort 是否容易实现,以及是否包含子容器的 snapshort? 已经确认 inversify 中的
container.snapshort
只会包含当前容器的快照,并不会包含子容器的快照。所以这条路是一条死路。container 需要有一个唯一 ID,binding 也需要一个唯一 ID。
需要提供一个 serializeState 方法,可以将整个树状容器中所有 binding 中的 cache 进行转化。因为当前的 cache 对象不仅包含数据,还可能包含方法,所以需要转换成
{'container-id': {'binding-id': state object of cache}}
这里有 2 个问题需要解决,第 1 个就是如何遍历所有的容器?第 2 是根容器需要特殊处理,因为根容器没有 ID。
需要在 useService 方法中根据容器唯一 ID,binding 唯一 ID,从水合数据中找到对应的初始化数据。
需要在 useService/getRootService 中向 ssr context 中注册 cache 对象,ssr context --> container id --> binding id --> cache 通过这样的结构,就可以解决上面提到的问题 1,从而可以方便 serializeState 方法遍历所有容器。
参考 Angular 的思路
Angular 的思路是非常简单明确的,实现起来也是非常的简单。
只需要实现对应的TransferState
和makeStateKey
方法即可。
对比上面参考 Pinia 的思路,makeStateKey
解决了怎么生成 id 的问题,本来的想法是尽量做到业务透明,尽量不要让开发者来维护这个 ID。 但是makeStateKey
则是完全把责任和工作都丢给开发者了。如果业务非常复杂,怎么避免 key 的重名和冲突呢?这里并没有考虑。
另一个不需要考虑的问题就是 service 中到底哪些属性是需要 ssr 的,哪些属性是不需要 ssr 的? 这个选择逻辑在 pinia 中是非常简单的,只需要考虑 store.state 属性即可,不需要考虑其他属性。 在 Angular 中也不需要考虑了,因为只有makeStateKey
关联的数据需要 ssr,这个属性甚至不是 service 的直接属性。 而是需要在业务代码中手动赋值数据给 service 对应的属性。
最终TransferState
对象维护了一个大的 map 结构,map 的 key 就是由makeStateKey
生成的所有 key。map 的 value 就是关联的所有数据。 这个 map 就相当于 pinia 中的pinia.state.value
属性。