【React】React18 Hooks 之memo、useCallback


在这里插入图片描述

useCallback官方地址
memo官方地址

React组件的默认渲染机制:

  • 只要父组件重新渲染,组件就会重新渲染。
  • 组件的自身的state发生了变化,组件就会重新渲染。

React的性能优化途径是之一就是对 组件、函数以及函数执行结果进行缓存,组件渲染时避开一些不必要的代码执行。

父组件每次重新渲染都触发子组件的重新渲染有的时候是没有必要的,即当子组件的内部数据不依赖于父组件此次的重新渲染,那么我们就没有必要去对子组件进行重新渲染。即有的组件无论如何渲染,每次的渲染结果都是相同的,很显然这种渲染是完全没有必要的。

为了减少这种组件的渲染,React提供了一个高阶函数React.memo(),可以用来缓存组件;React还提供useCallback钩子,可以缓存函数,依赖项不变的情况下,保持函数不会更新。

React.memo()

只要父组件重新渲染,React 就会重新渲染该组件。使用memo,你可以创建一个组件,只要其新 props 与旧props相同,React 就不会在其父组件重新渲染时重新渲染该组件.

用法:

memo(Component, arePropsEqual?) 

memo返回一个新的 React 组件,参数含义:

  • Component:要记忆的组件。不会memo修改此组件,而是返回一个新的记忆组件。任何有效的 React 组件(包括函数和forwardRef组件)均可接受。

  • arePropsEqual:接受两个参数的函数:组件的先前 props 及其新 props。默认情况下,React 会通过Object.is将每个 prop 与 进行比较.

案例1: 无依赖项,无props

// 1默认的渲染机制,子跟父一起渲染
// 2memo进行缓存,只用props发生变化的时候才会重新渲染(不考虑context)

import { useState,memo } from "react";

const MemoSon = memo(function Son(){
	console.log("我是子组件")
	return <div>this is Son</div>
})

function App() {
const [count,setCount] = useState(0)
  return (
    <div className="App">
		{count}
		<button onClick={()=>setCount(count+1)}>+</button>
     <MemoSon/>
    </div>
  );
}

export default App;

案例1: props比较机机制

(1)传递基本类型,props变化时组件重新渲染

传过来的props发生了变化,所以子组件更新


import { useState,memo } from "react";

const MemoSon = memo(function Son({count}){
	console.log("我是子组件")
	return <div>this is Son {count}</div>
})

function App() {
const [count,setCount] = useState(0)
  return (
    <div className="App">
		{count}
		<button onClick={()=>setCount(count+1)}>+</button>
     <MemoSon count={count}/>
    </div>
  );
}

export default App;

下面代码props传过去的固定的基本类型的值,点击按钮子组件不更新。

import { useState,memo } from "react";

const MemoSon = memo(function Son({count}){
	console.log("我是子组件")
	return <div>this is Son {count}</div>
})

function App() {
const [count,setCount] = useState(0)
const num =100;
  return (
    <div className="App">
		{count}
		<button onClick={()=>setCount(count+1)}>+</button>
     <MemoSon count={num}/>
    </div>
  );
}

export default App;

(2)传递的是引用类型的prop,比较的是新值和旧值的引用

点击加号按钮,App组件会重新渲染,声明的list就会有新的引用,所以子组件会重新渲染

import { useState,memo } from "react";

const MemoSon = memo(function Son({list}){
	console.log("我是子组件")
	return <div>this is Son {list}</div>
})

function App() {
const [count,setCount] = useState(0)
const num =100;
const list = [1,2,3];
  return (
    <div className="App">
		{count}
		<button onClick={()=>setCount(count+1)}>+</button>
     <MemoSon list={list}/>
    </div>
  );
}

export default App;

(3)保证引用类型稳定,使用useMemo

使用useMemo,组件渲染过程中缓存一个值,所以在点击按钮时,App组件重新渲染,而此时list还是之前的引用,故而,子组件不会重新渲染

import { useState, memo, useMemo } from "react";

const MemoSon = memo(function Son({ list }) {
  console.log("我是子组件")
  return <div>this is Son {list}</div>
})

function App() {
  const [count, setCount] = useState(0)
  const num = 100;
  // 空数组,只在组件渲染时执行一次。
  const list = useMemo(() => {
    return [1, 2, 3]
  }, [])
  return (
    <div className="App">
      {count}
      <button onClick={() => setCount(count + 1)}>+</button>
      <MemoSon list={list} />
    </div>
  );
}

