從介紹第一個 hooks 開始,我們都是在使用 React 提供的 hooks ,有沒有想過我們也可以實作自己的 hooks 呢?
先來看看我找到的一段對 custom hooks 非常簡單明瞭的解釋:
These are normal javascript functions which can use other hooks inside of it and contain a common stateful logic that can be reused within multiple components. These functions are prefixed with the word use.
當我們需要在兩個甚至更多的 component 中實現相同或者只有參數不同的 stateful 邏輯時,比起重複輸入相同的程式碼,不如把邏輯抽出去,獨立成一個可以重複使用的 hooks。
直接實際示範吧!
假設在做專案的過程中發現使用到 array 的時機實在太多了,因此決定把 array 的方法獨立出去封裝成一個 hooks,就叫他 useArray 吧:
import { useCallback, useState } from 'react';
export const useArray = initial => {
const [value, setValue] = useState(initial);
return {
value,
setValue,
add: useCallback((a) => setValue(v => [...v, a])),
clear: useCallback(() => setValue(() => [])),
removeIndex: useCallback((index) => {
setValue(v => {
const newValue = [...v];
newValue.splice(index,1);
return newValue;
})
})
}
}
如果按照大部份看到的定義來說,custom hooks 中需要呼叫基本的 hooks,不然就跟一般的 function 沒有什麼差別了呢。
範例中使用 useState 儲存陣列的值,並實作了一些陣列的基本操作,最後包在一個物件裡 return 回去。
定義完了就來使用它吧:
import { useArray } from './components/CustomHooks';
const App = () => {
const todos = useArray(['上班', 'it鐵人賽完賽', '學react']);
return (
<div>
<p>useArray test</p>
<ul>
{todos.value.map((todo, i) => {
return <li ket={i}>
{todo}
<button onClick={() => todos.removeIndex(i)}>delete</button>
</li>
})}
</ul>
<button onClick={() => todos.add(Math.random())}>add ramdom number</button>
<button onClick={todos.clear}>clear array</button>
</div>
)
}
export default App;
首先當然要先引入實作好的 hooks,再來看到
const todos = useArray(['上班', 'it鐵人賽完賽', '學react']);
後面的陣列就是上面實作 useArray 時傳進去的 initial 陣列, todos 則是 useArray 回傳的物件,因此陣列的內容存在 todos.value 中,其他陣列的操作方法也只需要從該物件拿出來用就沒問題了。
比較細心的讀者可能會發現,在 useArray 中只負責 state 跟 改變 state 的函式的建構而已,真正使用與呼叫 callback 都是在使用 custom hooks 的 component 中執行,如此一來更能達到易維護與可複用的特性。
hooks 的出現改變了整個 React 的生態圈,而能夠自己寫 hooks 又帶來了更多可能。其實已經有非常多人自己寫了 hooks 然後發到 npm 上囉,以後想要用 custom hooks 實作一個功能時,不妨先去看看是否有人已經做過同樣的 hooks,以免浪費時間實作已經存在的東西囉!
useArray
中的:
removeIndex: useCallback((index) => {
setValue(v => {
v.splice(index,1);
return v;
})
})
會有 side effect 喔!
因為 setValue 中回傳的是同一個 reference,雖然 array 中成功拿掉該 index 的元素,但不會觸發 re-render,可以改成如下比較適當:
removeIndex: useCallback((index) => {
setValue(v => {
const newV = [...v];
newV.splice(index,1);
return newV;
})
})
了解!感謝解惑~~~