likes
comments
collection
share

被动触发的React Context性能优化模式

作者站长头像
站长
· 阅读数 49

前言

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
评论
请登录