前言
基础示例
需求: 编写个父子组件
父组件
import React, { useState } from 'react'
import Child from './child'
export default function Parent(props: any) {
const [num, setNum] = useState(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div>
<h2>{num}</h2>
<button onClick={handleClick}>更改num</button>
{/* 引用子组件 */}
<Child />
</div>
)
}
子组件
export default function Child(props: any) {
console.info('子组件渲染了')
return <div>我是子组件</div>
}
现象:
每次点击"更改num"按钮,控制台都会弹出"子组件渲染了"
React.memo
上面子组件里并没有依赖父组件任何属性,却在每次父组件更改时,都会被重新渲染,此时可以用 React.memo 优化。
定义
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
如果想要自定义对比过程,可以通过第二个参数来实现:
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
优化
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child) // 新增代码
export default function Parent(props: any) {
const [num, setNum] = useState(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div>
<h2>{num}</h2>
<button onClick={handleClick}>更改num</button>
{/* 引用子组件 */}
{/* <Child /> */}
{/* 新增代码 */}
<MemoChild />
</div>
)
}
现象: 每次点击"更改num"按钮,控制台都不再弹出"子组件渲染了" 那么这个示例是否完美了呢?如果说父子组件之间不通讯,那么这样就可行了。但父组件要传数据跟事件给子组件时,就仍然会有问题。
父组件传递事件给子组件示例
更改父组件代码为:
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
const handleChange = () => {
console.info(2323)
}
return (
<div>
<h2>{num}</h2>
<button onClick={handleClick}>更改num</button>
{/* 引用子组件 */}
{/* 新增代码 */}
{/* 父组件传递 handleChange 事件给子组件 */}
<MemoChild handleChange={handleChange}/>
</div>
)
}
现象:
每次点击"更改num"按钮,控制台都会弹出"子组件渲染了" 当传递事件给子组件时,点击更改 num ,控制台会再次打印出"子组件渲染了",说明子组件又重新渲染了。
分析原因:
首先一个组件重新渲染,一般是三种情况导致的:
- 组件自己的状态改变了
- 父组件重新渲染,但父组件的 props 没有改变
- 父组件重新渲染,父组件传递的 props 改变
很明显,第一种不是,因为子组件并没有任何状态。
第二种,已经用 React.memo 优化掉了,所以也不是。
那说明就是第三种, 传递的 props 改了。
那为什么传递的 handleChange 函数会发生改变呢?
因为在函数式组件里每次重新渲染,都会重新从头开始执行函数调用,那么这两次创建的 handleChange 函数肯定发生了改变,所以导致子组件重新渲染。
流程:
- 用户点击"更改num"按钮
- 父组件修改 num 的状态值
- 状态值改变导致父组件重新渲染
- handleChange 函数体会重新注册一遍
- 传递给 handleChange props 也就变了
- 子组件重新渲染
useCallback
找到原因,那么就要想法子,在函数没有改变的情况下,重新渲染的时候保持函数的引用一致
定义
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
优化
// 新增引入 useCallback
import React, { useState, useCallback } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
// 修改代码
// 通过 useCallback 进行记忆 handleChange, 并将记忆的 handleChange 传递给 MemoChild
const memoizedCallback = useCallback(() => {
console.info(2323)
},[])
return (
<div>
<h2>{num}</h2>
<button onClick={handleClick}>更改num</button>
{/* 引用子组件 */}
<MemoChild handleChange={memoizedCallback} />
</div>
)
}
现象:
每次点击"更改num"按钮,控制台都不再弹出"子组件渲染了" 可以看到子组件并不会重新渲染,那么是不是可以结束了呢?
不,父子组件之间除了传递事件,还会传递状态
父组件传递状态给子组件示例
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child) // 新增代码
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const [name] = useState<string>()
const handleClick = () => {
setNum(num + 1)
}
return (
<div>
<h2>{num}</h2>
<button onClick={handleClick}>更改num</button>
{/* 引用子组件 */}
{/* 新增代码 */}
{/* 父组件传递 name 状态给子组件 */}
<MemoChild name={name} />
</div>
)
}
现象:
每次点击"更改num"按钮,控制台都不会弹出"子组件渲染了"。 这不是正常的吗?这不是在逗我吗?不是的,请继续看下来
上面传递的 name 是基本类型,如果是对象呢? 更改代码如下:
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div>
<h2>{num}</h2>
<button onClick={handleClick}>更改num</button>
{/* 引用子组件 */}
{/* 修改代码 */}
{/* 父组件传递 name 状态给子组件 */}
{/* <MemoChild name={name} /> */}
<MemoChild person={{name:"张三"}} />
</div>
)
}
现象:
每次点击"更改num"按钮,控制台都会弹出"子组件渲染了"。 实际业务中,传递的对象肯定更复杂,我这里只是为了说明只要是个对象,就会导致子组件重新渲染。
原因跟上面传递事件是一样的,传递对象,每次重新渲染都指向新的引用,子组件 react.memo 进行浅比较,会得出传递的 props 不等了,所以重新渲染。
useCallback 是用来处理父子传递事件的优化,至于传递状态就要用到 useMemo。
useMemo
定义
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
优化
import React, { useState,useMemo } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div>
<h2>{num}</h2>
<button onClick={handleClick}>更改num</button>
{/* 采用 useMemo */}
<MemoChild person={useMemo(()=>({name:"张三"}),[])} />
</div>
)
}
现象:
每次点击"更改num"按钮,控制台都不会弹出"子组件渲染了"。
这下可以放心传递状态跟事件了。
总结
React hook 比起类组件有了更大的灵活度和自由,但同时对开发者要求也更高了。因为 Hooks 使用不恰当很容易出现性能问题。 memo,useMemo,useCallback 都是用来提升性能的。
- memo 相当于 shouldComponentUpdate
- useCallback 让 shouldComponentUpdate 可以正常发挥作用
- useMemo 避免频繁的昂贵计算,当然也可以用在像本文 props 状态上
那什么时候用这几个 api 呢?
实际上在简单的应用中,尽量少用,因为对于简单应用,就算重新渲染也不会消费多少资源,而采用这几个 api 时,每次都会对第二个参数进行比较,反而消耗了资源。
当然对于开销比较大的组件就尽量用 memo。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!