React如何避免子组件无效刷新

 更新时间:2024年03月14日 10:43:03   作者:C+ 安口木  
这篇文章主要介绍了React几种避免子组件无效刷新的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

React如何避免子组件无效刷新

一个很常见的场景,React中父组件和子组件在一起,子组件不依赖于父组件任何数据,但是会一起发生变化。

在探究原理之前,先回忆一下,React中的Diff算法会将更新前后的两棵虚拟DOM树做对比,但这并不会决定组件是否更新,只会决定是否要复用老的节点。

举个简单的例子:

import { useState } from 'react';

const Child = () => {
  console.log('child render');

  return null;
};

const App = () => {
  const [name, setName] = useState(1);

  return (
    <div onClick={() => setName(2)}>
      <Child />
    </div>
  );
};

Child组件没有接收来自父组件的值,每次点击父组件元素让name更新,Child组件会更新吗?答案是会的,你一定会好奇,子组件没有接收任何的props,为什么也会更新呢?

首先,父组件经过了Diff阶段,会判断Child组件是否发生变化,在本案例中Child内部的元素结构和状态无任何变化,React还会对比Child组件前后的props是否相同,在本案例中,前后props不相同。

说到这里,你一定忍不住了,我都没传props,为啥不相同?原因是React内部对于props的对比只进行了浅层比较,通过 !== 来判断,这样即使没传props,每次生成的props对象都是新的指针,即使为空,也会生成不同的props空对象,就像这样:

typescript复制代码const oldProps = current.memoizedProps; // 更新前老的props

const newProps = workInProgress.pendingProps; // 待比较更新后的props

if (oldProps !== newProps) {
  didReceiveUpdate = true; // 标记为发生变化,需要更新
}

那有什么方法可以避免这样的无效更新呢?一共有三种方案。

  • 使用React.memo,可以指定在Diff时对于被memo包裹的组件只做浅层比较;
  • 使用React.useMemo或React.useCallback来包住子组件,让每次更新子组件都为同一个JSX对象,这也props的比较就会相同;
  • 将子组件作为children来传递;

React.memo

对于方案1,React.memo的原理其实来源于源码中的shallowEqual函数,该函数会接收两个对象,分别对应老的props和新的props,一共有四种比较策略,如果四种策略都通过,则判定新旧为同一个对象,不做更新,复用老的节点。

  • 判断两者是否为同一对象,不是同一对象则返回false;
  • 判断两者的值不为object或为null,则返回false;
  • 对比两者key的数量,不一致则返回false;
  • 对比两者key的值是否相同,不一致则返回false;

源码如下:

function shallowEqual(objA: mixed, objB: mixed): boolean {
  // 一样的对象返回true

  if (Object.is(objA, objB)) {
    return true;
  }

  // 不是对象或者为null返回false

  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
    return false;
  }

  const keysA = Object.keys(objA);

  const keysB = Object.keys(objB);

  // key数量不同返回false

  if (keysA.length !== keysB.length) {
    return false;
  }

  // 对应key的值不相同返回false

  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
      return false;
    }
  }

  return true;
}

可以看到浅比较props的实现原理很简单,对应着上述四种策略。

React.useMemo & React.useCallBack

对于方案2,如果你不了解React.useMemo和React.useCallback,没有关系,先看一下这段代码块:

import { useMemo } from 'react';

const Child = () => {
  console.log('child render');

  return null;
};

const App = () => {
  const [name, setName] = useState(1);

  const child = useMemo(() => <Child />, []);

  return <div onClick={() => setName(2)}>{child}</div>;
};

React.useMemo接收两个参数,第一个参数为返回值,第二个参数为依赖项,当依赖项数组中的值发生变化,则返回值会重新计算,也就是说第二个依赖项传空数组,则依赖项永远都不会发生变化,则Child组件经过React.useMemo包裹后一直不会被React去计算Diff,就实现了父组件更新,子组件不触发更新。

但对于React.useMemo的使用,如果传给了子组件的值,但是未声明依赖项,会导致子组件一直不发生变化,就像这样:

import { useMemo } from 'react';

const Child = ({ name }) => {
  console.log('child render');

  return name;
};

const App = () => {
  const [name, setName] = useState(1);

  const child = useMemo(() => <Child name={name} />, []);

  return <div onClick={() => setName(2)}>{child}</div>;
};

