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

 更新时间:2022年11月10日 14:02:29   作者:CBDxin  
这篇文章主要介绍了React如何有效减少使用useContext导致的不必要渲染,使用useContext在改变一个数据时,是通过自己逐级查找对比改变的数据然后渲染,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

前言

在我们使用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(() => <span>data:{state.depData}</span>, [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<any>(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选中的数据被更新时,组件才会重新渲染。

到此这篇关于React实现控制减少useContext导致非必要的渲染详解的文章就介绍到这了,更多相关React useContext内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redux Toolkit的基本使用示例详解(Redux工具包)

    Redux Toolkit的基本使用示例详解(Redux工具包)

    这篇文章主要介绍了Redux Toolkit的基本使用,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • react中实现修改input的defaultValue

    react中实现修改input的defaultValue

    这篇文章主要介绍了react中实现修改input的defaultValue方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • React Hooks - useContetx和useReducer的使用实例详解

    React Hooks - useContetx和useReducer的使用实例详解

    这篇文章主要介绍了React Hooks - useContetx和useReducer的基本使用,本文通过实例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-11-11
  • React Navigation 使用中遇到的问题小结

    React Navigation 使用中遇到的问题小结

    本篇文章主要介绍了React Navigation 使用中遇到的问题小结,主要是安卓和iOS中相对不协调的地方,特此记录,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • React实现Step组件的示例代码

    React实现Step组件的示例代码

    这篇文章主要为大家详细介绍了React实现Step组件(步骤条组件)的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-01-01
  • React中的useEffect useLayoutEffect到底怎么用

    React中的useEffect useLayoutEffect到底怎么用

    这篇文章主要介绍了React中的useEffect useLayoutEffect具体使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02
  • 详解在React项目中安装并使用Less(用法总结)

    详解在React项目中安装并使用Less(用法总结)

    这篇文章主要介绍了详解在React项目中安装并使用Less(用法总结),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • React18+TS通用后台管理系统解决方案落地实战示例

    React18+TS通用后台管理系统解决方案落地实战示例

    这篇文章主要为大家介绍了React18+TS通用后台管理系统解决方案落地实战示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • React创建对话框组件的方法实例

    React创建对话框组件的方法实例

    在项目开发过程中,对于复杂的业务选择功能很常见,下面这篇文章主要给大家介绍了关于React创建对话框组件的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • React高阶组件的使用浅析

    React高阶组件的使用浅析

    高阶组件就是接受一个组件作为参数并返回一个新组件(功能增强的组件)的函数。这里需要注意高阶组件是一个函数,并不是组件,这一点一定要注意,本文给大家分享React高阶组件使用小结,一起看看吧
    2022-08-08

最新评论