使用Nextjs开发应用已经不是什么新鲜事了,最近项目也是决定用它来改版我们的网站,目的是SEO优化,经过一两周的学习与思考,对Nextjs里store的管理有了新的领悟,在此记录一下。
这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
状态管理
我们的状态管理库使用的是Mobxjs,这是一个非常好用的库,它响应式的写法非常巧妙,简单明了,支持面向对象式的编程范式。团队下决定采用这个库也是经历了漫长的讨论与思考,在Redux与Mobx之间均衡再三,最终Mobx因为其一些特点胜出。
当应用只在客户端渲染时,一点问题都没有。但应用也跑在服务端时,很多问题就会涌现出来:
- 我们如何在服务端渲染好html内容返回给前端
- 我们渲染好html时,如何把相应的store同步前端
- store在服务端是怎样的表现形式?
其实上述三个问题紧密相连,回答了第三个问题,其他也就都迎刃而解了。
那么store在服务端是怎样的表现形式呢?
store负责在服务端渲染期间、正式生成html内容之前提供数据支撑。假如store里保存了应用的语言状态,某次请求的html可能需要中文也能需要英文,由用户之前的配置或设置决定。所以我们需要在开始渲染html之前将store初始化为我们需要的:store需要暴露一个初始化的方法。
我们继续分析,由于请求是彼此独立的,我们必须在每次请求都创建一份新的store并初始化,才能规避数据冲突。
另,store只是为本次请求的页面提供数据支撑,并不存在UI交互或者状态的二次更改,所以,store在服务端仅仅是一种普通js对象的存在:
graph TD
Request --> Store初始化 --> 读取Store数据完成页面渲染 --> clientSide
所以,我们再来总结一下store在服务端表现:
- 会频繁地创建(每次request都会创建一份新的store树,js的垃圾回收会负责处理)
- 我们并不需要store的响应式特性(客户端才需要)
- 存在着内存泄漏的风险
对于以上几点,我们需要内存比较大的服务器来支撑服务端渲染,我们需要在服务端禁用Mobx的响应式特性:
import {enableStaticRendering} from 'mobx-react'
if(!progress.browser){
enableStaticRendering(true)
}
另外,我们还需要在store里提供一个初始化的方法:
export class Store {
//省略store实现
}
let store:Store;
export function initializeStore(initialState:InitialStore = {}) {
if (isServer) {
return new Store(initialState)
}
if (!store) {
store = new Store(initialState)
}
return store
}
数据同步
解决了store在服务端和客户端表现的问题,接下来一个比较实际的问题是:
我们在服务端初始化了store以后,根据该store渲染好html返回到前端,前端store如何实现与服务端store的同步呢?
分析可知有两步:
- 服务端的store数据传到前端
- 前端根据传的数据初始化store
第一个问题,我们打开控制台即可发现:
我们发现:Nextjs聪明地将服务端的store数据序列化为json塞到脚本里了,这样前端就能获取到这份数据了。
第二个问题,前端如何使用这份数据呢?
还记得我们store的初始化方法initializeStore吗?
我们把数据传入它不就行了嘛。
const MyApp = ({ Component, pageProps,initialStore }: AppProps) => {
//服务端渲染则直接使用下面传入的store,客户端则根据服务端传的数据初始化一份新的store(响应式的)
const mobxStore = process.browser ? initializeStore(initialStore):initialStore;
return (
<StoreContext.Provider value={mobxStore}>
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
</StoreContext.Provider>
);
};
MyApp.getInitialProps = async (appContext:any) => {
//服务端初始化store,这里我们将语言初始化为了英文,也可以根据外部数据来初始化
const initialStore = initializeStore({
sharedStore:{
lang:"English"
}
})
const appProps = await App.getInitialProps(appContext);
return { ...appProps,initialStore };
}
鸣谢
其实最近学习了很多文章,有阿里大佬的文章这可能是大型复杂项目下数据流的最佳实践,里面介绍了store更好的组织方式。还有这一篇文章Next.js部署web同构直出应用全指南(MobX + TypeScript),读完真的受益匪浅,感谢你们无私的分享精神。
最后,感谢阅读,如有任何疑惑,欢迎留言讨论。