# React 優化項目(二): useCallback ###### tags: `React` `Optimize` `Hooks` `useCallback` `OptimizeRender` React 優化項目系列,減少大量且不必要的元件重新渲染週期 #### [React 優化項目(一):React.memo](https://hackmd.io/@yellow/optimize-render) > 如何觸發更新的 <br />紀錄 props value #### [React 優化項目(二):useCallback](https://hackmd.io/@yellow/optimize-render-usecallback) > 紀錄 func 記憶體位置 #### [React 優化項目(三):useMemo](https://hackmd.io/@yellow/optimizer-render-useMemo) > 紀錄 object value 避免重複執行相關處理 --- 延續上一篇文章範例,把 Button 包成一個元件傳進一個 event function,加上 log 以及 `React.memo` 測試。 #### 新增 Button 元件 ``` import React from 'react'; function Button(props) { console.log('Btn RUNNING!'); return <button onClick={props.onClick}>Toggle paragraph</button>; } // 加上 memo export default React.memo(Button); ``` #### RenderTry 引用 ``` ... import Button from '../../components/Button'; export default function RenderTry() { ... <Button onClick={toggleParagraph}>Toggle paragraph</Button> ... ``` 瀏覽器執行時可以發現,就算 Button 元件加上了 React.memo,但還是會進行更新。 這是因為 javascript 傳值與傳址的特性 ``` false === false // true 'abc' === 'abc' // true [1,2,3] === [1,2,3] // flase ``` :::info 當父層元件更新時 `toggleParagraphHandler` func 重新創造了一次(re-creation),產生了新的記憶體位置。所以對 `React.memo` 來說,這個 func 是不一樣的 ::: <br /> ## 使用 `React.useCallback()` > **使用`React.useCallback` 紀錄 func 記憶體位置,避免 `re-creation`** [可以先了解基本用法 >> ](https://hackmd.io/@yellow/react-hooks#useCallback-%E8%A8%98%E6%86%B6%E5%80%BC) #### 父層元件 RenderTry 修改 為了避免 `toggleParagraphHandler` func `re-creation` ,加上 `useCallback` ``` import React, { useState, useCallback } from 'react'; import DemoOutput from '../../components/demoOutput'; import Button from '../../components/Button'; export default function RenderTry() { const [showParagraph, setShowParagraph] = useState(false); console.log('APP RUNNING', showParagraph); // 加上 useCallback const toggleParagraph = useCallback(() => { setShowParagraph((pre) => !pre); }, []); return ( <div> <h1>Hi There!</h1> {/* 引用 DemoOutput 並傳入 props */} <DemoOutput show={false} /> <Button onClick={toggleParagraph}>Toggle paragraph</Button> </div> ); } ``` 很棒! `Btn RUNNING` 不見了 ![](https://i.imgur.com/M0MaEtH.gif) <br /> ## 繼續探討 useCallback 的 Dependencies - js function 特性是封閉性的 > 閉包 closures,會將內部的值鎖定再 func 內 #### 新增修改 RenderTry - 加上 `allowToggle` state - 加上新的 btn 去設定 `allowToggle` - 預期 `allowToggle` 為 true 時,就能夠切換 `toggleParagraph` ``` import React, { useState, useCallback } from 'react'; import DemoOutput from '../../components/demoOutput'; import Button from '../../components/Button'; export default function RenderTry() { const [showParagraph, setShowParagraph] = useState(false); // 新增 allowToggle const [allowToggle, setAllowToggle] = useState(false); console.log('APP RUNNING', showParagraph); // 新增判斷 allowToggle const toggleParagraph = useCallback(() => { allowToggle && setShowParagraph((pre) => !pre); }, []); // 新增控制 allowToggle const allowToggleHandle = () => { setAllowToggle(true); }; return ( <div> <h1>Hi There!</h1> <DemoOutput show={false} /> <Button onClick={toggleParagraph}>Toggle paragraph</Button> {/* 新增 event btn */} <Button onClick={allowToggleHandle}>allowToggle</Button> </div> ); } ``` 範例執行時可以發現,當我點擊 `allowToggleHandle` 更改 `allowToggle` 為 true 時,再點擊 `toggleParagraph` 是無效的 這就是 js 閉包的特性,他已經把 allowToggle false 記住了,更新時他卻不知道。 為了通知 `toggleParagraph` 需要更新可以在 `useCallback` 的 `dependencies` 加上 `allowToggle`。 這樣一來當 useCallback 就會去偵測 allowToggle 是否有改變,有的話就更新 `toggleParagraph` ``` ... const toggleParagraph = useCallback(() => { allowToggle && setShowParagraph((pre) => !pre); }, [allowToggle]); // ^^ 加入 allowToggle ^^ ... ``` 重新執行,一切正常,真棒~