一、问题

最近用 react 写一个无限瀑布流,发现当数据变多的时候非常卡顿,并且会出现闪烁情况。

而在 PC 浏览器上没发现什么卡顿情况,在手机H5上出现的卡顿,有点严重。

二、场景

主要场景描述一下,最外层容器主要是负责数据请求等操作,组件为 AppWrapper

AppWrapper 在请求完数据之后,会进行一次 setState,将 list 通过 props 传递给 WaterFall 组件。

WaterFall 组件本身拥有 renderItem prop,用来渲染每个内容。

每个内容卡片由三部分组成:封面图、标题、底部用户信息和点赞操作。

内容卡片分为多个类型,每个类型的视觉唯一区别集中在 封面图。

组件关系如下:

2222.png

三、最开始的粗暴行为

最开始开发没有考虑性能,直接往上面怼,处理逻辑如下:

  • AppWrapper 的 componentWillMount 中请求数据,并完成一次 setState
  • AppWrapper 组件中判断,当 thsi.state.list.length > 0 的时候渲染 WaterFall 组件,将 list 传入 WaterFall
  • WaterFall 组件的 renderItem 方法中判断并且组装不同的卡片,并将 每个卡片的信息通过 this.props.info 传入每个卡片
  • 每个卡片都有 Cover 组件Title 组件UserInfo 组件 三部分拼成,其中 Cover 组件可能存在不同的类型,比如 PicCover or VideoCover

上面的处理逻辑看起来是没有什么问题的,所有的内容均通过 porp 向下面传递。

比较特殊的是在 UserInfo 组件中,存在点赞操作,每次点赞或者取消点赞之后,都会触发一次网络请求,然后会执行一个 prop 回调,传入 id,并在整个 list 中查找这个 id,修改点赞的数量,然后将整个 List 进行一次 setState 操作。

四、粗暴行为带来的问题

每页返回20条数据,最开始的时候是没有问题的,但是当进行无限瀑布流加载之后,发现每次加载都非常卡顿。

无限瀑布流加载的规则无非是当页面滚动到底部后再次请求,然后将数据 push 到 this.state.list 中,然后进行一次 setState。

然后就是上面的操作,导致在手机上非常卡顿。

而上面的点赞操作,每次成功都将整个 list 进行一次 setState,这也导致了 Title 组件经常会出现闪烁的问题。

五、简单的优化

已经保证了每个卡片的 key

只是简单优化,保证了不闪烁等问题,并没有优化太多,其实可优化的地方还有很多,最早的时候我是按照卡片类型去写卡片组件,而不是将卡片的内容进行拆分。

优化主要是从 shouldComponentUpdate 入手

1、UserInfo 包含点赞的组件

优化前:

所有内容信息都是从 props 获取,本地没有 state,点赞状态和点赞数量的变化依赖执行的回调中的 setState 的改变。

优化后:

第一次获取点赞状态和点赞数量是从 props 中获取,直接写入本地 state,点赞或取消点赞不去执行回调,也就不进行 setState。

同时 shouldComponentUpdate 中更新规则如下:

  shouldComponentUpdate(nextProps, nextState) {
    // console.log(this.state.favourStatus, nextState.favourStatus);
    return this.state.favourStatus !== nextState.favourStatus;
  }

只有点赞状态变更之后,再会进行重新渲染,否则无论 props 怎么更新,UserInfo 组件是不关心的。

这也就保证了,当我在无限加载瀑布流的时候,即使触发 setState 了,点赞状态和数量也不会丢失,根本不需要在点赞或者移除点赞的时候去 setState。

2、Cover 组件和 Title 组件

即使 Cover 组件有许多个不同的类型页不需要去更新,只需要渲染一次就 OK 了,Title 组价也是如此,因此这两类组件的更新都是 false.

  shouldComponentUpdate(nextProps, nextState) {
   return false;
  }

3、整个 WaterFall 组件

因为页面上不仅仅有 WaterFall 组件,因此需要保证,只有当瀑布流的 list 发生变化的时候才会进行 WaterFall 组件的更新。

因为我的场景中不存在下拉刷新,只有上拉加载,因此组件更新策略很简单:

shouldComponentUpdate(nextProps, nextState) {
   return nextProps.list.length !== this.props.length;
  }

如果涉及到下拉刷新的 WaterFall 组件,则不能仅仅通过长度去判断,而是应当通过一个标志去判断,我记得我曾经有一个做法是在 AppWrapper 中设定一个 state: { refreshing: false },将这个 refreshing 传递给 WaterFall 组件,并且修改 shouldComponentUpdate:

shouldComponentUpdate(nextProps, nextState) {
   return nextProps.refreshing || nextProps.list.length !== this.props.length;
}