useMemo与useCallback小结

142 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情

无优化:

 function Child(props){
      console.log("child render");
            return(
            <div>
                <p>child {props.data.count}</p> 
                <button onClick={props.handleCount}>child click</button>
            </div>
            )
 }

上面Child组件中分别引用了父组件中data对象中的count变量,和改变count的handleCount函数方法

function App(){
                console.log("render father");
                const [count,setCount] = useState(0)
                const [value,setValue] = useState('')
                let data = {count:count}
                let handleCount = ()=>{
                    setCount(count+1)
                }
                let handleValue=(e)=>{
                    setValue(e.target.value)
                }
                return(
                <div>
                      <p>father {count}</p>
                      <p>father {value}</p>
                      <input type="text" value={value} onChange={handleValue}/>
                      <hr />
                      <MemoChild data={data} handleCount={handleCount}/>
                </div>
            )
        }

运行该代码可知:data中的count引用父组件中count变量,当点击子组件中按钮时,setCount会+1,结果导致父组件重新render,子组件也重新render。输入框输入时,也导致父子组件都重新声明。

image.png

缓存数据:useMemo

情况1. data中count数据如果是常数0,当点击按钮改变父组件中count后,即使子组件中数据仍然是0,也会使父组件中count+1的同时,使let重新声明data,产生新地址,重新渲染child子组件。

 let data = {count:0}

(按钮是修改父组件中count的) image.png

优化:

将钩子函数useMemo包裹住:

  let data = useMemo(()=>{
             return {count:0} 
         },[]) //注意,为[],只声明一次,每次获得的都是0

子组件始终为常数0,可以用[]始终data被缓存一次,count为0就成为始终没变的,那父组件改变无关子组件时,只挂载一次值,不会再刷新。

情况2. data中count数据如果是count变量的话。父组件改变的情况:

let data = {count:count}

当count改变时,子组件会重新声明,有两种渲染情况。

let data = useMemo(()=>{
              return  {count:count}
       },[]) //只执行一次,页面显示的还只是最开始的缓存值
let data = useMemo(()=>{
                    return  {count:count}
                },[count]) //依赖于count,当count改变就会每次动态的返回具体值

函数缓存:useCallback

子组件中引用父组件中声明的函数,当没有调用此方法修改数据时,该方法也会被重新渲染产生新地址

 function Child(props){
          console.log("child render");
            return(
            <div>
                <p>child {props.data.count}</p> 
                <button onClick={props.handleCount}>child click</button>
            </div>
      )
  }
 function App(){
                console.log(" father");
                const [count,setCount] = useState(0)
                const [value,setValue] = useState('')
                //data对象已经完成缓存
                let data = useMemo(()=>{return  {count:count}},[count])
                let handleCount = ()=>{setCount(count+1)}  
                let handleValue=(e)=>{setValue(e.target.value)}
                return(
                <div>
                      <p>father {count}</p>
                      <p>father {value}</p>
                      <input type="text" value={value} onChange={handleValue}/>
                      <hr />
                      <MemoChild data={data} handleCount={handleCount}/>
                </div>
            )
        }

优化方法:useCallback 将handleCount传进子组件前进行函数缓存。

情况1:[ ]约束

let handleCount = useCallback(()=>{
        console.log(count)
         setCount(count+1) 
     },[])

[ ]使得函数只执行一次,只获取一次值,所以每次点击完按钮打印count时,获得的都是0,因为它只执行了一次,函数内setCount只进行一次加一的操作。但是虽然成功完成加一,但也不会渲染到子组件页面上。

image.png

情况2:[count]

let handleCount = useCallback(()=>{
        console.log(count)
         setCount(count+1) 
     },[count])

每次依赖于count数据,每次改变都会渲染,与count无关的数据改变就不会使子组件重新render。

比如改变count时,子组件会重新渲染,但输入文字时,子组件不会重新声明。

image.png

image.png

熟练使用:

熟练使用useCallback缓存函数,使useMemo缓存对象,使memo缓存函数组件,使pureComponent缓存类组件,使用shouldComponentUpedate自定义是否更新函数。