React实现控制减少useContext导致非必要的渲染详解_React

目录
前言
1.拆分context
2.使用useMemo包裹函数
前言
在我们使用useContext来进行数据流管理时,每当context更新时,所有使用到该context的组件都会重新渲染。如果我们的context的数据是由多个部分组成的,但只有其中一两个字段会频繁更新,但其他的数据都比较稳定时,这时,即使组件值使用到了比较稳定的那部分数据,但它依然会频繁渲染,这就很容易会导致性能问题。我们一般会使用拆分context或者结合useMemo来减少组件渲染的次数:

1.拆分context
我们可以通过将context拆分为承载不稳定数据的instableContext和承载稳定数据的stableContext。

const InstableStateContext = React.createContext();
const StableStateContext = React.createContext();
function Provider({children}) {
const [instableState, instableDispatch] = React.useState();
const [stableState, stableDispatch] = React.useState();
return (
<StableStateContext.Provider value={{state:stableState, dispatch:stableDispatch}}>
<InstableStateContext.Provider value={{state:instableState, dispatch:instableDispatch}}>
{children}
</InstableStateContext.Provider>
</StableStateContext.Provider>
)
}
在只使用稳定数据的组件中,我们只去使用stableContext,

//stableComponent.js
function stableComponent() {
const {state} = React.useContext(StableStateContext);
return …;
}
这能够让stableComponent.js只有在StableStateContext中的数据更新时,才会触发渲染,而不需要关心InstableStateContext

2.使用useMemo包裹函数
useMemo可以传入一个数据生成函数和依赖项,它可以使数据生成函数当且仅当依赖性发生变化时,才会重新计算要生成的数据的值。我们可以将组件的返回值使用useMemo进行包裹,把要使用的数据作为依赖项传入

const {state}= useContext(AppContext);
return useMemo(() => data:{state.depData}, [state.depData]);
在上面的例子中,当且仅当depData发生变化时,该组件才会重新渲染。

虽然上面两种方法都可以减少一些不必要的渲染,但写起来总觉得不够优雅(很麻烦)。下面我们来讲讲另一种减少使用useContext导致的不必要渲染的方法。

使用发布订阅减少使用useContext导致的不必要渲染

我们有没有办法做到只有在我们使用到的context数据发生变化时,才去触发渲染,而不需要使用useMemo进行繁琐的包裹呢。
我们可以创建这么一个store,它拥有一个getState方法可以用来获取context中存储的数据。

const [state, dispatch] = useReducer(this.reducer, initState);
const store = {
getState: () => state,
dispatch,
}
我们使用useMemo对store的值进行包裹,且deps为空数组:

const [state, dispatch] = useReducer(this.reducer, initState);
const store =useMemo(() => ({
getState: () => state,
dispatch,
}),[]);
这样store的值的引用便不会发生改变,如果把store作为context.Provider的value值进行传递:

Provider = (props: ProviderProps) => {
const { children, initState = {} } = props;
const [state, dispatch] = useReducer(this.reducer, initState);
//store值不会更新,所以不会触发渲染
const store = useMemo(
() => ({
getState: () => cloneDeep(state),
dispatch,
}),
[],
);
return <this.context.Provider value={store}>{children}</this.context.Provider>;
};
这样Provider下的组件便不会因为state的变化而触发渲染。但这样的话,因为store的值没有发生变化,provider内的组件便没有办法得知该何时去渲染了。这时我们引入发布订阅模式,来通知组件该何时渲染。当state发生变化时,我们会触发stageChange事件:

