# 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` 不見了

<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 ^^
...
```
重新執行,一切正常,真棒~