乐趣区

关于next.js:Nextjs页面重复渲染水合问题

案例

我从 2020 年开始始终应用 next.js 做为我的前端 SSR 框架,应用 @reduxjs/toolkit 做为全局状态管理器,应用 next-redux-wrapper 帮助 next.js 连贯和合并 redux 中 store 数据并且放弃不变,否则会导致数据反复渲染性能问题,然而最近发现一个很奇怪的问题:就是 router.push 路由跳转的时候导致以后页面反复渲染问题!

换句话说也就是:我从 PageA 跳转到 PageB,应该是只有 PageB 页面才会渲染,然而当初岂但 PageB 渲染了,PageA 也渲染了!!!

剖析与操作

通过我应用排除代码的形式剖析发现,原来每次路由导航的时候都会触发 useSelector 这个办法,而这个办法是 react-redux 插件的。

为什么会导航的时候会触发 useSelector 呢?我又在网上搜寻相干话题,终于被我找到一篇文章 react-redux 应用 useSelector 获取数据导致组件反复渲染的问题

通过 redux 中的 hooks – useSelector 获取 store 中的数据时,只有 store 中的数据产生了扭转,即便组件中并没有获取批改的数据,组件也会进行从新渲染。

也就是说造成反复渲染的起因是因为 redux 中 store 数据源变动了导致的。

于是咱们应用文中提到的设置 useSelector 的第 2 个参数,雷同的时候返回 true 会阻止反复渲染,不同的时候返回 false 从新渲染的机制,咱们应用了 lodashisEqual办法来判断。

留神:react-redux自带的 shallowEqual 办法是浅比拟,所以是数组对象的状况下比拟是有问题的,所以这里咱们应用了 lodashisEqual办法来深度比拟判断。

import _ from 'lodash'

const {userInfo, latestNews, likeUrls, reportUrls} = useSelector((state) => state.home, (_old, _new)=>{console.log('old=',_old,',new=',_new)
    return _.isEqual(_old, _new)
  });

然而问题是,我只是做了一个路由跳转为什么会导致 redux 中 store 数据源前后不统一呢?

于是我加了个打印日志的代码,如下:

发现的确不一样了,而且次要集中在 latestNews, likeUrls, reportUrls 这三个数据源上,而 userInfo 不变。

这是为什么呢?于是我又看了一下 next-redux-wrapper 文档,发现外面这段代码和一段形容:

State reconciliation during hydration
Each time when pages that have getStaticProps or getServerSideProps are opened by user the HYDRATE action will be dispatched. This may happen during initial page load and during regular page navigation. The payload of this action will contain the state at the moment of static generation or server side rendering, so your reducer must merge it with existing client state properly.

翻译成中文

水合过程中的状态调节
每次当用户关上具备 getStaticProps 或 getServerSideProps 的页面时,都会调度 HYDRATE 操作。这可能产生在初始页面加载期间和惯例页面导航期间。此操作的无效负载将蕴含动态生成或服务器端出现时的状态,因而您的 reducer 必须将其与现有客户端状态正确合并。

难道我没有正解合并客户端数据吗?

于是我又看了一下我的水合代码:

const initialState = {
  userInfo: null,
  latestNews: [],
  likeUrls: [],
  reportUrls: []}

[HYDRATE]: (state, action) => {console.log('HYDRATE action.payload=',action.payload);
      return {
        ...state,
        ...action.payload.home,
      };
    },

感觉没啥问题啊!于是我又看看 PageA 的 getServerSideProps 办法,发现有个区别:

userInfo是通过 getServerSidePropsServer 服务端渲染失去的,而latestNews, likeUrls, reportUrls 是 Client 客户端渲染拿的!!!

神思之蛙始终摸你肚子!

联合下面官网给的文档,再加上这个发现,那就是说我没有正确水合服务端和客户端数据!

果然,我打印水合数据的时候,action.payload是拿不到客户端的数据的,都是空的。

留神:尽管客户端通过接口获取的数据保留到了 store 中,然而水合的时候是拿不到的,水合的时候只能拿到服务端数据。

接下来咱们只有正确水合客户端和服务端数据就可解决问题,所以咱们批改下代码:

 [HYDRATE]: (state, action) => {console.log('HYDRATE action.payload=',action.payload);
      const _merge = {
        ...state,
        ...action.payload.home,
      }
      console.log('state.latestNews=',state.latestNews)
      // 这里的 latestNews、likeUrls、reportUrls 都是客户端数据,所以都要正确的水合到 redux store 中
      if(state.latestNews){_merge.latestNews = state.latestNews}
      if(state.likeUrls){_merge.likeUrls = state.likeUrls}
      if(state.reportUrls){_merge.reportUrls = state.reportUrls}
      return _merge
    },

加好之后,咱们再跳转页面发现水合数据胜利了,useSelector因为进行了深度比拟判断 store 也是不变的,所以也就不会导致反复渲染了,终于胜利了!!!

总结

1、SSR 的水合思维集体感觉了解起来是有点难度的,毕竟之前做前端开发是没有遇到雷同思维的问题
2、next-redux-wrapper 文档其实也阐明了很分明,客户端数据要水合到 store 中,否则会有问题的。
3、触发水合的场景有:每次当用户关上具备getStaticPropsgetServerSideProps的页面时,都会调度 HYDRATE 操作。这可能产生在初始页面加载期间和惯例页面导航期间。
4、当触发 HYDRATE 时,只有服务端数据会保留到 store 中,客户端数据不会主动存储到 store 中,所以能够将前置的 state 的客户端数据被动合并到 store 中。
5、useSelector办法的第 2 个参数通过判断是否从新渲染,true时不从新渲染,false时从新渲染。
6、next-redux-wrapperstar 这么少,是不是跟它的思维难度 (水合) 无关???而且好多外网大佬都不倡议应用任何一种状态管理器的。

援用

next-redux-wrapper
[redux toolkit useSelector]()

退出移动版