前言:先回顾下在hooks诞生之前,我们通过写class 组件的方式开发新组件的,继承是 class 本身的特性,它支持设置 state,会在 state 改变后重新渲染,可以重写一些父类的方法,这些方法会在 React 组件渲染的不同生命周期调用。
特性:只要父组件的状态更新,无论有没有对子组件进行操作,子组件都会进行更新
基于以上class的特性,如果要优化性能就是:使用immutable进行比较,在不相等的时候调用setState;在shouldComponentUpdate中判断前后的props和state,如果没有变化,则返回false来阻止更新。
在hooks出来之后,让函数组件可以做类组件的事,可以有自己的state,可以处理一些副作用,获取ref。但是function 组件不能做继承,react不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。useMemo 和useCallback就是解决性能问题的杀手锏。
useMemo和useCallback的区别
useMemo | useCallback | |
---|---|---|
参数 | 第一个参数为回调 第二个参数为要依赖的数据 | 第一个参数为回调 第二个参数为要依赖的数据 |
返回值 | 缓存计算结果的值 | 缓存的函数 |
应用场景 | 需要计算的状态 | 函数式组件每次任何一个 state 的变化 整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。 |
useCallback 也可以理解为 useMemo的语法糖。 useCallback((x) => { log(x) }, [m]) 等价于 useMemo(() => { (x) => { log(x) } }, [m])
主要区别是 useMemo 将调用 fn 函数并返回其结果,而 useCallback 将返回 fn 函数而不调用它
注意: 不要滥用会造成性能浪费,react中减少render就能提高性能,所以这个仅仅只针对缓存能减少重复渲染时使用和缓存计算结果。
useMemo
useMemo 用来返回一个结果值,避免无用方法的调用
如果不用useMemo,无论是修改maxTimes还是typeVal,由于组件的重新渲染,都会触发validateStatus的执行(能够在控制台看到,即使修改val,也会打印);但是这里的validateStatus计算只依赖于maxTimes的值,在typeVal修改的时候,是没有必要再次计算的。
const validateStatus = (value: string) => {
console.log(value, '计算');
const reg = /^\d*$/;
if (!reg.test(`${value}`)) {
return '请填入正整数';
}
if (Number(value) > 10 * 1000) {
return '时间最多不超过10000';
} else if (Number(value) < 1) {
return '时间最少不小于1';
}
return '';
};
......
<input value={typeVal} onChange={event => setValue(event.target.value)}/>
<Row className={styles.tableselect}>
{useMemo(() => {
return (
<>
<Row>
<Col className="ant-col ant-form-item-label">
<label className="ant-form-item-required" title="设置时间">
设置时间
</label>
</Col>
<InputNumber
value={maxTimes}
onChange={onTimesChange}
style={{
verticalAlign: 'middle',
borderBottomRightRadius: 0,
borderTopRightRadius: 0,
width: 120,
}}
/>
</Row>
<div
className="ant-input-group-addon addon-affter"
style={{
paddingTop: '2px',
verticalAlign: 'middle',
display: 'inline-table',
lineHeight: '24px',
height: '32px',
borderLeft: '0',
}}
>
秒
</div>
<Col className="ant-form-item-explain ant-form-item-explain-error valid-error">
{validateStatusTimes(maxTimes)}
</Col>
</>
);
}, [maxTimes, onTimesChange])}
</Row>
上面我们可以看到,使用useMemo来执行计算时,将maxTimes作为依赖值传递进去。这样,就只会在maxTimes改变的时候触发validateStatus执行,在修改typeVal的时候,返回上一次缓存的值,不会执行validateStatusTimes。
useCallback
useCallback 用来返回一个函数,用在父子组件传参或者通用函数封装中
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
// 父级调用子级时,在onClick参数上加上useCallback,参数为[],则第一次初始化结束后,不在改变。
const Child = ({ name, onClick}: ChildProps): JSX.Element => {
console.log('子组件')
return(
<>
<div>我是一个子组件,父级传过来的数据:{name}</div>
<button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
</>
);
}
const ChildMemo = memo(Child);
const Page = (props) => {
const [count, setCount] = useState(0);
const [name, setName] = useState('Child组件');
return (
<>
<button onClick={(e) => { setCount(count+1) }}>加1</button>
<p>count:{count}</p>
<ChildMemo name={name} onClick={ useCallback((newName: string) => setName(newName), []) }/>
{/* useCallback((newName: string) => setName(newName),[]) */}
{/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数*/}
</>
)
}
注意:useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这两种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问