像这种情况,父组件将name传给了子组件,但是由于子组件未声明name为改变依赖项,因此当name发生变化,子组件依然会永远返回初始值1,因此对于React.useMemo的缓存策略在优化时也需要充分考虑意外事故发生。

向上提炼 & 向下移动

对于方案3,可以简单理解成向上提炼和向下移动state,先看一个案例:

const App = () => {
  const [color, setColor] = useState('red');

  return (
    <div>
      <input value={color} onChange={(e) => setColor(e.target.value)} />

      <p style={{ color }}>Hello, world!</p>

      <Child />
    </div>
  );
};

Input的onChange事件是一个频繁触发的颜色指示器,一秒会触发上百次,而Child组件是一个固定渲染不依赖父组件状态的子组件,如何通过状态向下移动的方式来避免Child组件被渲染呢?

const App = () => {
  return (
    <div>
      <Form />
      <Child />
    </div>
  );
};

我们只需要将这段性能消耗大的代码抽离到单独的一个Form组件中,同时把color状态单独交给Form组件去管理,这样App父组件一直没有发生重渲染,Child子组件也不会被影响,只有Form子组件在单独发生交互,这种方案更像是一个状态下移 + 隔离。

还有一种解法就是状态提升,我们可以把这段性能消耗严重的代码同样单独封装成一个组件,将Child子组件的内容传递给Form子组件,就像这样:

const Form = ({ children }) => {
  const [color, setColor] = useState('red');

  return (
    <div style={{ color }}>
      <input value={color} onChange={(e) => setColor(e.target.value)} />

      {children}
    </div>
  );
};

const App = () => {
  return (
    <div>
      <Form>
        <p>Hello, world</p>
        <Child />
      </Form>
    </div>
  );
};

其实思路是和状态向下提升是一样的,把性能消耗严重的一部分单独抽离到一个组件中,将相对不期望被影响的一部分通过特定形式渲染,因此Child子组件在这种情况也不会被重新渲染。

React强制刷新子组件方法

给子组件打个key值

每次操作更新个key就好了

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持程序员之家。

相关文章

  • 浅谈React碰到v-if

    浅谈React碰到v-if

    这篇文章主要介绍了浅谈React碰到v-if,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • react lazyLoad加载使用详解

    react lazyLoad加载使用详解

    lazy是React提供的懒(动态)加载组件的方法,React.lazy(),路由组件代码会被分开打包,能减少打包体积、延迟加载首屏不需要渲染的组件,依赖内置组件Suspense标签的fallback属性,给lazy加上loading指示器组件,Suspense目前只和lazy配合实现组件等待加载指示器的功能
    2023-03-03
  • 解决React报错Cannot assign to 'current' because it is a read-only property

    解决React报错Cannot assign to 'current'

    这篇文章主要为大家介绍了React报错Cannot assign to 'current' because it is a read-only property的解决方法,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • 详解使用React.memo()来优化函数组件的性能

    详解使用React.memo()来优化函数组件的性能

    本文讲述了开发React应用时如何使用shouldComponentUpdate生命周期函数以及PureComponent去避免类组件进行无用的重渲染,以及如何使用最新的React.memo API去优化函数组件的性能
    2019-03-03
  • 解决react组件渲染两次的问题

    解决react组件渲染两次的问题

    这篇文章主要介绍了解决react组件渲染两次的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • React实现合成事件的源码分析

    React实现合成事件的源码分析

    React?中的事件,是对原生事件的封装,叫做合成事件。抽象出一层合成事件,是为了做兼容,抹平不同浏览器之间的差异。本文将从事件绑定和事件触发角度,带大家解读下源码,感兴趣的可以了解一下
    2022-12-12
  • create-react-app构建项目慢的解决方法

    create-react-app构建项目慢的解决方法

    这篇文章主要介绍了create-react-app构建项目慢的解决方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • 归纳总结Remix?表单常用方法及示例详解

    归纳总结Remix?表单常用方法及示例详解

    这篇文章主要为大家归纳总结了Remix?表单常用方法及示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • 详解React开发必不可少的eslint配置

    详解React开发必不可少的eslint配置

    本篇文章主要介绍了详解React开发必不可少的eslint配置,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • ReactNative?状态管理redux使用详解

    ReactNative?状态管理redux使用详解

    这篇文章主要介绍了ReactNative?状态管理redux使用详解
    2023-03-03

最新评论

?


http://www.vxiaotou.com