目录
- React.memo()
- 案例1: 无依赖项,无props
- 案例1: props比较机机制
- (1)传递基本类型,props变化时组件重新渲染
- (2)传递的是引用类型的prop,比较的是新值和旧值的引用
- (3)保证引用类型稳定,使用useMemo
- useCallback
- 案例1:不带依赖项数组
- 案例2:带依赖项数组
- 好书推荐
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小时的教学视频,可大幅提升学习效率。