【问题标题】:What is useCallback in React and when to use it?什么时候在 React 中使用 useCallback?
【发布时间】:2022-08-24 16:21:18
【问题描述】:

我已经阅读了几篇关于useCallbackuseMemo 的文章,内容是关于何时使用和何时不使用,但我主要看到的是contrived 代码。我正在查看我公司的代码,我注意到有人这样做了:

const takePhoto = useCallback(() => {
    launchCamera({ mediaType: \"photo\", cameraType: \"front\" }, onPickImage);
  }, []);

  const pickPhotoFromLibrary = async () => {
    launchImageLibrary({ mediaType: \"photo\" }, onPickImage);
  }

  const onUploadPress = useCallback(() => {
    Alert.alert(
      \"Upload Photo\",
      \"From where would you like to take your photo?\",
      [
        { text: \"Camera\", onPress: () => takePhoto() },
        { text: \"Library\", onPress: () => pickPhotoFromLibrary() },
      ]
    );
  }, [pickPhotoFromLibrary, takePhoto]);

这就是 onUploadPress 的调用方式:

    <TouchableOpacity
                    style={styles.retakeButton}
                    onPress={onUploadPress}
                  >

你认为这是正确的称呼方式吗?根据我对这些文章的理解,这看起来不正确。有人能告诉我什么时候使用useCallback,还可以用更人性化的方式解释 useCallback 吗?

我阅读的文章:https://kentcdodds.com/blog/usememo-and-usecallback

    标签: javascript reactjs typescript react-native


    【解决方案1】:

    useCallback 返回一个普通的 JavaScript 函数,关于如何使用它。它与它作为第一个参数获得的关于它的作用的参数相同。不同之处在于返回的函数是memoized,这意味着当组件重新渲染时,它不会在新的内存引用上重新创建,这是组件内部正常函数的行为。

    如果依赖数组中的变量之一发生更改,它将在新的内存引用上重新创建。现在,你为什么要打扰这个?好吧,如果你在 useEffect 的依赖数组中有这个函数,那么它是值得的。如果您将其传递给使用memo 记忆的组件,这也是值得的。

    useEffect 的回调在第一次渲染时被调用,并且每次依赖数组中的一个变量发生变化时。而且由于通常每次组件重新渲染时都会创建该函数的新版本,因此回调可能会被无限调用。所以useCallback 用于记忆该功能。

    带有memo 的memoized 组件仅在其stateprops 更改时才会重新渲染,而不会导致其父级重新渲染。并且因为通常在父组件重新渲染时会创建一个作为 props 的传递函数的新版本,所以子组件会获得一个新的引用,因此它会重新渲染。所以useCallback 用于记忆该功能。

    为了说明我创建了下面的 React 应用程序和这个CodeSandbox。单击该按钮以触发父级的重新渲染并观看控制台。希望它可以解决问题。

    const MemoizedChildWithMemoizedFunctionInProps = React.memo(
      ({ memoizedDummyFunction }) => {
        console.log("MemoizedChildWithMemoizedFunctionInProps renders");
        return <div></div>;
      }
    );
    
    const MemoizedChildWithNonMemoizedFunctionInProps = React.memo(
      ({ nonMemoizedDummyFunction }) => {
        console.log("MemoizedChildWithNonMemoizedFunctionInProps renders");
        return <div></div>;
      }
    );
    
    const NonMemoizedChild = () => {
      console.log("Non memoized child renders");
      return <div></div>;
    };
    
    const App = () => {
      const [state, setState] = React.useState(true);
    
      const nonMemoizedDummyFunction = () => {};
    
      const memoizedDummyFunction = React.useCallback(() => {}, []);
    
      console.log("Parent renders");
    
      React.useEffect(() => {
        console.log("useEffect callback with nonMemoizedDummyFunction runs");
      }, [nonMemoizedDummyFunction]);
    
      React.useEffect(() => {
        console.log("useEffect callback with memoizedDummyFunction runs");
      }, [memoizedDummyFunction]);
    
      return (
        <div>
          <button onClick={() => setState((prev) => !prev)}>Toggle state</button>
          <MemoizedChildWithMemoizedFunctionInProps
            memoizedDummyFunction={memoizedDummyFunction}
          />
          <MemoizedChildWithNonMemoizedFunctionInProps
            nonMemoizedDummyFunction={nonMemoizedDummyFunction}
          />
          <NonMemoizedChild />
        </div>
      );
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById("root")
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

    要知道记忆不是免费的,做错了更糟糕。这是我所见过的最完整和最短的资源,用于深入了解 React 的渲染过程,了解何时值得记忆以及如何正确地做到这一点:React Render Tutorial

    在您的情况下,将useCallback 用于onUploadPress 是一种浪费,导致非memoized 函数pickPhotoFromLibrary 在依赖数组中。如果TouchableOpacity 没有被memo 记忆,那也是一种浪费,我不确定。

    【讨论】:

      【解决方案2】:

      简单来说,useCallback 用于将函数引用保存在组件渲染之外的某个位置,以便我们可以再次使用相同的引用。每当依赖项数组中的变量之一发生更改时,该引用就会更改。 如您所知React 尝试通过观察一些变量的值变化来最小化重新渲染过程,然后它决定重新渲染不依赖于这些变量的旧值和新值。 因此,useCallback 的基本用法是平等地保存旧值和新值。

      我将通过在我们必须使用useCalback 的情况下给出一些示例来尝试更多地演示它。

      • 示例1:当函数是useEffect的依赖数组之一时。
      function Component(){
        const [state, setState] = useState()
        
        // Should use `useCallback`
        function handleChange(input){
          setState(...)
        }
      
        useEffect(()=>{
          handleChange(...)
        },[handleChange])
      
        return ...
      }
      
      • 示例 2:将函数传递给其中一个子组件时。尤其是在他们的 useEffect 钩子上调用它时,它会导致无限循环。
      function Parent(){
        const [state, setState] = useState()
        
        function handleChange(input){
          setState(...)
        }
      
        return <Child onChange={handleChange} />
      }
      
      function Child({onChange}){
        const [state, setState] = useState()
        
        useEffect(()=>{
          onChange(...)
        },[onChange])
      
        return "Child"
      }
      
      • 示例 3:当您使用 React Context 保存状态并仅返回状态设置器函数时,您需要该 context 的使用者不要在每次状态更新时重新呈现,因为它可能会损害性能。
      const Context = React.createContext();
      
      function ContextProvider({children}){
        const [state, setState] = useState([]);
        
        // Should use `useCallback`
        const addToState = (input) => {
          setState(prev => [...prev, input]);
        }
      
        // Should use `useCallback`
        const removeFromState = (input) => {
          setState(prev => prev.filter(elem => elem.id !== input.id));
        }
      
        // Should use `useCallback` with empty []
        const getState = () => {
          return state;
        }
      
        const contextValue= React.useMemo(
          () => ({ addToState , removeFromState , getState}),
          [addToState , removeFromState , getState]
        );
      
        // if we used `useCallback`, our contextValue will never change and all the subscribers will not re-render
        <Context.Provider value={contextValue}>
          {children}
        </Context.Provider>
      }
      

      示例 4:如果订阅了观察者、定时器、文档事件,并且需要在组件卸载时或其他任何原因取消订阅。所以我们需要访问相同的引用来取消订阅。

      function Component(){
      
        // should use `useCallback`
        const handler = () => {...}
        
        useEffect(() => {
          element.addEventListener(eventType, handler)
          return () => element.removeEventListener(eventType, handler)
        }, [eventType, element])
      
      
        return ...
      }
      

      就是这样,您也可以在多种情况下使用它,但我希望这些示例能够展示useCallback 背后的主要思想。并且永远记住,如果重新渲染的成本可以忽略不计,你就不需要使用它。

      【讨论】:

        猜你喜欢
        • 2022-12-18
        • 1970-01-01
        • 2017-06-04
        • 1970-01-01
        • 1970-01-01
        • 2021-04-15
        • 1970-01-01
        • 2012-07-29
        相关资源
        最近更新 更多