useCallback

简介

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 .
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容