首页 > 其他分享 >React.memo 解决函数组件重复渲染

React.memo 解决函数组件重复渲染

时间:2023-03-04 09:33:36浏览次数:43  
标签:const 渲染 memo React props 组件

为什么会存在重复渲染?

react 在 v16.8 版本引入了全新的 api,叫做 React Hooks,它的使用与以往基于 class component 的组件用法非常的不一样,不再是基于类,而是基于函数进行页面的渲染,我们把它又称为 functional component

因为 react hook 使用的是函数组件,父组件的任何一次修改,都会导致子组件的函数执行,从而重新进行渲染。

那么下面我们考虑三种情况:

  • 父组件没有 props 传入子组件 props
  • 父组件传入子组件的 props 都是简单数据类型
  • 父组件传入子组件的 props 存在复杂数据类型

React.memo 为高阶组件。它与 React.PureComponent 非常相似。默认只会对复杂类型对象做浅层比较,如果需要控制对比过程我们可以将比较函数作为第二个参数传入:React.memo(MyComp, areEqual)

父组件没有 props 传入子组件 props

在这种情况下,子组件的渲染不需要依赖父组件值的变化,使用 React.memo 包裹子组件,即缓存下子组件。这样,父组件中的数值如何变化,都会使用缓存下来的子组件。

父组件传入子组件的 props 都是简单数据类型

在这种情况下,父组件传入子组件的 props 都是简单数据类型,浅层对比即可判断是否发生了变化,使用 React.memo 包裹子组件,也可以解决重复渲染的问题。

父组件传入子组件的 props 存在复杂数据类型

父组件通过 props 向子组件传值时,可能需要传入复杂类型如 object,以及 function 类型的值。而 memo 子组件进行渲染比对时进行的是浅比较,即使我们传入相同的 objectfunction,子组件也会认为传入参数存在修改,从而子组件重新进行渲染。这个时候仅仅使用 memo 包裹子组件应该没办法解决问题了,是时候用上我们的 useCallback 以及 useMemo 了。

下面我们来看一下 React.memo 的使用。

React.memo 的使用

例如,一个父组件 Home 中渲染了子组件 List,同时 Home 组件还有一个计数器组件,每次点击 count 都会加 1,遇到类似的场景就会出现子组件重复渲染问题,这是因为 React 中当父组件的一个状态改变后,无论和子组件是否有关,子组件都会受到影响进行重新渲染,这也是 React 中默认的一个行为。

函数组件中的解决方案是使用 React.memo() 函数,将需要优化的函数组件传入即可。

import React, { useEffect, useState } from "react";

// 未使用 memo:const List = ({ dataList }) => {
const List = React.memo(({ dataList }) => {
  console.log("List 渲染");

  return (
    <div>
      {dataList.map((item) => (
        <h2 key={item.id}> {item.title} </h2>
      ))}
    </div>
  );
});

const Home = () => {
  const [count, setCount] = useState(0);
  const [dataList, setDataList] = useState([]);

  useEffect(() => {
    const list = [
      { title: "React 性能优化", id: 1 },
      { title: "Node.js 性能优化", id: 2 },
    ];
    setDataList(list);
  }, []);

  return (
    <div>
      <button type="button" onClick={() => setCount(count + 1)}>
        count: {count}
      </button>
      <List dataList={dataList} />
    </div>
  );
};

export default Home;

自定义控制对比过程

函数 React.memo() 还提供了第二个参数 propsAreEqual,用来自定义控制对比过程。

// React.memo() 的 TypeScript 类型描述
function memo<T extends ComponentType<any>>(
  Component: T,
  propsAreEqual?: (
    prevProps: Readonly<ComponentProps<T>>,
    nextProps: Readonly<ComponentProps<T>>
  ) => boolean
): MemoExoticComponent<T>;

使用memo, useMemo, useCallback

useCallback

先来说下,经常使用一些的 useCallback

