Rematch状态管理实战与避坑指南在复杂项目中的应用总结

Mr-瑞芹 框架 阅读 14
赞 19 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

前段时间接手了一个用Rematch做状态管理的项目,说真的,刚运行起来的时候简直怀疑人生。首屏加载慢得离谱,切换页面的时候UI直接卡住不动,动一下就跟幻灯片似的。最夸张的一次,整个应用从点击按钮到更新完成用了将近5秒!用户等个反馈都能睡着了。

Rematch状态管理实战与避坑指南在复杂项目中的应用总结

而且这还不是偶然现象,在低配设备上更严重。试想下用户点个按钮要等好几秒才响应,谁受得了?所以这次优化势在必行,必须想办法把这个性能问题解决掉。

找到瓶颈了!

既然是性能问题,第一步当然是定位瓶颈在哪。我先是用Chrome DevTools的Performance面板跑了几轮分析,发现主要耗时都在Redux的状态更新和React的渲染阶段。具体来说:

  • 每次dispatch后触发大量不必要的组件重渲染
  • 部分selector计算开销过大
  • 有些effect里做了冗余的数据处理

另外我还用React DevTools里的Profiler工具检测了组件树,发现很多纯展示型组件都被无脑地re-render了,哪怕它们依赖的数据根本没变。这一顿分析下来,基本可以确定问题就出在状态管理和视图更新的配合上。

核心优化方案:精简状态更新

优化这种性能问题,我的思路是先从状态管理入手,尽量减少不必要的更新扩散。Rematch本身已经对Redux做了很好的封装,但如果不注意使用方式,还是容易写出低效的代码。

第一个大改动是把原来写得很随意的model拆分得更细。举个例子,原先有个全局的userModel,里面塞了各种用户相关数据:

const userModel = {
  state: {
    profile: {},
    notifications: [],
    settings: {}
  },
  reducers: {
    updateProfile(state, payload) {
      return { ...state, profile: payload };
    },
    addNotification(state, payload) {
      return { ...state, notifications: [...state.notifications, payload] };
    }
    // 其他reducers...
  }
}

这么写的问题在于,只要任何一个字段更新,所有订阅这个model的组件都会重新渲染。于是我把它拆成了更细粒度的几个model:

const userProfileModel = {
  state: {},
  reducers: {
    update(state, payload) {
      return { ...state, ...payload };
    }
  }
}

const userNotificationModel = {
  state: [],
  reducers: {
    add(state, payload) {
      return [...state, payload];
    }
  }
}

// 其他细分model...

这样改完之后,只有真正依赖某个具体model的组件才会被触发更新,大大减少了无效渲染。

优化Selector计算

第二个重点是优化selector的性能。之前有不少selector直接在返回值里做了复杂的计算,比如:

const getProcessedData = (state) => {
  return state.rawData.map(item => {
    return {
      ...item,
      computedValue: someExpensiveCalculation(item)
    };
  });
};

这种写法会导致每次状态变化时都重新执行昂贵的计算。于是我把这些计算结果缓存起来,只在原始数据变化时才重新计算:

import memoize from 'lodash/memoize';

const getProcessedData = memoize((rawData) => {
  return rawData.map(item => {
    return {
      ...item,
      computedValue: someExpensiveCalculation(item)
    };
  });
}, (rawData) => JSON.stringify(rawData));

这里用的是lodash的memoize方法,当然也可以自己实现简单的缓存逻辑。关键是避免重复计算,尤其是那些耗时较长的操作。

小心effect里的坑

还有一点很容易忽略的就是effect里的异步操作。之前有段代码是这样的:

effects: {
  async fetchData() {
    const data = await fetch('https://jztheme.com/api/data').then(res => res.json());
    this.updateData(data);
  }
}

看起来没啥问题,但实际运行时我发现它会在每次组件挂载时都触发请求,即使数据其实没变。后来改成带条件判断的版本:

effects: {
  async fetchData(_, rootState) {
    if (!rootState.someModel.dataLoaded) {
      const data = await fetch('https://jztheme.com/api/data').then(res => res.json());
      this.updateData(data);
    }
  }
}

这样就能避免重复请求了,同时记得在合适的地方维护dataLoaded这个标记位。

性能数据对比

经过这一系列优化,效果还是很明显的:

  • 首屏加载时间从原来的4.8秒降到800毫秒左右
  • 交互响应时间从平均3秒缩短到300毫秒以内
  • 低配设备上的卡顿现象基本消失

虽然还没达到完美,但至少现在用户操作时不会觉得明显卡顿了。特别是一些高频交互场景,流畅度提升非常明显。

以上是我的优化经验

总的来说,这次Rematch性能优化的核心就是:减少不必要的状态更新扩散、优化计算开销、控制异步请求频率。每个环节单独看都不算特别复杂,但组合起来确实能带来显著的性能改善。

当然还有一些小细节没展开讲,比如如何更好地组织model结构、怎样设计高效的selector等等。如果大家有更好的优化方案,欢迎在评论区交流!后续我也会继续分享类似的实战经验。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论