简介
1 .优化子组件的渲染次数
2 .父组件重新渲染组件的时候,传给子组件的函数类型的props也会重新生成,所以子组件也会再次重新render一遍
3 .useCallback可以保证,无论render多少次。我们的函数都是同一个函数,减小不断创建的开销。
4 .主要是配和Memo用于优化子组件的渲染次数
import React,{useState,useCallback} from 'react'
function Parent(){
const [count,setCount]=useState(0)
function handleParentClick(){
setCount(count+1)
// 当setState的时候
}
function handleChildren(){
console.log('click children')
}
const handleChildrenCallback=useCallback(()=>{
handleChildren()
},[])
// 用callBack包装一下需要往下传的函数
return(
<div>
<div>
<span onClick={handleParentClick}>add parent</span>{count}
</div>
<Child handleChildren={handleChildrenCallback}></Child>
</div>
)
}
// 传的组件必须是需要React.memo装饰一下,不然是不起效的
const Child=React.memo((props)=>{
console.log('又渲染了一次')
const {handleChildren}=props
return (
<div onClick={handleChildren}>
children
</div>
)
})
export default Parent
作为props。传递给子组件,为了避免子元素不必要的渲染,需要配合React.Memo,或者useMemo使用否则没有意义
useMemo
import React,{useEffect,useState,useCallback,useMemo} from "react";
const Child = () => {
alert('更新了');
return <div>
lal
</div>
}
export default function App() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
}, [count]);
const Children = useMemo(() => <Child callback={callback}/>,[callback])
// 这种返回形式,需要{Children}来写
console.log(Children)
return <div>
<h4>{count}</h4>
{
Children
}
<Child />
{/* 这个是没有包装的 */}
{/* <Children /> */}
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
只有当该方法会被拿去做引用对比的时候,才需要写useCallback,否则性能反而不会提升,普通的方法可以不必要加
true === true // true
false === false // true
1 === 1 // true
'a' === 'a' // true
{} === {} // false
[] === [] // false
() => {} === () => {} // false
const z = {}
z === z // true
// NOTE: React 使用的是 Object.is, 和 === 非常类似
优化往往都是有成本的
import React,{useEffect,useState,useCallback,useMemo} from "react";
const Child = (props) => {
console.log('更新了外面',props);
useEffect(()=>{
console.log('更新了count')
},
// [props.count]
[props.obj]
)
//这里想要的结果是当传入的props发生变化,才执行这个操作
// 但是useEffect将对每次渲染中对props进行引用相等性检查,由于react和js的机制,每次渲染的props(如果是对象object类型)都是新的
// 所以当react检测是否发生了变化的时候,必然是每次都是true,所以这里的优化其实跟没做一样
// 而且也不能直接比较props,props是一个内置的对象,肯定是变化的,或者传入的props里面的结构是一个非常复杂的结构
// {
// obj:{name:"lala"},
// arr:[1,2,3,4]
// }
// 所以这个时候就需要在父组件对传入的东西进行useMemo,useCallback包装,或者说分别对这俩个进行包装
// 如果 bar 或者 baz 是(非原始值)对象、数组、函数等,这不是一个实际的解决方案,useCallback 和 useMemo 存在的原因
return (
<>
<hr/>
</>
)
}
export default function App() {
const [count, setCount] = useState(1);
const [obj,setObj]=useState({name:"天天"})
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
}, [count]);
const useMemoCount=useMemo(()=>count,[count])
// 简单类型是没有问题的
const useMemoObj=useMemo(()=>obj,[obj])
return <div>
<h4>{count}</h4>
<Child count={count} obj={useMemoObj}></Child>
{/* 这个是没有包装的 */}
{/* <Children /> */}
<div>
<button onClick={() => setObj({name:"xixi"})}>count</button>
<input type="text" onChange={(e)=>setVal(e.target.value)}/>
</div>
</div>;
}
情况2在 useEffect() 中使用外部创建的函数, 但不希望这个函数一直变化, 导致 useEffect 被重复触发
useCallback 返回值
1 .返回一个 memoized 回调函数。在依赖参数不变的情况下,返回的回调函数是同一个引用地址
2 .注意 每当依赖参数发生改变useCallback就会自动重新返回一个新的 memoized 函数(地址发生改变)
遇到的特殊情况
//原来是这种情况不能用useCallback,只需要一个普通的函数就可以
1 .state的数据渲染出了一个列表,每一个项绑定了一个函数,就是要在这个项渲染出来执行一段动画,执行动画之后就删除他自己,一个类似于弹幕的操作
2 .然后因为使用了useCallback,导致后面新加的函数得到的state,其实是最新的数据,但是旧的函数里面的state都不是最新的数据,是当时的state状态,所以删除的时候会出现问题。而且在useCallback里面添加state依赖参数,也是不行,也就是useCallback里面操作自己的依赖函数,不知道这种情况有没有bug,最后的处理是用了ref。实际测试发现useCallback里面的参数并没有根据依赖参数修改。
3 .第二天的时候才发现是useCallback的问题,实际上直接一个裸的参数就可以了
const handleRemove=useCallback(()=>{
console.log(state,'当前数据')
},[state])
console.log(arrRef.current,'当前元素')
return (
<>
<h1>弹幕出现了</h1>
<button onClick={handleAdd}>
添加一条弹幕
</button>
<hr/>
<div className="danmu-container">
{arrRef.current.map((e:any,index:any)=>(
<Item key={e.index} data={e.value} index={e.index} handleRemove={handleRemove}></Item>
)
)}
</div>
</>
)
使用场景
1 .大量列表需要渲染,并且上面绑定了相同的事件,发生改变可以复用之前的事件绑定函数,所以zh
2 .在虚拟DOM更新过程中,如果事件句柄相同,那么就不用每次都进行removeEventListner与addEventListner。最后就是useMemo,取得上次缓存的数据,它可以说是useCallback的另一种形式
所有内置hook的执行顺序
useState: setState
//但是这个并不会提到最上面,还是要依赖习惯操作
useReducer: setState
useRef: ref
useImperativeMethods: ref
useContext: context
useCallback: 可以对setState的优化
useMemo: useCallback的变形
useLayoutEffect: 类似componentDidMount/Update, componentWillUnmount
useEffect: 类似于setState(state, cb)中的cb,总是在整个更新周期的最后才执行
1 .seCallback其实也并不是解决内部函数重新创建的问题。
仔细看看,其实不管是否使用useCallback,都无法避免重新创建内部函数
2 .useCallback,每次渲染都会重新创建内部函数作为useCallback的实参
1 .这里的实参读取其实就是[]里面的值
2 .