【问题标题】:When to use useCallback, useMemo and useEffect?何时使用 useCallback、useMemo 和 useEffect?
【发布时间】:2019-11-16 11:59:51
【问题描述】:

useCallbackuseMemouseEffect 的主要区别是什么?

举例说明何时使用它们。

【问题讨论】:

  • 你读过hooks api doc吗?
  • @Vencovsky 我的朋友,文档无法回答所有问题。假设如果props改变改变States,useMemo和useEffect最好使用?

标签: reactjs react-hooks


【解决方案1】:

简短的解释。

使用效果

它是类组件生命周期方法componentDidMountcomponentWillUnmountcomponentDidUpdate 等的替代方案。您还可以使用它来在依赖项更改时创建副作用,即“如果某些变量更改,请执行此操作” .

使用回调

在每次渲染时,功能组件内的所有内容都会再次运行。如果子组件依赖于父组件的函数,则每次父组件重新渲染时子组件都会重新渲染,即使该函数“没有改变”(引用发生了变化,但函数的作用不会' t).
它用于通过避免来自子级的不必要渲染来进行优化,使函数仅在依赖项更改时更改引用。 当函数是副作用的依赖项时,您应该使用它,例如useEffect.

使用备忘录

它将在每次渲染时运行,但具有缓存值。当某些依赖项发生变化时,它只会使用新值。当您有昂贵的计算时,它用于优化。 Here is also a good answer that explains it.

【讨论】:

    【解决方案2】:

    useEffect() 将允许您根据发送给组件的依赖项在组件上创建副作用。

    function Example() {
      const [count, setCount] = React.useState(0);
    
      React.useEffect(() => {
        document.title = `You clicked ${count} times`;
      }, [count]);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    
    ReactDOM.render(<Example />, document.getElementById('root'))
    <script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script>
    
    <div id="root"></div>

    上面的例子是taken from the documentation of React。可以看到,每次点击按钮都会触发 count 字段的更新(使用 setCount()),然后,依赖于 count 变量的效果会触发页面标题的更新。


    useCallback() 将返回一个 memoized 回调。通常情况下,如果你有一个子组件接收到一个函数 prop,那么在每次重新渲染父组件时,都会重新执行这个函数;通过使用useCallback(),您可以确保仅在其依赖数组上的任何值发生更改时才重新执行此函数。

    function ExampleChild({ callbackFunction }) {
      const [value, setValue] = React.useState(0);
    
      React.useEffect(() => {
        setValue(value + 1)
      }, [callbackFunction]);
    
      return (<p>Child: {value}</p>);
    }
    
    function ExampleParent() {
      const [count, setCount] = React.useState(0);
      const [another, setAnother] = React.useState(0);
      
      const countCallback = React.useCallback(() => {
        return count;
      }, [count]);
      
      return (
        <div>
          <ExampleChild callbackFunction={countCallback} />
          <button onClick={() => setCount(count + 1)}>
            Change callback
          </button>
          
          <button onClick={() => setAnother(another + 1)}>
            Do not change callback
          </button>
        </div>
      )
    }
    
    ReactDOM.render(<ExampleParent />, document.getElementById('root'));
    <script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script>
    
    <div id="root"></div>

    useMemo() 将返回一个 memoized 值,它是传递参数的结果。这意味着useMemo() 将对某个参数进行一次计算,然后从缓存中返回相同参数的相同结果。

    当您需要处理大量数据时,这非常有用。

    function ExampleChild({ value }) {
       const [childValue, setChildValue] = React.useState(0);
    
       React.useEffect(() => {
         setChildValue(childValue + 1);
       }, [value])
    
       return <p>Child value: {childValue}</p>;
    }
    
    function ExampleParent() {
      const [value, setValue] = React.useState(0);
      const heavyProcessing = () => {
        // Do some heavy processing with the parameter
        console.log(`Cached memo: ${value}`);
        return value;
      };
    
      const memoizedResult = React.useMemo(heavyProcessing, [value]);
      
      return (
        <div>
          <ExampleChild value={memoizedResult} />
          <button onClick={() => setValue(value + 1)}>
            Change memo
          </button>
        </div>
      )
    }
    
    ReactDOM.render(<ExampleParent />, document.getElementById('root'));
    <script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script>
    
    <div id="root"></div>

    【讨论】:

      【解决方案3】:

      最简单的解释:

      使用效果:

      当你有一些逻辑被执行作为对状态变化的反应或变化即将发生之前。

      useEffect(() => {
        // execute when state changed
        () => {
          // execute before state is changed
        }
      }, [state]);
      

      或者在没有依赖的情况下:

      useEffect(() => {
        // execute when component has mounted
        () => {
          // execute when component will unmount
        }
      }, []);
      

      使用回调:

      只要你有一个依赖于特定状态的函数。此挂钩用于性能优化,并防止重新分配组件内的函数,除非依赖状态发生更改。

      const myFunction = useCallback(() => {
        // execute your logic for myFunction
      }, [state]);
      

      如果没有 useCallback,myFunction 将在每次渲染时重新分配。因此,它会像使用 useCallback 一样使用更多的计算时间。

      使用备忘录

      只要您有一个取决于特定状态的值。与 useCallback 相同,useMemo 用于减少重新分配以优化性能。

      const myValue = useMemo(() => {
        // return calculated value
      }, [state]); 
      

      与 useCallback 相同,myValue 仅在状态更改时分配,因此会减少计算时间。否则 myValue 将在每次渲染时重新分配。

      !模仿 componentWillMount 生命周期的技巧

      useMemo(() => {
        // execute componentWillMount logic
      ]}, []);
      

      因为 useEffect 在第一次渲染之后调用,然后在每次依赖项更改时调用。它永远不会在第一次渲染之前运行。 useMemo 与您的 JS 内联执行,因此将在它到达您的组件返回语句之前执行。

      !NOTE:带有 useCallback 的函数和带有 useMemo 的值可以在 useCallback、useMemo 和 useEffect 中用作依赖项。强烈建议使用这些钩子,以便在组件中拥有结构良好且可读的状态流。这些钩子不会触发渲染。只有 useState 和 useReducer 可以!

      如果你想保持不触发重新渲染或任何上述钩子的状态,你可以使用 useRef。 useRef 将在渲染过程中保持值一致,而不会触发任何与状态相关的值或效果。

      【讨论】:

        【解决方案4】:

        知道何时使用这些功能很好,但我想知道它们之间的实际区别是什么!这是我发现的:

        • useMemo 立即运行代码,因此返回值可用于后面的代码。这意味着它在第一次渲染之前运行,因此您用于访问 HTML 组件的任何 useRef 在初始运行时都将不可用。 (但是您可以将ref.current 添加到useMemo 依赖项中,以便在useRef 值可用时,在第一次渲染后再次运行useMemo 代码)。由于返回值可用于直接跟随它的代码,这就是为什么它建议用于不需要在每次渲染上重新运行的复杂计算,因为返回值立即可用可以让您不必弄乱状态现在存储该值并稍后访问它 - 只需获取 useMemo() 的返回值并立即使用它。
        • useEffect 不会立即运行,而是在第一次渲染后运行。这意味着任何引用 HTML 元素的 useRef 值将在第一次运行时有效。由于它在您的函数中的所有代码完成并呈现后运行,因此没有返回值,因为没有进一步的代码运行可以使用它。 useEffect 代码可以做任何可见的事情的唯一方法是更改​​状态以导致重新渲染,或直接修改 DOM。
        • useCallbackuseMemo 相同,只是它记住函数本身而不是返回值。这意味着useCallback 函数不会立即运行,但可以稍后运行(或根本不运行),而useMemo 会立即运行其函数并保存其返回值以供以后使用。与useMemo 不同,这不适合复杂的计算,因为每次使用代码都会再次运行。如果您曾经将回调函数作为道具传递给渲染函数中的另一个组件,请确保您传递了useCallback 的返回值。如果您将回调函数设为const onClick = () =&gt; { ... } 或在JSX 中设为onClick={() =&gt; { ... }},那么每次渲染都会获得该函数的新实例,因此子组件将始终重新渲染,因为它认为您正在将回调更改为每个渲染都有不同的功能。但是useCallback 每次都返回函数的相同 实例,因此如果没有其他任何更改,子函数可能会完全跳过渲染,从而使您的应用更具响应性。

        例如,如果我们将相同的函数传递给useMemouseCallback

        let input = 123;
        const output = useMemo(() => {
          return input + 1;
        }, [
          input,
        ]);
        
        // The above function has now run and its return value is available.
        
        console.log( output ); // 124
        
        input = 125; // no effect as the function has already run
        console.log( output ); // 124
        
        let input = 123;
        const output = useCallback(() => {
          return input + 1;
        }, [
          input,
        ]);
        
        // The above function has not run yet but we can run it now.
        
        console.log( output() ); // 124
        
        input = 125; // changes the result as the function is running again
        console.log( output() ); // 126
        

        这里,useCallback 已经记住了该函数,并将在以后的渲染中继续返回原始函数,直到依赖关系发生变化,而 useMemo 实际上立即运行该函数并只记住它的返回值。

        useCallback()useMemo() 都提供了可以立即使用的返回值,而useEffect() 没有,因为它的代码要等到渲染完成后很久才会运行。

        【讨论】:

        • 由于useMemo中的依赖关系,即[输入],useMemo应该在依赖关系改变时再次运行,所以结果对于各种输入值都是正确的。
        【解决方案5】:

        useEffect

        在组件挂载、卸载及其任何依赖项发生更改时调用。

        当组件为mountedsubscribeunsubscribe 时,可用于获取数据到组件mountsunmounts 时的事件流(想想rxjs)。

        const [userId, updateUser] = useState(1);
        
        useEffect(()=>{
          //subscription
           const sub = getUser(userId).subscribe(user => user);
        
        // cleanup
          return () => {
           sub.unsubscribe();
         }
        
        },[userId]) // <-- Will get called again when userId changes
        

        也可用于不需要清理的一次性方法调用

        useEffect(()=>{
        
          oneTimeData();
        
        },[]); // pass empty array to prevent being called multiple times
        
        

        useCallback

        1. 有不想在每个组件渲染上重新创建的函数?

        2. 想要一个不会在组件挂载或卸载时调用的函数?

        使用useCallback

        const [val, updateValue] = useState(0);
        
        const Compo = () => {
        
        /* inc and dec will be re-created on every component render. 
           Not desirable a function does very intensive work.
        */
        
        const inc = () => updateValue(x => x + 1);
        const dec = () => updateValue(x => x - 1);
        
        return render() {
           <Comp1 onClick={inc} />
           <Comp2 onClick={dec} />
         }
        }
        
        

        useCallback 救援

        const [val, updateValue] = useState(0);
        
        const Compo = () => {
        
        const callbackInc = useCallback(() => {
          setCount(currentVal => currentVal + 1);
        }, []);
        
        const callbackDec = useCallback(() => {
          setCount(currentVal => currentVal - 1);
        }, []);
        
        return render() {
           <Comp1 onClick={callbackInc} />
           <Comp2 onClick={callbackDec} />
         }
        }
        

        如果传递给setCount 的参数不是函数,那么您希望useCallback '注意' 的变量必须在依赖项数组中指定,否则不会产生任何更改效果。

        const callbackInc = useCallback(() => {
          setCount(val + 1); // val is an 'outside' variable therefore must be specified as a dependency
        }, [val]);
        

        useMemo

        进行繁重的处理并希望memoize缓存)结果?使用useMemo

        /*
          heavyProcessFunc will only be called again when either val or val2 changes
        */
        const result = useMemo(heavyProcessFunc(val, val2),[val,val2])
        

        【讨论】:

          【解决方案6】:

          所有这些钩子都有一个相同的目标:避免多余的组件重建(以及钩子内的东西的重新执行)。

          useEffect 不返回任何内容 (void),因此适用于此类情况。

          useCallback 返回一个函数,稍后将在组件中使用。与普通函数声明不同,它不会触发组件重建,除非其依赖关系发生变化。

          useMemo 只是useCallback 的另一种风格。

          Here 是迄今为止我见过的最好的解释。

          【讨论】:

            猜你喜欢
            • 2022-12-18
            • 2021-09-29
            • 2022-12-15
            • 2021-03-09
            • 2022-12-23
            • 2019-09-20
            • 2020-01-22
            • 1970-01-01
            相关资源
            最近更新 更多