useMemo useCallback useRef

useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印);但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算:

export default function WithMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);
 
    return (
      <div>
        <h4>{count}-{expensive}</h4>
          {val}
        <div>
          <button onClick={() => setCount(count + 
              1)}> +c1 </button>
            <input value={val} onChange={event => 
                setValue(event.target.value)}/>
        </div>
    </div>);
}

useCallback

import React, { useState, useCallback } from 'react';

const set = new Set();
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');

    const callback = useCallback(() => {
        console.log(count);
    }, [count]);

    set.add(callback);
 
    return (
      <div>
        <h4>{count}</h4>
        <h4>{set.size}</h4>
        <div>
           <button onClick={() => setCount(count + 1)}>+ 
           </button>
           <input value={val} 
            onChange={event => setVal(event.target.value)}/>
        </div>
      </div>);
}

我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

知道useCallback有什么样的特点,那有什么作用呢?

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);

    return (
      <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
           <button onClick={() => setCount(count + 1)}>+ 
           </button>
           <input value={val} onChange={event => 
                setVal(event.target.value)}/>
        </div>
     </div>);
}
 
function Child({ callback }) {
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
        setCount(callback());
    }, [callback]);
    return (<div>  {count} </div>)
}

不仅是上面的例子,所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。

useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。

useRef

useRef hooks函数,除了传统的用法之外,它还可以“跨渲染周期”保存数据

它保存数据的用法

在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state。没错,一个组件的state在多次渲染之后依旧不变。但是的,state问题在于一旦修改了它就会造成组件的重新渲染。

那么这个时候就可以使useRef用来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染。

import React, { useState, useEffect, useMemo, useRef } from 'react';

export default function App(props){
  const [count, setCount] = useState(0);

  const doubleCount = useMemo(() => {
    return 2 * count;
  }, [count]);

  const timerID = useRef();
  
  useEffect(() => {
    timerID.current = setInterval(()=>{
        setCount(count => count + 1);
    }, 1000); 
  }, []);
  
  useEffect(()=>{
      if(count > 10){
          clearInterval(timerID.current);
      }
  });
  
  return (
    <>
      <button ref={couterRef} onClick={() => {setCount(count 
        + 1)}}>
         Count: {count}, double: {doubleCount}
     </button>
    </>
  );
}
//  useRef 穿透闭包
function Form() {
  const [text, updateText] = useState('');
  const textRef = useRef();

 // useLayoutEffect: DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
  useLayoutEffect(() => {
    textRef.current = text; // 将 text 写入到 ref
  });

  const handleSubmit = useCallback(() => {
    const currentText = textRef.current; // 从 ref 中读取 text
    alert(currentText);
  }, [textRef]); // handleSubmit 只会依赖 textRef 的变化。不会在 text 改变时更新

  return (
    <>
      <input value={text} onChange={e => 
        updateText(e.target.value)} 
      />
      <ExpensiveTree onSubmit={handleSubmit} />
    </>
  );
}

上面 input 值改变了, handleSubmit 也获取到的值也会变, 但 handleSubmit 不会变,不会触发ExpensiveTree 更新,

Leave a Reply

Your email address will not be published. Required fields are marked *