减少渲染的节点/降低渲染计算量
0️⃣ 不要在渲染函数里面进行不必要的计算
1️⃣ 减少不必要的嵌套 一般不必要的节点嵌套都是滥用高阶组件/RenderProps 导致的 有很多种方式来代替高阶组件/RenderProps,例如优先使用 props、React Hooks
2️⃣ 虚拟列表 实现方案: 1.intersectionobserver只渲染在可视区的内容 只观察第一个跟最后一个元素,比如一个list,当最后一个元素进入可视区的时候,渲染的第一个元素变成最后一个元素的下一个元素 2.滚动加载
3️⃣ 惰性加载
精细化渲染
- 简化 props ① 如果一个组件的 props 太复杂一般意味着这个组件已经违背了‘单一职责’,首先应该尝试对组件进行拆解. ② 另外复杂的 props 也会变得难以维护, 比如会影响shallowCompare效率, 还会让组件的变动变得难以预测和调试.
hooks优化
- useState初始化惰性函数
function Table(props) {
// ⚠️ createRows() 每次渲染都会被调用
const [rows, setRows] = useState(createRows(props.count));
// ...
}
为避免重新创建被忽略的初始 state,我们可以传一个 函数 给 useState:
function Table(props) {
// ✅ createRows() 只会被调用一次
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}
当initialState以函数形式传入时,它只会在函数组件初始化的时候执行一次,函数re-render时不会再被执行
以上尚待考究;
useState(props.value)
并不会随着props的变化而变化,初始化值只会初始一次,同样的useRef也是一样
优化key
除了以下的情况不使用index作为key:
列表的重新排序,增删,过滤不会发生
减少re-render
哪些原因会导致rerender
-
状态更改
- 当组件的状态发生变化时,它将重新渲染自身。需要注意的是,类组件的state被重复设置成同一个值会导致重新渲染,但是函数组件不会。
-
父级(或子级)重新渲染
注意是父组件的重新渲染触发了子组件的重新渲染,与子组件的 props 是否变化无关。只有那些使用了 React.memo 和 useMemo 的组件,props 的变化才会触发组件的重新渲染。 -
context 变化
-
hooks 变化。
- hooks 内部的状态变化会触发组件的 re-render
- 如果 hooks 使用了 context,并且 context 的值发生了变化,也会触发组件的 re-render
怎么避免re-render
-
避免在 render 函数中创建组件,原因如下: 在每次重新渲染时,React 都会重新装载这个组件(即销毁它并从头开始重新创建),这比正常的重新渲染要慢得多。除此之外,还会导致以下问题:
- 在重新渲染期间可能出现内容 “闪烁”
- 每次重新渲染时在组件的状态会被重置
- 每次重新渲染时不会触发依赖项的 useEffect
- 如果组件被聚焦,则焦点将丢失
-
state 下移到子组件中
-
组件作为 props
与前面的模式基本相同,将状态封装在一个较小的组件中,而较重的组件作为 props 传递给它。props 不受 state 变化的影响,因此该较重的组件不会被重新渲染
- 使用 React.memo
常见误区
- 无用的 useMemo/useCallback props
将子组件的 props 包装成 memoize 值,是不能避免该子组件重新渲染的。
只要父组件重新渲染,那么子组件就会被重新渲染,与 props 没有关系。这个时候就需要用到React.Memo了
错误做法
正确做法
- 正确使用key 如果列表项是一个组件,父组件重新渲染了,仅仅设置 key 值并不会提高列表的性能。为了避免列表元素的重新渲染,还要用 React.memo 包装它们
- 避免 Context 引起 re-render
- 将 Provider 的值做 memoize 处理
如果 Context Provider 没有在页面的根节点上,那么祖先节点的变化也会导致它被重新渲染,所以它的值也应该被 memoize。
- 对数据和 API 做拆分
- @rematch/core 使用时:useContext,无需在根组件包裹store,只需要
init({ models: { global } });
- 使用
createPortal创建的组件A,插入到一个dom节点,dom节点所在的组件被重新渲染的话,是不会引发组件A的重新渲染的。
context优化
- 拆分context
- useMemo React.memo做缓存
- 借鉴redux的发布订阅模式
// provider
const Provider = ({ children }) => {
const [store, dispatch] = useReducer(reducer, initState);
const storeRef = useRef(null);
storeRef.current = store;
// 订阅所有子组件更新的回调函数
const subscribeRef = useRef([]);
// store变化的时候执行所有的监听函数
useEffect(() => {
subscribeRef.current.forEach((sub) => sub());
}, [store]);
// 缓存value 利用ref拿到最新的状态
const value = useMemo(
() => ({
dispatch,
subscribe: (cb) => {
subscribeRef.current.push(cb);
// return unsubscribe
return () => {
subscribeRef.current = subscribeRef.current.filter(
(item) => item !== cb
);
};
},
getState: () => storeRef.current,
}),
[]
);
// value此后都不会变化了 只有store变化 store变化的时候通知所有的消费者
return <MyContext.Provider value={value} children={children} />;
};
// useSelector
export const useSelector = (selector) => {
const [, forceRender] = useReducer((s) => s + 1, 0);
const store = useContext(Mycontext);
// 获取state
const selectedStateRef = useRef(null);
selectedStateRef.current = selector(store.getState());
// 更新回调 对比更新后的state
const checkUpdates = useCallback(() => {
const newState = selector(store.getState());
if (newState !== selectedStateRef.current) {
forceRender();
}
}, []);
// mounted的时候订阅store
useEffect(() => {
const unsubscribe = store.subscribe(checkUpdates);
// 卸载的时候取消订阅
return unsubscribe;
}, [store, checkUpdates]);
// 返回最新的state
return selectedStateRef.current;
};
useDispatch
使用:
参考
- (一份详尽的 React re-render 指南)[mp.weixin.qq.com/s/SH7N2f5Zh…]