# [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) ![](https://hackmd.io/_uploads/ryroMp5Es.png) - 將共用邏輯抽出(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 ![](https://hackmd.io/_uploads/S1CSiitwi.png) ```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 ![](https://hackmd.io/_uploads/Hkav-3Fvi.png) ```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)