// 仅使用了memo,父组件传递给子组件的prop为方法,
// 该方法在子组件中被调用,改变了父组件的值,导致父组件重新渲染。
// 又由于父组件重新渲染,传给子组件的方法因其引用地址的不同会被认为有修改,导致子组件出现了不必要的重新渲染。
const Child = memo((props) => {
    console.log('我是一个子组件');
    return (
        <button onClick={props.handleClick}>改变父组件中的年龄</button>
    )
})

const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    return (
        <div>
            <span>`目前的count值为${age}`<span>
            <Child handleClick={() => setAge(age + 1)}/>
        </div>  
    )
}
// 使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数
const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    return (
        <div>
            <span>`目前的年龄为${age}`<span>
            <Child handleClick={useCallback(() => setAge(age + 1), [])}/>
        </div>
    )
}

注意:在 useCallback 的第二个参数处要传入正确的依赖值,否则 useCallback 就不会重新执行,其中使用的变量就还是之前的值,useMemo也是如此。

我们在方法中可能会使用一些组件中但是存在方法外的参数,我们一定要将这些参数放入依赖项中,否则会一直使用缓存的方法,里面的外部参数也会一直是旧值。

useMemo

// 使用了memo以及useCallback,我们会发现更新属性profile为对象时,
// 尽管子组件只改变了age的值且子组件并没有使用age字段,子组件还是执行了。
// 这是因为在父组件更新其他状态的情况下,子组件的profile作为复杂类型,
// 仅仅进行浅比较会被认为存在修改,从而会一直重新渲染改变,导致子组件函数一直执行,这也是不必要的性能浪费。
// 解决这个问题,就需要在profile属性上使用useMemo了
const Child = memo((props) => {
    console.log('我是一个子组件');
    const {profile, handleClick} = props;
    return (
        <div>
           <div>{`父组件传来的用户信息:姓名${profile.name}, 性别${profile.gender}`}</div>
           <button onClick={handleClick}>改变父组件age</button>
        </div>
    )
})

const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    const [name, setName] = useState('张三男');
    const 
    return (
        <div>
            <span>`目前的年龄为${age}`<span>
            <Child
                profile={{name, gender: name.indexOf('男') > -1 ? '男' : '女' }}
                handleClick={useCallback(() => setAge(age + 1), [])}
            />
        </div>
        
    )
}
// 使用useMemo,返回一个和原对象一样的对象,第二个参数是依赖性,仅当name发生改变的时候,才产生一个新的对象,注意:依赖项千万要填写正确,否则name改变时,profile依旧使用旧值,就会产生错误

const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    const [name, setName] = useState('张三男');
    const 
    return (
        <div>
            <span>`目前的年龄为${age}`<span>
            <Child
                profile={useMemo(() => ({
                    name, 
                    gender: name.indexOf('男') > -1 ? '男' : '女' }), [name])
                }
                handleClick={useCallback(() => setAge(age + 1), [])}
            />
        </div>
        
    )
}

React.memo 无效情况

第一种

React.memo 对普通的引用类型是无效的。例如,在 List 组件增加 user 属性,即使使用了 React.memo() ,每次点击 count, List 组件还会重复渲染。

const Home = () => {
  const user = {name: '哈哈'};
  ...

  return (
    <div>
      <List dataList={dataList} user={user} />
    </div>
  );
};

React.memo() 结合使用时,普通引用类型对象需要通过 useMemouseState 处理,来避免组件的重复渲染。

const user = useMemo(() => ({ name: "哈哈" }), []);
const [user] = useState({ name: "哈哈" });

第二种

函数组中包括了一些 Hooks 例如 useStateuseContext,当上下文发生变化时,组件也同样会重新渲染,React.memo 在这里仅比较 props。上面例子中,如果把 button 组件放到 List 组件里,每次点击,List 也还是会被重新渲染。