Provider = (props: ProviderProps) => {
const { children, initState = {} } = props;
const [state, dispatch] = useReducer(this.reducer, initState);
useEffect(() => {
//告知useSelector,state已更新,让它触发forceUpdate
this.emit(‘stateChange’);
}, [state]);
//store值不会更新,所以不会触发渲染
const store = useMemo(
() => ({
getState: () => cloneDeep(state),
dispatch,
}),
[],
);
return <this.context.Provider value={store}>{children}</this.context.Provider>;
在下面讲到的useSelector中会订阅此事件来告知组件需要重新渲染了。
接下来我们会实现一个useSelector方法,作为我们在组件内获取state中的数据的桥梁,他接收一个selector函数作为参数,如:

const a = useSelector(state=>state.a)
这样,我们就可以获取到state中的a。接下来我们要做的就是如何使得当state.a更新时,组件能够触发渲染,同时获取到最新的a。
上面说到,在useSelector中,我们会订阅stageChange事件,这时,我们会检查selector选中的数据有没有发生变化,有的话便使用forceUpdate进行强制渲染;

useSelector: UseSelector = (selector) => {
const forceUpdate = useForceUpdate();
const store = useContext(this.context);
const latestSelector = useRef(selector);
const latestSelectedState = useRef(selector(store.getState()));
if (!store) {
throw new Error(‘必须在Provider内使用useSelector’);
}
latestSelector.current = selector;
latestSelectedState.current = selector(store.getState());
useEffect(() => {
const checkForUpdates = () => {
const newSelectedState = latestSelector.current(store.getState());
//state发生变化时,检查当前selectedState和更新后的SelectedState是否一致,不一致则触发渲染
if (!isEqual(newSelectedState, latestSelectedState.current)) {
forceUpdate();
}
};
this.on(‘stateChange’, checkForUpdates);
return () => {
this.off(‘stateChange’, checkForUpdates);
};
}, [store]);
return latestSelectedState.current;
};
forceUpdate的原理也很简单,通过变更一个无用的状态来触发组件更新:

const useForceUpdate = () => {
const [_, setState] = useState(false);
return () => setState((val) => !val);
};
就这样,当我们在组件时使用useSelector时获取数据时,只有在selector选中的数据被更新时,组件才会重新渲染。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Hooks 是 React 16.8 中新增的特性,它可以让你在函数组件中使用 state、生命周期钩子等 React 特性。使用 Hooks 可以让你写出更简洁、可复用且易于测试的代码。 React Hooks 提供了一系列的 Hook 函数,包括 useState、useEffect、useContextuseReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect 和 useDebugValue。每个 Hook 都有特定的用途,可以帮助你处理不同的问题。 下面是 React Hooks 的一些常用 Hook 函数: 1. useState useState 是最常用的 Hook 之一,它可以让你在函数组件中使用 state。useState 接受一个初始状态值,并返回一个数组,数组的第一个值是当前 state 值,第二个值是更新 state 值的函数。 ``` const [count, setCount] = useState(0); ``` 2. useEffect useEffect 可以让你在组件渲染后执行一些副作用操作,比如订阅事件、异步请求数据等。useEffect 接受两个参数,第一个参数是一个回调函数,第二个参数是一个数组,用于控制 useEffect 的执行时机。 ``` useEffect(() => { // 这里可以执行副作用操作 }, [dependencies]); ``` 3. useContext useContext 可以让你在组件树中获取 context 的值。它接受一个 context 对象,并返回该 context 的当前值。 ``` const value = useContext(MyContext); ``` 4. useRef useRef 可以让你在组件之间共享一个可变的引用。它返回一个对象,该对象的 current 属性可以存储任何值,并在组件的生命周期中保持不变。 ``` const ref = useRef(initialValue); ref.current = value; ``` 5. useCallback useCallback 可以让你缓存一个函数,以避免在每次渲染时都创建一个新的函数实例。它接受一个回调函数和一个依赖数组,并返回一个 memoized 的回调函数。 ``` const memoizedCallback = useCallback(() => { // 这里是回调函数的逻辑 }, [dependencies]); ``` 6. useMemo useMemo 可以让你缓存一个计算结果,以避免在每次渲染时都重新计算。它接受一个计算函数和一个依赖数组,并返回一个 memoized 的计算结果。 ``` const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ``` 以上就是 React Hooks 的一些常用 Hook 函数,它们可以帮助你更好地处理组件状态、副作用、上下文和性能优化等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值