memo、useCallback、useMemo的区别和用法
发布于2021-12-06 11:26:26阅读 3980react在渲染父子嵌套组件的时候,有时会发生不必要的渲染,根据经验总结出来,大致有四种情况需要处理:
- 父子组件嵌套,父组件未向子组件传值
- 父子组件嵌套,父组件向子组件传值,值类型为值类型
- 父子组件嵌套,父组件向子组件传值,值得类型为方法
- 父子组件嵌套,父组件向子组件传值,值得类型为对象
首先看第一种情况,看如下代码:
子组件:
function ChildComp () {
console.log('render child-comp ...')
return <div>Child Comp ...</div>
}
父组件:
function ParentComp () {
const [ count, setCount ] = useState(0)
const increment = () => setCount(count + 1)
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp />
</div>
);
}
代码解读:当点击父组件中的button按钮时,父组件中的count发生变化,父组件会重新渲染,但是此时子组件也会重新渲染,这是不必要的,该怎么解决呢?我们此时可以用memo来解决,memo函数的第一个参数是组件,结果返回一个新的组件,这个组件会对组件的参数进行浅对比,当组件的参数发生变化组件才会重新渲染,而上面的实例子组件根本没有传递参数,所以不会随着父组件渲染。
第二种情况,当父组件给子组件传值,当父组件传递的值是值类型,完全可以用memo来解决。
第三种情况当父组件给子组件传值,当父组件传递的值是方法函数,看代码:
子组件:
import React, { memo } from 'react'
const ChildComp = memo(function ({ name, onClick }) {
console.log('render child-comp ...')
return <>
<div>Child Comp ... {name}</div>
<button onClick={() => onClick('hello')}>改变 name 值</button>
</>
})
父组件:
function ParentComp () {
const [ count, setCount ] = useState(0)
const increment = () => setCount(count + 1)
const [ name, setName ] = useState('hi~')
const changeName = (newName) => setName(newName) // 父组件渲染时会创建一个新的函数
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp name={name} onClick={changeName}/>
</div>
);
}
父组件在调用子组件时传递了 name 属性和 onClick 属性,此时点击父组件的按钮,可以看到控制台中打印出子组件被渲染的信息。
我们看到meomo失效了,为什么因为memo进行的是浅对比,父组件重新渲染,changename等于重新生成了一次,所以子组件的props发生了变化,所以子组件也会跟着重新渲染,该怎么应对呢?这时就需要用到useCallback,useCallback
是一个函数,其参数是需要被缓存的方法,我们观察上面代码,发现changename方法需要被缓存,所用useCallback将其缓存一下,如何使用呢,useCallback类似函数装饰器,参数函数,结果返回一个新函数,看代码:
import React, { useCallback } from 'react'
function ParentComp () {
// ...
const [ name, setName ] = useState('hi~')
// 每次父组件渲染,返回的是同一个函数引用
const changeName = useCallback((newName) => setName(newName), [])
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp name={name} onClick={changeName}/>
</div>
);
}
此时点击父组件按钮,控制台不会打印子组件被渲染的信息了。
究其原因:useCallback() 起到了缓存的作用,即便父组件渲染了,useCallback() 包裹的函数也不会重新生成,会返回上一次的函数引用。
第四种情况父子组件嵌套,父组件向子组件传值,值得类型为对象,前面父组件调用子组件时传递的 name 属性是个字符串,如果换成传递对象会怎样? 下面例子中,父组件在调用子组件时传递 info 属性,info 的值是个对象字面量,点击父组件按钮时,发现控制台打印出子组件被渲染的信息。 代码如下:
import React, { useCallback } from 'react'
function ParentComp () {
// ...
const [ name, setName ] = useState('hi~')
const [ age, setAge ] = useState(20)
const changeName = useCallback((newName) => setName(newName), [])
const info = { name, age } // 复杂数据类型属性
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp info={info} onClick={changeName}/>
</div>
);
}
分析原因跟调用函数是一样的:
- 点击父组件按钮,触发父组件重新渲染;
- 父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。
使用 useMemo 对对象属性包一层。 useMemo 有两个参数: 第一个参数是个函数,返回的对象指向同一个引用,不会创建新对象; 第二个参数是个数组,只有数组中的变量改变时,第一个参数的函数才会返回一个新的对象。看代码:
function ParentComp () {
// ....
const [ name, setName ] = useState('hi~')
const [ age, setAge ] = useState(20)
const changeName = useCallback((newName) => setName(newName), [])
const info = useMemo(() => ({ name, age }), [name, age]) // 包一层
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp info={info} onClick={changeName}/>
</div>
);
}
再次点击父组件按钮,控制台中不再打印子组件被渲染的信息了。
以上便是memo、useCallback、useMemo的区别和用法,希望对你有所帮助。
原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 [email protected] 删除。
标签:count,const,name,渲染,useMemo,memo,useCallback,组件 From: https://www.cnblogs.com/sexintercourse/p/16778966.html