export default App;

useCallback

在组件的顶层调用useCallback,组件多次重新渲染的时候缓存函数。

用法:

useCallback(fn, dependencies) 

useCallback钩子接收两个参数,内联回调函数和依赖数组。它将返回该回调函数的memoized函数,只有仅在某个依赖项改变时,回调函数会更新。

案例1:不带依赖项数组

App.js中,点击“+”按钮,增加count数值,可以看到父组件打印“父组件渲染”,子组件打印“子组件重新渲染”。是因为父组件更新之后,传递给子组件的函数changeHandler也更新了,所以导致子组件的props发生变化,子组件重新渲染。其中子组件用memo包裹,memo让你在组件的 props 不变的情况下跳过重新渲染组件。

import { useState, memo, useMemo, useCallback } from "react";
const Input =  memo(function Input({onChange}){
  console.log("子组件重新渲染")
  return <input type ="text" onChange={(e)=>onChange(e.target.value)}/>
})

function App() {
  console.log("父组件渲染")
  const [count, setCount] = useState(0)
  const changeHandler = value => console.log(value))
  return (
    <div className="App">
      {/* 把函数作为prop传递给子组件 */}
      <Input onChange = {changeHandler} />
      {count}
      <button onClick={()=>setCount(count+1)}>+</button>
    </div>
  );
}
export default App;  

在这里插入图片描述
其实可以看到子组件是没有必要重新渲染的,并且增加页面渲染的时间,逻辑复杂可能会卡顿。

使用useCallback修改上面的代码
点击加号按钮后,可以看到只有父组件打印了“父组件渲染”,子组件并没有重新渲染。父组件使用useCallback缓存了changeHandler,传递给子组件的函数changeHandler不会发生变化,还是之前的引用,所以子组件的props不会发生变化,子组件不会重新渲染。

  const changeHandler = useCallback( value => console.log(value),[])

在这里插入图片描述

案例2:带依赖项数组

这里只是举个例子,changeHandler函数依赖于count,发现点击加号按钮时,父组件,子组件均重新渲染,且在input输入框中输入数据,打印出count的值。

import { useState, memo, useMemo, useCallback } from "react";

import MegaBoost from "./MegaBoost";
const Input = memo(function Input({onChange}){
  console.log("子组件重新渲染")
  return <input type ="text" onChange={(e)=>onChange(e.target.value)}/>
}) 

function App() {
  console.log("父组件渲染")
  const [count, setCount] = useState(0)
  const changeHandler = useCallback( value=> console.log(count,'value') ,[count])
  return (   
    <div className="App">
      {/* 把函数作为prop传递给子组件 */}
      <Input onChange = {changeHandler} />
      {/* <MegaBoost handleClick={changeHandler} /> */}
      <button onClick={()=>setCount(count+1)}>+</button>
    </div>
  );
}

export default App;  

在这里插入图片描述

好书推荐

Vue.js 3.x+Express全栈开发:从0到1打造商城项目

《Vue.js 3.x+Express全栈开发 : 从0到1打造商城项目》是一本详尽的全栈开发教程,旨在通过Vue.js和Express框架引导读者从零开始构建一个完整的电商项目。内容覆盖电商项目的基本结构,以及Vue.js和Express的核心概念与架构;深入讲解Vue.js开发生态中的关键模块,包括网络请求、UI组件、路由管理和状态管理等;探讨Express框架的常用组件,如处理加密数据的中间件和与MySQL数据库交互的插件;最后指导读者打造一个完整的电商项目。在用户端,实现注册登录、商品浏览、购物车等功能;在服务端,完成用户验证、商品维护、订单处理等任务;在后台管理端,进行商品信息、订单数据等的管理与统计分析。通过阅读《Vue.js 3.x+Express全栈开发 : 从0到1打造商城项目》,读者能够掌握Vue.js和Express全栈开发技术,并独立完成电商项目的搭建与开发。《Vue.js 3.x+Express全栈开发 : 从0到1打造商城项目》还提供了完整的项目源码、代码导读手册以及长达30小时的教学视频,可大幅提升学习效率。
在这里插入图片描述

  • 31
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不叫猫先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值