何时使用useCallback、useMemo和useEffect?

136

useCallbackuseMemouseEffect有什么主要区别?

请给出使用它们的示例。


2
你有阅读过Hooks API文档吗? - Vencovsky
24
我的朋友,文档无法回答所有的问题。如果 props 改变来改变状态,使用 useMemo 和 useEffect 是否更好? - S22
6个回答

96

简短解释。

useEffect

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

useCallback

在每次渲染时,所有内容都将重新运行函数式组件中的所有内容。如果子组件依赖于父组件的函数,则每当父组件重新渲染时,子组件都会重新渲染,即使该函数“没有改变”(引用变化,但函数所做的事情不变)。 它用于通过仅在依赖项更改时使函数更改引用来避免子组件进行不必要的渲染优化。 当函数是副作用例如useEffect的依赖项时,应使用它。

useMemo

它将在每次渲染时运行,但使用缓存的值。只有在特定依赖项更改时才会使用新值。当您拥有昂贵计算时,它用于优化。这里还有一个很好的解释


75

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>

上面的例子是来自React文档。你可以看到每次点击按钮,它都会触发计数字段的更新(使用setCount()),然后依赖于计数变量的效果将触发页面标题的更新。


useCallback()将返回一个记忆化的回调函数。通常情况下,如果您有一个子组件接收一个函数属性,在父组件重新渲染时,这个函数将被重新执行;通过使用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()将返回一个记忆化值,该值是传递参数的结果。这意味着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>


45

最简单的解释:

useEffect:

每当您有一些逻辑需要在状态更改时执行或在更改即将发生之前执行。

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

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

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

useCallback:

当您有一个依赖于特定状态的函数时,使用此钩子可以进行性能优化,并且防止在相关状态未更改时重新分配组件内的函数。

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

如果没有使用 useCallback, myFunction 会在每次渲染时被重新赋值。因此,它会比使用 useCallback 更消耗计算时间。

useMemo

当您拥有一个依赖于特定状态的值时,与 useCallback 相同,useMemo 旨在减少重新分配以进行性能优化。

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

与 useCallback 相同,只有当状态改变时,myValue 才会被赋值,因此可以减少计算时间。否则,myValue 将在每次渲染时重新分配。

!模仿 componentWillMount 生命周期的技巧

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

由于useEffect在第一次render之后调用,然后在每次依赖项改变时都会被调用。它永远不会在第一次render之前运行。 useMemo与您的JS内联执行,因此将在到达组件return语句之前执行。

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

如果您想保留不触发重渲染或任何上述解释钩子中的状态,可以使用useRef。useRef可以在不触发任何依赖项或效果的情况下保持值的一致性。


1
似乎您忘记在useEffect的解释中添加return。 - alanextar
1
这不对吗?从useEffect返回的函数在状态改变之前并没有运行,它是在组件从UI中移除之前运行的。 - jacob
@jacob 是的,它在那个时间运行,清理函数。 - Antonio Mora Bautista

16

了解何时使用这些函数是好的,但我想知道它们之间的实际区别! 这是我的研究结果:

  • useMemo立即运行代码,因此返回值可在其后的代码中使用。这意味着它在第一次渲染之前运行,因此您正在使用的任何useRef以访问HTML组件将在初始运行时不可用。(但您可以将ref.current添加到useMemo依赖项中,以使useMemo代码再次在第一次渲染后运行,此时useRef值已变为可用)。由于返回值可直接用于紧随其后的代码,因此建议在不需要在每个渲染上重新运行的复杂计算中使用它,因为立即获得返回值可以避免必须操作状态来存储值并稍后访问它。
  • useEffect不会立即运行,但会在第一次渲染后运行。这意味着指向HTML元素的任何useRef值将在第一次运行时有效。由于它在函数中所有代码完成并呈现之后运行,因此没有返回值,因为没有进一步运行可以使用它。 useEffect代码唯一可见的方法是通过更改状态以引起重新渲染,或直接修改DOM。
  • useCallbackuseMemo相同,只是记住函数本身而不是其返回值。这意味着useCallback函数不会立即运行,但可以稍后运行(或根本不运行),而useMemo会立即运行其函数并仅保存其返回值供以后使用。与useMemo不同,这对于复杂计算来说并不好,因为每次使用时代码都将再次运行。如果您将回调函数作为prop传递给渲染函数中的另一个组件,请确保传递useCallback的返回值。如果您使回调函数如const onClick = () => { ... } 或在JSX中的onClick={() => { ... }},那么每次渲染都会得到该函数的新实例,因此子组件将始终重新渲染,因为它认为您正在每个单独的渲染中更改回调为不同的函数。但是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()不能,因为它的代码要等到渲染完成后才会运行。


1
由于useMemo中存在依赖关系即[input],所以当依赖关系发生变化时,useMemo应该重新运行,以便针对不同的输入值得出正确的结果。 - dsi

6
所有这些钩子的目标都是避免组件重建(以及在钩子内部重新执行内容)冗余。 useEffect 无返回值(void),因此非常适合这种情况。 useCallback 返回一个将在组件中稍后使用的函数。与普通函数声明不同,它只有在其依赖项更改时才会触发组件重构。 useMemo 只是另一种 useCallback 的风味。 这里 是我迄今为止见过的最好的解释。

6

useEffect

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

可以用于在组件挂载时获取数据,以及在组件挂载卸载时订阅和取消订阅事件流(考虑 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])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接