回顧前一篇的困饒:「useCallback 中有從 props 傳來的 inner function 要如何正常運作?」
由於我們的 function 一直被重新建立的關係,在 useCallback 反而不如預期的運作,這時候我們可以搭配 useRef + useEffect 來達成。
亦或是 useEventCallback、useCallbackRef 類似的名詞組合可以透過 Google 搜尋都會查到類似的內容
簡單來說,這個 Hook 可以解決上述的情境,其實就是將傳入的 function 存放入 ref 並透過其性質來持續更新,簡易版本如下:
function useEventRef(callback) {
const eventRef = useRef(callback)
eventRef.current = callback
return (...args) => eventRef.current?.(...args)
}
來嘗試加入到 useToggle 裡面:
function useToggle(props = {}) {
const { defaultState, onOn, onOff } = props
if (defaultState !== undefined && typeof defaultState !== "boolean") {
throw new Error("UseToggle: defaultState should be Boolean")
}
const [state, setState] = useState(defaultState || false)
const onOnEventCallback = useEventRef(onOn)
const onOffEventCallback = useEventRef(onOff)
const toggleOn = useCallback(() => {
setState(true)
onOnEventCallback()
}, [])
const toggleOff = useCallback(() => {
setState(false)
onOffEventCallback()
}, [])
const toggle = useCallback(() => {
const action = state ? toggleOff : toggleOn
action()
}, [state])
return { isOn: state, toggle, toggleOn, toggleOff }
}
VOILA! 但這樣還不是最完美的,為了確保為「最新的」,可以再加入 useEffect,並可依照當下情境再加入 deps
useEffect(() => {
eventRef.current = callback
}, deps)
若有 reference equality 的需求可以再加入 useCallback
return useCallback((...args) => eventRef.current?.(...args),[])
在探索這個 hook 時,發現有些是使用 useLayoutEffect
:
useEffect(() => {
eventRef.current = callback
})
or
useLayoutEffect(() => {
eventRef.current = callback
})
透過官方可以知道 useEffect 與 useLayoutEffect 本質上有相同的行為,但觸發時間的時間點稍微不同之外,且 useLayoutEffect 在 server-side 上是沒有任何作用的,因此也可以找到類似這樣的寫法:
function useSafeEffect() {
return typeof window !== 'undefined' ? useLayoutEffect : useEffect
}
useSafeEffect(() => {
eventRef.current = callback
})
所以,這樣的useToggle 結構與 useEventRef 真的有必要嘛?
若今天只是從一個元件做邏輯上的分離,就不用這麼麻煩,但若是以共用的 library 來看,這類的情況就要考慮進去,雖然做法上有點取巧,可以感受到日後回來維護時腦筋又要再一次打結了 இдஇ
參考來源一一附上: