由来
我们做项目经常会碰到一种场景,父组件状态变更需要重新render,通常情况下会连带着所有子组件一起重新render。
当我们传递某个子组件props并没有改变,该子组件并不需要re-render
在之前的class组件中,我们可以使用 PureComponent 或 shouldComponentUpdate 来控制子组件是否需要渲染 于是为了处理函数组件渲染优化性能 memo、useMemo、useCallback 应运而生
React.memo
React.memo
仅检查 props 变更,默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
注意:但是函数中存在useState
useReducer
或 useContext
的 Hook,当 state 或 context 发生变化时,它仍会重新渲染
import { ChangeEvent, memo,useState } from "react"
interface IProps{
count?:number
value?:string
}
const Child=(props:IProps):any=>{
console.log('子组件render');
return (
<div>子组件count:{props.count}</div>
)
};
//不使用memo
const ChildMemo=Child
//1 使用memo
//const ChildMemo=memo(Child)
//2 使用memo控制对比,第二个参数,true则re-render,false不re-render
// const ChildMemo=memo(Child,(prevProps, nextProps)=>{
// if(prevProps.count===1){
// return true
// }else {
// return false
// }
// })
const Home =() =>{
const [count, setCount] = useState(0)
const [value, setValue] = useState('null');
const addClick= () =>{
setCount(count + 1)
}
const handleChange=(e: ChangeEvent<HTMLInputElement>)=>{
setValue(e.target.value)
}
console.log('父组件render')
return (
<div>
<p>父组件{count} times</p>
<button onClick={addClick}>Click me</button>
<input onChange={(e) => handleChange(e)} />
<ChildMemo count={count}></ChildMemo>
</div>
);
}
不使用React.memo时,value值变化时,子组件还是会重新渲染,然而子组件没有依赖value值时并不需要渲染 然后我们看下加上React.memo
父组件改变value并不会影响子组件re-render 当我们memo第二个参数时,可以在加一下准确的判断条件进而控制,当我们加了prevProps.count===1返回true,当父组件count不等于1时,子组件已经不会再re-render
useMemo 及useCallback
useMemo功能是判断组件中的函数逻辑是否重新执行,如果useMemo返回的是一个函数,则可以用useCallback省略顶层的函数,useCallback是useMemo的变体,这两个hooks都返回缓存的值,useMemo返回缓存的变量、useCallback返回缓存的函数 下面看下useMemo如何优化的
import { ChangeEvent, memo,useMemo,useState } from "react"
interface IProps{
onButtonClick?:()=>void,
count?:number
value?:string
}
const Child=(props:IProps):any=>{
console.log('子组件render');
return (
<div>子组件count:{props.count}</div>
)
};
const ChildMemo=memo(Child)
const Home =() =>{
const [count, setCount] = useState(0)
const [value, setValue] = useState('null');
const addClick= () =>{
setCount(count + 1)
}
const handleChange=(e: ChangeEvent<HTMLInputElement>)=>{
setValue(e.target.value)
}
/**不使用useMemo */
const childmemo=(()=>{
let result = Math.random() * count;
console.log('render-result')
return result;
})()
/** 使用useMemo*/
// const childmemo=useMemo(()=>{
// let result = Math.random() * count;
// console.log('render-result')
// return result;
// },[count])
console.log('父组件render')
return (
<div>
<p>父组件{count} times <br/>使用useMemo的计算count {childmemo}</p>
<button onClick={addClick}>Click me</button>
<input onChange={(e) => handleChange(e)} />
<ChildMemo count={count}></ChildMemo>
</div>
);
}
不使用useMemo这个时候我们看到childmemo的方法,在改变value的输入框,也会随着变化,但是我们childmemo依赖的是count,不应该变化
当我们使用useMemo可以看到log只有count变化,值才跟着变化,这个类似vue里面computed
useCallback是useMemo的延伸,再记忆一下useMemo返回缓存的变量、useCallback返回缓存的函数 我们把上面的修改一下,假如childmemo返回的是函数时
const childmemo=useMemo(()=>{
return ()=>{
let result = Math.random() * count;
console.log('render-result')
return result;
}
},[count])
这个时候,就会出现useMemo不生效的,这个时候我们就可以使用useCallback修改后,省略上层函数,改写后
import { ChangeEvent, memo,useCallback,useEffect,useLayoutEffect,useMemo,useRef,useState } from "react"
interface IProps{
callback?:()=>number,
count?:number|undefined
value?:string
}
const Child=(props:IProps):any=>{
console.log('子组件render');
let value:number| undefined=0
useEffect(()=>{
value=props.callback&&props.callback()
console.log('callback更新了',value);
},[props.callback])
return (
<div>子组件count:{props.count}
</div>
)
};
const ChildMemo=memo(Child)
const Home =() =>{
const [count, setCount] = useState(0)
const [value, setValue] = useState('null');
const addClick= () =>{
setCount(count + 1)
}
const handleChange=(e: ChangeEvent<HTMLInputElement>)=>{
setValue(e.target.value)
}
const childmemo=useCallback(()=>{
console.log('render-result')
let result = Math.random() * count;
return result
},[count])
console.log('父组件render')
return (
<div>
<p>父组件{count} times</p>
<button onClick={addClick}>Click me</button>
<input onChange={(e) => handleChange(e)} />
<ChildMemo count={count} callback={childmemo}></ChildMemo>
</div>
);
}
子组件接收,count值更改时,才会触发子组件props.callback的,去掉useCallback则修改value值时,childmemo也会执行。
以上都是个人记录理解的一个过程,如有问题望指正~