被动触发的React Context性能优化模式
前言
Context
可以让你从组件中读取和订阅上下文。
通常,我们是这么使用的
import { useContext } from 'react';
function MyComponent() {
const theme = useContext(ThemeContext);
// ...
被动触发
很多时候,一个context
中会包含很多个变量,某个组件可能只会依赖其中一个变量。更进一步,可能只依赖某个变量的特殊变化。
但是只要依赖了一个context
,其value
的变化总会导致整个组件重新进行渲染,或者某个值的变化在当前业务场景下,不需要组件做出改变,也会造成不必要的开销。
因此,我们换种思路,value
存储一个注册函数,当state
变化时,依次调用注册函数数组中的函数。由于value
本身没有发生变化,依赖此上下文的组件,都不会受影响。而真正处理是否要更新组件的函数,由依赖此上下文的组件自身控制。
代码实现
首先定义context
export interface IPassivityValue {
value: number;
}
export interface IPassivityContext {
addEventListener: (callback: (opts: IPassivityValue) => void) => () => void;
}
export const PassivityContext = React.createContext<IPassivityContext>({} as any);
然后,实现对应的Provider
组件,组件仅暴露一个注册函数,用来将业务处理函数进行注册
/**
* 定义Provider组件
* @param param0
* @returns
*/
export function PositionAndScaleProvider({ children }: { children: ReactNode }) {
const eventListeners = useRef<Array<(opts: IPassivityValue) => void>>([]).current;
// todo 存储value,需自定义如何调用setState,或直接不使用state
const [state, setState] = useState({} as any);
// state变更时,逐个调用回调函数
useEffect(()=>{
eventListeners.forEach(fn=> fn(state));
},[state])
const addEventListener = useCallback(
(callback: (opts: IPassivityValue) => void) => {
eventListeners.push(callback);
return () => {
eventListeners.splice(eventListeners.indexOf(callback), 1);
};
},
[eventListeners]
);
const value = useMemo(() => {
return {
addEventListener,
// 可以把dispatch也放进来用于变更state
};
}, [addEventListener]);
return <PassivityContext.Provider value={value}>{children}</PassivityContext.Provider>;
}
再封装两个hooks
/**
* 实现专用函数
* @returns
*/
export const usePassivityContext = () => {
return useContext(PassivityContext);
};
/**
* 将注册与取消逻辑封装
* @param fn
*/
export const usePassivityContextListener = (fn: (opts: IPassivityValue) => void) => {
const { addEventListener } = usePassivityContext();
useEffect(() => {
const disposal = addEventListener(fn);
return () => {
disposal?.();
};
}, [addEventListener, fn]);
};
最后验证一下
function TestContext() {
const [visible, setVisible] = useState(false);
const visibleRef = useRef(visible);
visibleRef.current = visible;
// 函数内部自行判断是否要变更,通常使用ref存储上一次的状态,用于做比较
const handler = useCallback(({ value }: IPassivityValue) => {
if (value > 9 && !visibleRef.current) {
setVisible(true);
return;
}
if (visibleRef.current) {
setVisible(false);
}
}, []);
// 在业务组件中注册回调函数
usePassivityContextListener(handler);
return null;
}
handler
里可以加入一些业务逻辑来明确是否需要更新自身的state
,也可以直接调用句柄执行某些动作
转载自:https://juejin.cn/post/7362734937214304310