const List = React.memo(({ dataList }) => {
  console.log("List 渲染");
  const [count, setCount] = useState(0);

  return (
    <div>
      <button type="button" onClick={() => setCount(count + 1)}>
        List count: {count}
      </button>
      {dataList.map((item) => (
        <h2 key={item.id}> {item.title} </h2>
      ))}
    </div>
  );
});

总结

React.memo() 是一个高阶组件,接收一个组件并返回一个新组件。它会记忆组件上次的 Props,同下次需要更新的 Props 做 “浅对比”,如果相同就不做更新,只有在不同时才会重新渲染。如果你的组件存在一些耗时的计算,每次重新渲染对页面性能显然是糟糕的,这时 React.memo() 对你来说也许是一个好的选择。并不是所有的组件都要引入 React.memo(),自身浅对比这个过程也会有一些消耗,如果没有特殊需求,也不一定非要使用。

  1. 子组件没有从父组件传入的 props 或者传入的 props 仅仅为简单数值类型使用 memo 即可
  2. 子组件有从父组件传来的方法时,在使用 memo 的同时,使用 useCallback 包裹该方法,传入方法需要更新的依赖值
  3. 子组件有从父组件传来的对象和数组等值时,在使用 memo 的同时,使用 useMemo 以方法形式返回该对象,传入需要更新的依赖值

标签:const,渲染,memo,React,props,组件
From: https://www.cnblogs.com/niuben/p/17177606.html

相关文章

  • React-crm的权限管理总结
    引言最近在写react-redux的后台管理系统,我觉得权限管理挺重要的,在此总结一下。大致流程配置axios,解决代理以调用接口获取数据用户登陆调用接口,获取传入token以允许登......
  • HelloReact
    单页面中使用React在单页面中使用React需要引入三个js:React的核心库、依赖核心库、babel(编译JSX)<scriptcrossoriginsrc="https://unpkg.com/react@16/umd/react.dev......
  • 从0搭建Vue3组件库(四): 如何开发一个组件
    本篇文章将介绍如何在组件库中开发一个组件,其中包括如何本地实时调试组件如何让组件库支持全局引入如何在setup语法糖下给组件命名如何开发一个组件目录结构在p......
  • Vue3父组件调用子组件内部的方法
    1.子组件中定义方法并通过defineExpose暴露出去import{reactive,defineExpose}from"vue";conststate=reactive({dataList:[],});constchangeData=()......
  • Go组件库总结之协程睡眠唤醒
    本篇文章我们用Go封装一个利用gopark和goready实现协程睡眠唤醒的库。文章参考自:https://github.com/brewlin/net-protocol1.gopark和goready的声明//go:linknamegopark......
  • 【前端开发】一个滑动滑块校验登录的组件思路(用vue写的)
    <template><el-dialog:visible.sync="dialogVisible"custom-class="slideVerifyDialog":close-on-click-modal="false"title="身份验证"widt......
  • SpringMVC_核心组件
    基础的四个组件。  一、DisapatcherServlet前端控制器,接受所有的请求。(配置为/则为所有不包括jsp的请求。/*则为所有请求)配置:在web.xml中配置一个前端控......
  • 译文:5个让人惊喜的React库
    译文:5个让人惊喜的React库欧巴菜菜web前端开发​关注  原文链接:https://dev.to/naubit/5-small-and-hidden-react-libraries-you-should-a......
  • 适用于 .NET 的开源文本差异对比组件
    适用于.NET的开源文本差异对比组件DotNet大王源码资料,微信zhaoxi965,有问必复​关注他 1人赞同了该文章对于开发人员来说,Git是我们经常使用......
  • Pandas read_csv:low_memory 和 dtype 选项
    df=pd.read_csv('somefile.csv')...给出错误:.../site-packages/pandas/io/parsers.py:1130:DtypeWarning:列(4,5,7,16)具有混合类型。在导入时指定dtype......