【问题标题】:useCallback necessary within React.memo?在 React.memo 中需要 useCallback 吗?
【发布时间】:2020-07-09 09:09:46
【问题描述】:

考虑这个例子:

import React, { useCallback } from 'react';

type UserInputProps = {
  onChange: (value: string) => void;
};

const UserInput = React.memo(({ onChange }: UserInputProps) => {
  // Is this `useCallback` redundant?
  const handleChange = useCallback(
    (event) => {
      onChange(event.target.value);
    },
    [onChange]
  );

  return <input type="text" onChange={handleChange} />;
});

export default UserInput;

我的问题是:

  1. 当 props 仅包含 onChange 而没有其他元素时,在这种情况下 useCallback 是否不必要,因为整个组件已经基于 onChange 进行了备忘录?
  2. 如果我们添加一个额外的属性(比如value 用于&lt;input&gt; 的初始值),那么我认为useCallback 会变得有用,因为否则即使onChange 不会重新创建handleChange t 改变,但 value 改变了。对吗?

【问题讨论】:

    标签: javascript reactjs typescript


    【解决方案1】:

    当 props 只包含 onChange 而没有其他元素时,在这种情况下 useCallback 是否不必要,因为整个组件已经基于 onChange 进行了备忘录?

    它是基于所有道具记忆的,而不仅仅是onChange。是的,useCallback 在那里是不必要的。

    如果我们添加一个额外的属性(比如value 用于&lt;input&gt; 的初始值),那么我认为useCallback 会变得有用,否则即使onChange 没有,handleChange 也会被重新创建t 改变,但价值改变。对吗?

    如果您想在更新input 时更新value 而不是更新onChange,您可以将value 添加到您的props 并继续为您的handleChange 使用useCallback(如果您喜欢,因此 React 在设置值时不必换入新的事件处理程序;我的印象是这通常是矫枉过正的)。例如:

    const UserInput = React.memo(({ onChange, value }: UserInputProps) => {
      const handleChange = useCallback(
        (event) => {
          onChange(event.target.value);
        },
        [onChange]
      );
    
      return <input type="text" value={value} onChange={handleChange} />;
    });
    

    如果 onChange 属性没有改变,这将使用最新的 value 并重用以前的 handleChange

    在这种情况下,您可能不会保留React.memo,如果您预计value 会因onChange 而发生变化。也就是说,如果您希望 value 在每次调用组件时都不同,那么 React.memo 对 props 的检查是您可能不需要做的额外工作:

    const UserInput = ({ onChange, value }: UserInputProps) => {
      const handleChange = useCallback(
        (event) => {
          onChange(event.target.value);
        },
        [onChange]
      );
    
      return <input type="text" value={value} onChange={handleChange} />;
    };
    

    但如果您希望 React.memo 继续检查 props 并且在 props 均未更改时不调用您的函数,则可以保留它。

    如果你只是想为你的组件提供 initial 值然后在本地控制它,你可以继续使用React.memo(因为它仍然呈现相同的相同的输入道具),在这种情况下你不需要useCallback

    const UserInput = React.memo(({ onChange, initialValue }: UserInputProps) => {
      const [value, setValue] = useState(initialValue);
      const handleChange = (event) => {
        setValue(event.target.value);
        onChange(event.target.value);
      };
    
      return <input type="text" value={value} onChange={handleChange} />;
    });
    

    只有在 onChangeinitialValue 发生变化时才会调用它。您还可以在那里使用useCallback,以便再次仅在input 上更新onChange onChange 属性更改,以避免React 删除旧处理程序并在只有@ 时设置新处理程序987654357@变化:

    const UserInput = React.memo(({ onChange, initialValue }: UserInputProps) => {
      const [value, setValue] = useState(initialValue);
      const handleChange = useCallback(
        (event) => {
          setValue(event.target.value);
          onChange(event.target.value);
        },
        [onChange]
     );
    
      return <input type="text" value={value} onChange={handleChange} />;
    });
    

    旁注:要记住的一件事是,每次调用组件函数时都会创建一个新的handleChange 函数,即使您使用的是useCallback。它必须是,所以它可以作为参数传递给useCallback。唯一的区别是您是使用那个新函数,还是第一次创建的原始函数(useCallback 的结果)。我认为重用为给定的一组依赖项创建的第一个依赖项的原因是尽量减少传递给子组件的更改。

    【讨论】:

    • "你将value添加到你的props中,删除React.memo,并继续使用useCallback作为你的handleChange"——在这种情况下我为什么要删除React.memo?即使没有更改任何道具,这不会导致UserInput 重新渲染吗?
    • @ZizhengTai - 好问题。我已经更新了答案以澄清这一点。您可能会删除它或不取决于。如果你认为你的组件会被相同的valueonChange 调用很多,你可以保留它。但是在答案的那一部分中,我假设父级中的onChange 几乎每次都会更新value,这只会使React.memo 对道具的检查额外工作。不过,这完全取决于,所以,好问题。 :-)
    【解决方案2】:

    当 props 仅包含 onChange 而没有其他元素时,在这种情况下 useCallback 是不必要的,因为整个组件已经基于 onChange 进行了备忘录?

    不,可能有必要memouseCallback 的用途不同。

    如果没有useCallback,您可能会在每次渲染时进行“大量计算”:

    “繁重计算”是指一般情况,而不是仅传递事件值的特定示例。

    const UserInput = React.memo(({ onChange = () => {} }) => {
      // Uncomment for memoization
      // Note that you can implement useCallback with useMemo
    
      // const handleChange = useMemo(() => {
      //   console.log("heavy computation memoized");
      //   return event => {
      //     onChange(event.target.value);
      //   };
      // }, [onChange]);
    
      const handleChange = event => {
        // Here we can have some heavy computation
        // Not related to this specific usecase
        console.log("heavy computation on every render");
        onChange(event.target.value);
      };
    
      return <input type="text" onChange={handleChange} />;
    });
    

    如果我们添加一个额外的属性(比如为 的初始值添加一个值),那么我认为useCallback 会变得有用,因为否则即使 onChange 没有改变但值改变了,handleChange 也会被重新创建。对吗?

    如果你要使用受控组件(由于使用了inputvalue 属性),initialValue 将被初始化一次,所以试图记住它是没有用的(出于什么目的?):

    const UserInputWithValue = ({ onChange, initialValue }) => {
      // initilized once, no need to memoize
      const [value,setValue] = useState(initialValue);
      const handleChange = useCallback(
        (event) => {
          setValue(event.target.value)
          onChange(event.target.value);
        },
        [onChange]
      );
    
      // Controlled
      return <input type="text" value={value} onChange={handleChange} />;
    };
    

    【讨论】:

    • 您在答案的第一部分中谈论的是什么“繁重的计算”?
    • 它与您只传递事件值的特定用例无关,我试图解释“useCallback inside Memo”的一般情况
    • 认为我知道你想说什么,只是上面的“这里我们可以进行一些繁重的计算”评论在更改 handler ,不是创建处理程序的东西;它在处理程序运行时运行,而不是在构建时运行。也许您正在考虑useMemo,虽然您每次都在创建一个新函数,但您可以避免每次都运行该新函数。 (不是useCallback 的一个因素,因为useCallback(fn, [deps]) 只是useMemo(() =&gt; fn, [deps]);,“为我构建一个处理程序”函数没有计算可言。)(无论如何,仍然是一个很好的答案。)
    猜你喜欢
    • 1970-01-01
    • 2020-02-25
    • 2020-04-27
    • 2023-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-23
    相关资源
    最近更新 更多