# [Udemy - React] 課程流程筆記(sec. 15)
###### tags: `Udemy 課程筆記` `React` `前端筆記`
## 189. custom hook 就是從 component 把共用函式取出
- ==custom hook 必須遵從 hooks 的命名規則:useXXX==
- custom hook -> function(不需要有 view,是用來讓各個 component 用的共同函式)
> 1. 共用的 stateful function
> 2. custom hook 的 state 更新就會觸發引用 custom hook 之 parent component 的 re-render(re-execute)
- component -> 需要回傳 view(也就是 JSX)

- 將共用邏輯抽出(custom hook)之後 component 內就可以刪除
- custom hook 可以回傳各種東西(也是 pure function 的概念,給同樣的 input 就會得到同樣的 output)
## 190. custom hook 就是 function
- function 是透過 parameters 增加 function 的靈活度
- 練習:
1. 用開關(`true` / `false`)
2. 傳整個 callback
> 傳 callback 就要記得使用的東西是什麼,因為 `useCallback` 就只是回傳第一個參數(callback)
## 193. custom hook 可以達到類似 factory function 的效果
- 一個函式(工廠)建立不同的零件(object -> properties and methods)
- factory function 建立 methods 也可以用 `useCallback` 包,確保 function 不會因為 re-render 而重建
> `useCallback` 有 external state 才要放 dependency array,因為那些東西是這個函式不相干的,需要用 dependency array 偵查是否有變動
> methods 可以開 paramter 省去要回頭確保叫用 custom hook 的 argument 是否有變動(`useMemo`, `useCallback`)
- 同樣區域的東西可以用 object 組合,但是不相干的東西就是放在其他 parameter(比如說 `testFn({requestConfig: url: '', method: ''}, callback)`(因為 `requestConfig` 只處理打 API 的設定,額外的 function 就不要放在裡面,而是另開的一個 parameter)
- `function.bind()` 用來 preconfigure 函式:
1. 會回傳叫用 `function.bind()` 的函式(但不會叫用 function 本身)
2. `function.bind(thisArg[, arg1[, arg2[, ...]]])` 第一個參數是綁定 `this`(如果沒有要綁定 `this` 就給 `null`),第二個參數之後是讓其參數自動加到原來的函式
```javascript=
const printFn = (name) => {
console.log(name);
}
/* 透過 function.bind() 把 'lun' 綁到 printFn 的 argument 上 */
const preConfigedPrintFn = printFn.bind(null, 'lun');
preConfigedPrintFn(); // 'lun'
/* 以上等價於 */
const wrapperFn = (name) => {
printFn(name);
}
wrapperFn('lun'); // 'lun'
```
```javascript=
const printFn = (name, secondArg) => {
console.log(this.name);
console.log(secondArg);
}
```
### 範例一:上層 `useCallback` 包函式,custom hook `useCallback` 回傳函式確保不會重複 re-render

```javascript=
// src/App.js
function App() {
const [tasks, setTasks] = useState([]);
const fetchConfig = {
method: 'GET',
requestUrl: 'https://udemy-react-15-190-default-rtdb.firebaseio.com/tasks.json',
};
// useCallback 包覆函式傳給 custom hook,確保 custom hook 不會因為新 scope 而建立新 callback 給 app component
const getDataHandler = useCallback(data => {
const loadedTasks = Object.entries(data).map(([key, value]) => ({ id: key, ...value }));
setTasks(loadedTasks);
}, []);
// sendRequest 為 custom hook 回傳的 callback,處理打 API 的共用邏輯
const { isLoading, error, sendRequest: fetchTasks } = useHttp({ handler: getDataHandler });
useEffect(() => {
fetchTasks({ fetchConfig });
// 非 useEffect 內的東西就是 external state,所以需要放入 dependency 中記憶
}, [fetchTasks]);
const taskAddHandler = task => {
setTasks(prevTasks => prevTasks.concat(task));
};
return (
<React.Fragment>
<NewTask onAddTask={taskAddHandler} />
<Tasks items={tasks} loading={isLoading} error={error} onFetch={fetchTasks} />
</React.Fragment>
);
}
```
```javascript=
// src/hooks/use-http.js
// 叫用時傳入 callback
const useHttp = ({ handler }) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// 傳上去的 callback 決定其上層是否需要再次打 API
const sendRequest = useCallback(
async ({ fetchConfig }) => {
const { method, requestUrl, headers, body } = fetchConfig;
setIsLoading(true);
setError(null);
try {
const response = await fetch(requestUrl, {
method: method ?? 'GET',
headers: headers ?? {},
...(body && { body: JSON.stringify(body) }),
});
if (!response.ok) {
throw new Error('Request failed!');
}
const data = await response.json();
// 往外找拿 handler
handler(data);
} catch (err) {
setError(err.message || 'Something went wrong!');
} finally {
setIsLoading(false);
}
},
// 非 useEffect 內的東西就是 external state,需要放入 dependency 記憶
[handler]
);
return {
isLoading,
error,
sendRequest,
};
};
```
### 範例二:可以更進一步省略非必要的 `useCallback`
- 回到函式本質,函式因為 parameter 而產生更多的靈活性,且函式透過 input / output 使得每次執行完畢的結果都相同就稱之為 pure function(也就是說每次傳入同樣的 parameter 就會得到同樣的結果)
- 所以從原本是由傳 callback 給 `useHttp` 的到的工廠函式的結果,倒不如直接傳 paramter 給工廠函式的結果的函式
- 函式從 input 拿東西並不算 external state

```javascript=
// src/App.js
function App() {
const [tasks, setTasks] = useState([]);
const fetchConfig = {
method: 'GET',
requestUrl: 'https://udemy-react-15-190-default-rtdb.firebaseio.com/tasks.json',
};
// 不傳 callback 給工廠函式 useHttp
const { isLoading, error, sendRequest: fetchTasks } = useHttp();
useEffect(() => {
const getDataHandler = data => {
const loadedTasks = Object.entries(data).map(([key, value]) => ({ id: key, ...value }));
setTasks(loadedTasks);
};
// 而是直接傳 callback 給工廠函式之結果,這樣子工廠函式建立的函式就不用往外找,而是直接拿 input 即可
fetchTasks({ fetchConfig, handler: getDataHandler });
}, [fetchTasks]);
const taskAddHandler = task => {
setTasks(prevTasks => prevTasks.concat(task));
};
return (
<React.Fragment>
<NewTask onAddTask={taskAddHandler} />
<Tasks items={tasks} loading={isLoading} error={error} onFetch={fetchTasks} />
</React.Fragment>
);
}
```
```javascript=
// src/hooks/use-http.js
// 工廠函式不拿 callback
const useHttp = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// 而是直接傳 callback 給工廠函式生成的結果
const sendRequest = useCallback(async ({ fetchConfig, handler }) => {
const { method, requestUrl, headers, body } = fetchConfig;
setIsLoading(true);
setError(null);
try {
const response = await fetch(requestUrl, {
method: method ?? 'GET',
headers: headers ?? {},
...(body && { body: JSON.stringify(body) }),
});
if (!response.ok) {
throw new Error('Request failed!');
}
const data = await response.json();
// 直接拿 input,就不需要往外找了
handler(data);
} catch (err) {
setError(err.message || 'Something went wrong!');
} finally {
setIsLoading(false);
}
}, []);
return {
isLoading,
error,
sendRequest,
};
};
```
## Recap
1. 需要注意,每個 custom hook 都是獨立的 state,所以並不會影響其他 components 內叫用 custom hook 的 state(即便其他 components 都叫用同一個 custom hook,但因為 components -> call function,所以不同 components 都會建立新的 scope)
2. custom hook 就是專注處理共同的 state function
3. 別忘了「工廠函式」(factory function)
4. 函式可以透過 parameter 達到靈活性(callback 當然也可以當作 parameter 傳入)
## 課程
[React - The Complete Guide (incl Hooks, React Router, Redux) - sec. 15](https://www.udemy.com/course/react-the-complete-guide-incl-redux/learn/lecture/25599852#questions)