Next.js同构应用如何处理store问题(MobX)

3,088 阅读4分钟

使用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

第一个问题,我们打开控制台即可发现:

image.png 我们发现: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),读完真的受益匪浅,感谢你们无私的分享精神。

最后,感谢阅读,如有任何疑惑,欢迎留言讨论。