React memo使用解决高频组件渲染问题

问题:添加一个新的元素,导致所有子组件重复渲染问题。

期望:子组件不要频繁的渲染,只渲染新增或发生变化数据的子组件。

解决方案:使用memo包囊子组件。

代码:

子组件Cat.js:

import React from 'react';

const Cat = ({name})=>{
  console.log(`Cat init: ${name}`);
  return <p>{name}</p>
}
export default Cat;

父组件CatList.js:

import React, { useState } from 'react';
import { Button } from 'antd';
import Cat from '../components/demo-cats/Cat'; // 没有使用memo
/**
 * cat列表-父类
 * @returns 
 */
const CatList = () => {
  console.log("cat 父类");
  const [cats, setCats] = useState(["Cat 1","Cat 2","Cat 3"]);

  // add
  const handleAddCat = () => {
    setCats([...cats, prompt("请输入cat名字")]);
  }
  return (
    <div>
      <h2>List of Cat</h2>
      {
        cats.map((name, index)=>(<Cat key={index} name={name}></Cat>))
      }
      <Button onClick={() => handleAddCat()}>Add Cat</Button>
    </div>
  );
};
export default CatList;

运行结果:

可以看到新增一个New Cat之后,cats数组长度发生了变化,导致父类重新计算state,所有的子组件重新渲染了一次。

 解决思路:子组件用 React.memo() 包裹,如果组件在相同 props 的情况下渲染相同的结果,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

import React, { memo } from 'react';
import { Button } from 'antd';

const Cat = ({name, index, edit})=>{
  console.log(`PureCat init: ${name}`);
  return <p>{name} <Button onClick={() => edit(index)}>Edit</Button></p>
}
const PureCat = memo(Cat)
export default PureCat;

再次运行结果:

添加一个Memo Cat,只渲染了新增的子元素,不重复渲染之前的子元素。

再来看一个数组对象问题:

把上面cats改成数组对象,结合数组对象再来看一下数组的操作和调函数对子组件渲染的问题。

CatArray.js:

import React, { useState, useRef, useCallback } from 'react';
import { Button, Input } from 'antd';
// import Cat from '../components/demo-cats/Cat';
import Cat from '../components/demo-cats/PureCat';

// Cat数组对象 - demo
const CatArray = () => {
  const [cats, setCats] = useState([{ name: "Cat 1" },{ name: "Cat 2" }, { name: "Cat 3" }]);
  const refInput = useRef(null)
  // add
  const handleAddCat = () => {
    var newCat = { name: refInput.current.input.value };
    setCats([...cats, newCat]);
  }
  /**
   * 属性发生变化,重新计算states
   * @param {*} index 
   */
  const edit = (index) => {
    const catsCopy = [...cats]; //浅拷贝一下
    // setCats(catsCopy.map((item, key) => key === index ? { ...item, name: "Jony" } : item));
    catsCopy.splice(index, 1, {...catsCopy[index], name: "Jony"});
    setCats(catsCopy)
  }

  return (
    <div>
      <h2>Array Object of Cat</h2>
      {
        cats.map((item, index) => (<Cat key={index} edit={edit} index={index} name={item.name}></Cat>))
      }
      <Input ref={refInput} placeholder="Please input cat name"></Input>
      <Button onClick={() => handleAddCat()}>Add Cat</Button>
    </div>
  );
};

 备注:Cat是使用memo包囊的子组件。

新增一个名字为New Cat的Cat,添加到数组之后,即使memo包囊的子组件PureCat,子组件依然被重复渲染了。为什么?因为cats发生变化,state重新计算,导致父组件重新渲染,导致edit重新生成了新的方法,子组件props中edit发生了变化,导致子组件重新被渲染了。

React相关的知识:

渲染(Render)
Render基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。
react 处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。会将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。

何时触发渲染(Render)
组件挂载
setState() 方法被调用 ( 当 setState 传入 null 的时候,并不会触发 render )

使用React.useCallback解决父组件state发生变化,回调函数重新计算的问题。

  /**
   * useCallback依赖空数组,初始化时调用一次
   */
  const editCallBack = useCallback((index) => {
    const catsCopy = [...cats]; //浅拷贝一下
    // setCats(catsCopy.map((item, key) => key === index ? { ...item, name: "Jony" } : item));
    catsCopy.splice(index, 1, {...catsCopy[index], name: "Jony"});
    setCats(catsCopy)
	}, []);

使用useCallback把edit方法包起来,useCallback依赖空数组,初始化时生成子组件依赖的回调函数,回调函数仅在某个依赖项改变时才会更新。

总结
父组件中state(状态)改变,不受memo保护的子组件也会重新渲染
memo会检测props到改变来决定组件是否需要进行重新渲染,换言之就是,被memo函数包起来的组件只有本身的props被改变之后才会重新渲染
memo只能进行浅拷贝来校验决定是否触发重新渲染。所以改变数组(对象)的props时候记得返回一个全新的数组(对象)
memo不是项目中所有的组件都需要包一下。包的太多反而会起反效果,我们需要选择那些经常被重新渲染的组件有选择性的去缓存。

参考链接:

React 中 memo()、useCallback()、useMemo()_Jas555的博客-CSDN博客

react性能优化之memo的作用和memo的坑_工边页字的博客-CSDN博客_reactmemo

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值