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

目录
  • 前言
  • 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(() => <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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • React源码分析之useCallback与useMemo及useContext详解

    目录 热身准备 初始化mount mountCallback 更新 update 使用场景 总结 热身准备 createContext Provider Consumer useContext 初始化mount&更新update 总结 热身准备 useCallback和useMemo是一样的东西,只是入参有所不同. useCallback缓存的是回调函数,如果依赖项没有更新,就会使用缓存的回调函数: useMemo缓存的是回调函数的return,如果依赖项没有更新,就会使用缓存的return:

  • React 中的 useContext使用方法

    目录 什么是上下文呢? useContext使用的方法: 1.要先创建createContex 2.Provider 指定使用的范围 3.最后使用useContext useContext就是上下文 什么是上下文呢? 全局变量就是全局的上下文,全局都可以访问到它:上下文就是你运行一段代码,所要知道的所有变量 useContext使用的方法: 1.要先创建createContex 使用createContext创建并初始化 const C = createContext(null); 2.Prov

  • React函数组件useContext useReducer自定义hooks

    目录 一.hooks(useContext) 二.hooks(useReducer) 三.hooks(useContext搭配useReducer使用) 四.自定义hooks 一.hooks(useContext) 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值.当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定. 新建useContext.js

  • React 数据共享useContext的实现

    因为疫情, 最近接手一个新项目.是React的.上次写React已经过去1年多了. 虽然捡起来也不是什么难事,不过技术这个东西,长时间不用就容易忘记. 为了保证这个项目根其他平行项目的技术栈统一. 采用的是 Nextjs .styled-components.useContext .react-query.ts 今天不做重点介绍项目,还是说说在项目中碰到的问题. 这个足足折腾了差不多2个小时,我一直在排查其问题. 最后这个问题也是比较的诡异. ReferenceError: Cannot acc

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

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

  • React memo减少重复渲染详解

    目录 1. 概述 2. 使用 1. 概述 此方法是一个 React 顶层 Api 方法,给函数组件来减少重复渲染,类似于 PureComponent 和 shouldComponentUpdate 方法的集合体. React.memo顶层Api方法,它可以用来减少子组件的重复渲染次数,从而提升组件渲染性能. React.memo它是一个只能在函数组件中使用的顶层Api方法. 当父组件发生改变时,默认情况下它的子孙组件也会重新渲染,当某些子组件不需要更新时,也会被强制更新,为了避免这种情况,我们可

  • React如何利用Antd的Form组件实现表单功能详解

    一.构造组件 1.表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等. 这里先引用了封装的表单域 <Form.Item /> 2.使用Form.create处理后的表单具有自动收集数据并校验的功能,但如果不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用Form.create并自行处理数据 经过Form.create()包装过的组件会自带this.props.form属性,this.props.form提供了很多API来处理数据,如getFieldDe

  • Java @Async注解导致spring启动失败解决方案详解

    前言 在这篇文章里,最后总结处,我说了会讲讲循环依赖中,其中一个类添加@Async有可能会导致注入失败而抛异常的情况,今天就分析一下. 一.异常表现,抛出内容 1.1循环依赖的两个class 1.CycleService1 @Service public class CycleService1 { @Autowired private CycleService2 cycleService2; @WangAnno @Async public void doThings() { System.out

  • Java并发编程加锁导致的活跃性问题详解方案

    目录 死锁(Deadlock) 死锁的解决和预防 1.超时释放锁 2.按顺序加锁 3.死锁检测 活锁(Livelock) 避免活锁 饥饿 解决饥饿 性能问题 上下文切换 什么是上下文切换? 减少上下文切换的方法 资源限制 什么是资源限制 资源限制引发的问题 如何解决资源限制的问题 我们主要处理锁带来的问题. 首先就是最出名的死锁 死锁(Deadlock) 什么是死锁 死锁是当线程进入无限期等待状态时发生的情况,因为所请求的锁被另一个线程持有,而另一个线程又等待第一个线程持有的另一个锁 导致互相等

  • 使用 React 和 Threejs 创建一个VR全景项目的过程详解

    最近我在学习使用 React 配合 Three.js 来搭建一个可以浏览720全景图片的项目 实现的是加载一张 2:1 的720全景 分享一下我的创建过程 一.搭建框架并安装需要的插件 npx create-react-app parano // 创建一个 React 项目 npm install -S typescript // 安装 typescript,这个是类型辅助插件,与全景项目关系不大 npm install -S @types/three // 安装 typescript 支持的

  • 轻松入门正则表达式之非贪婪匹配篇详解

    非贪婪匹配 (.*?) import re a = '456qwe789rty123abc' re=re.findall('456(.*?)789',a) print(re) 通常情况,满足匹配规则"456(.*?)789"的内容通常不止一个,那么findall()函数会从字符串的起始位置开始寻找文本A,找到后开始寻找文本B,当找到第一个文本B后,暂时停止寻找,将文本A和文本B之间的内容存入列表:然后继续寻找文本A,并重复之前的步骤,直到到达字符串的结束位置,并将所有匹配到的内容存入列

  • React实现多个场景下鼠标跟随提示框详解

    目录 前言 实现原理 固定定位实现 MousePositionDemo MousePositionModal 绝对定位(相对于整个浏览器窗口) MousePositionDemo MousePositionModal 绝对定位和相对定位(相对于鼠标跟随框的父元素) MousePositionDemo MousePositionModal2 最后 前言 鼠标跟随框的作用如下图所示,可以在前端页面上,为我们后续的鼠标操作进行提示说明,提升用户的体验.本文将通过多种方式去实现,从而满足不同场景下的需求

  • 从零开始最小实现react服务器渲染详解

    前言 最近在写 koa 的时候想到,如果我部分代码提供api,部分代码支持ssr,那我应该如何写呢?(不想拆成 2个服务的情况下) 而且最近写的项目里面也用过一些服务端渲染,如nuxt,自己也搭过next的项目,确实开发体验都非常友好,但是友好归友好,具体又是如何实现的呢,诸位有没有考虑过? 本着求真务实的折腾态度,选了react作为研究对象(主要是vue写的有点多,恶心了),那下面就简单就以最小成本写一个react的服务端渲染 demo 用到的技术栈 react 16 + webpack3 +

  • react使用mobx封装管理用户登录的store示例详解

    1.MobX 介绍 MobX 是一个简单.可伸缩的响应式状态管理库.通过 MobX 你可以用最直观的方式修改状态,其他的一切 MobX 都会为你处理好(如自动更新UI),并且具有非常高的性能.当状态改变时,所有应用到状态的地方都会自动更新. 1.1 React和Mobx关系 React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染.而MobX提供机制来存储和更新应用状态供 React 使用. 1.2 核心概念 State:驱动应用的数据 Computed values:计算值.如果你想

随机推荐