React中的React.memo、useMemo、useCallBack
首先我们需要了解React组件的渲染机制:
React组件会在两种情况下发生重新渲染。第一种,当组件自身state发生变化的时候会重新渲染。第二种,当组件的父组件重
新渲染时,会重新渲染。第一种情况下的渲染无可厚非,state都变了,组件自然就重新渲染了,但是第二种情况下的渲染有时
候就不那么的必要。
在实际开发的过程中,我们常常会使用React.memo、useMemo、useCallBack来减少组件的重新渲染
1.React.memo
React.memo()是一个高阶组件,它接受另一个组件作为参数,并且会返回一个包装过的新组件。 包装过的新组件就会具有缓存
功能,包装过后,只有组件的props发生变化才会触发子组件的重新渲染,否则总会返回缓存中的结果
父组件:
interface LikeListProps {
likeList: Array<priceImg>
}
const useStyle = makeStyles((theme) => ({
root: {
marginTop: theme.spacing(3.75),
},
}));
const LikeList:React.FC<LikeListProps> = ({ likeList }) => {
const classes = useStyle();
const [value, setValue] = React.useState('');
const handleInput = (e:any) => {
setValue(e.target.value);
};
return (
<div className={classes.root}>
<input value={value} onChange={handleInput} />
<ChildComp />
</div>
);
};
子组件:
import React from 'react';
const ChildComp:React.FC = () => {
console.log('child render');
return (<div>child component</div>);
};
export default ChildComp;
控制台打印
childComp.tsx:4 child render
index.tsx:20 12
childComp.tsx:4 child render
index.tsx:20 123
childComp.tsx:4 child render
index.tsx:20 1234
childComp.tsx:4 child render
index.tsx:20 12345
childComp.tsx:4 child render
index.tsx:20 123456
childComp.tsx:4 child render
不难看出当子组件没有使用React.memo时,父组件输入框每一次输入时,子组件都会重新渲染。
当我们将子组件改为React.memo 创建时
子组件:
import React from 'react';
const ChildComp = React.memo(() => {
console.log('child render');
return (<div>child component</div>);
});
export default ChildComp;
控制台打印
child render
index.tsx:20 1
index.tsx:20 12
index.tsx:20 123
index.tsx:20 1231
index.tsx:20 12316
可以看出我们父组件每一次输入因为没有影响到子组件的props,并没有导致子组件更新,因此child render只会在组件第一次被渲染时打印
2.useMemo
useMemo和useCallback十分相似,useCallback用来缓存函数对象,useMemo用来缓存函数的执行结果。在组件中,会有一些函数具有十分的复杂的逻辑,执行速度比较慢。闭了避免这些执行速度慢的函数返回执行,可以通过useMemo来缓存它们的执行结果,像是这样:
const result = useMemo(()=>{
return 复杂逻辑函数();
},[依赖项])
useMemo中的函数会在依赖项发生变化时执行,注意!是执行,这点和useCallback不同,useCallback是创建。执行后返回执行结果,如果依赖项不发生变化,则一直会返回上次的结果,不会再执行函数。这样一来就避免复杂逻辑的重复执行。
当我们实现了一个方法,并且在方中输出一个字符串用于测试方法执行次数
组件代码:\
const sum = (a:number, b:number) => {
console.log('函数执行了');
return a + b;
};
const LikeList:React.FC<LikeListProps> = ({ likeList }) => {
const classes = useStyle();
const [value, setValue] = React.useState(1);
const handleClick = () => {
setValue((preState) => preState + 1);
};
const res = sum(123, 456);
return (
<div className={classes.root}>
<div>父组件</div>
<div>{res}</div>
<div>{value}</div>
<button onClick={handleClick} type="button" tabIndex={0}>点击修改value</button>
</div>
);
};
控制台输出:\
函数执行了
函数执行了
函数执行了
函数执行了
我们点击了四次按钮,sum方法就被执行了4次,然而sum函数的传参并没有改变,结果自然也不会改变,如果sum是一个很复杂的计算函数,显然这样会浪费性能
当我们使用useMemo,原组件调整为:\
const sum = (a:number, b:number) => {
console.log('函数执行了');
return a + b;
};
const LikeList:React.FC<LikeListProps> = ({ likeList }) => {
const classes = useStyle();
const [value, setValue] = React.useState(1);
const handleClick = () => {
setValue((preState) => preState + 1);
};
const res = React.useMemo(() => sum(123, 456), []);
return (
<div className={classes.root}>
<div>父组件</div>
<div>{res}</div>
<div>{value}</div>
<button onClick={handleClick} type="button" tabIndex={0}>点击修改value</button>
</div>
);
};
控制台输出:\
函数执行了
我们多次点击button修改 value的数量,也只会打印一次,当然实际开发过程中不会往函数里面传递字面量,那样我们只需要在依赖项数组里面添加依赖的变量值,
即可轻松完成缓存结果的效果
3.useCallBack
在我们实际开发过程中,随着功能不断开发,我们会发现React.memo并不是所有情况下都能成功缓存子组件,例如props中传递的值是一个函数的时候,我们虽然没有
去主动修改函数,但是在父组件作用域中创建的函数会随着父组件的重新调用而再次创建一个新的函数,我们都知道函数是引用数据类型,当函数被重新创建了的时候,
它传给子组件的props中的引用值也会被修改,这时候子组件就无法判断新函数是否还是之前那个函数,导致子组件更新。这时候我们就要用到useCallBack()了。
简介: useCallBack() 是一个钩子函数,用来创建React中的回调函数,并且为其创建缓存
useCallBack的参数:1. 回调函数 2. 依赖数组
-当依赖数组中的变量发生变化时,回调函数才会重新创建
-如果不指定依赖数组,则回调函数每一次都会重新创建(无意义)
实际编写一个useCallBack的使用场景
父组件:
const LikeList:React.FC<LikeListProps> = ({ likeList }) => {
const classes = useStyle();
const [value, setValue] = React.useState(1);
const handleClick = React.useCallback(() => {
setValue((preState) => preState + 1);
}, []);
console.log('父组件 render');
return (
<div className={classes.root}>
<div>
父组件
{value}
</div>
<ChildComp handleClick={handleClick} />
</div>
);
};
子组件
interface Props {
handleClick: ()=> void
}
const ChildComp:React.FC<Props> = ({ handleClick }) => {
console.log('child render');
return (
<>
<div>child component</div>
<button onClick={handleClick} type="button" tabIndex={0}>点击修改父组件</button>
</>
);
};
export default React.memo(ChildComp);
控制台打印:
父组件 render
childComp.tsx:8 child render
父组件 render
父组件 render
父组件 render
我们通过控制台的更新结果可以看出,每次当子组件点击按钮触发父组件传过来的回调函数的时候,子组件并没有重新渲染