Last active
April 19, 2019 02:01
-
-
Save xpsteven/8663c36fd176b75b81ad5caa02afac8f to your computer and use it in GitHub Desktop.
React Function Component Pattern 2:用 useRef / useEffect / useCallback 封裝 callback 函數
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useEffect, useRef, ChangeEventHandler, memo } from 'react'; | |
/** 使用 MyFrom 的範例程式 */ | |
export function Example() { | |
/** | |
* 即使用 MyForm 的設計師不負責任的使用 arrow function | |
* 也只會觸發 MyForm 的 render | |
* 但不會觸發 MemoedInput 的 render | |
*/ | |
return <MyForm onChange={(e) => console.log(e.target.value)} />; | |
} | |
/** MyForm: 一個接收 onChange 且往下傳的元件 */ | |
export const MyForm: React.FC<IMyFormProps> = (props) => { | |
/** betterOnChange 不會變,但實際上會呼叫最新的 props.onChange */ | |
const betterOnChange = useRefCallback(props.onChange); | |
return ( | |
<form> | |
{/* onChange 的改變不會觸發 MemoedInput 的 render */} | |
<MemoedInput onChange={betterOnChange} /> | |
</form> | |
); | |
}; | |
/** MyInput: 最終收到 onChange 的元件 */ | |
const MyInput: React.FC<IMyFormProps> = (props) => { | |
return ( | |
<input onChange={props.onChange} /> | |
); | |
}; | |
/** memo 起來效果類似 extends PureComponent */ | |
export const MemoedInput = memo(MyInput); | |
/** 另一種範例 */ | |
export function Example2() { | |
/** | |
* 即使用 MyForm 的設計師不負責任的使用 arrow function | |
* 也只會觸發 MyForm2 的 render | |
* 但不會觸發 MemoedInput 的 render | |
*/ | |
return <MyForm2 onChange={(e) => console.log(e.target.value)} />; | |
} | |
/** MyForm2: 這個 case 不會把 onChange 往下傳,但本身會做一些事情在呼叫 porps.onCahgne */ | |
export const MyForm2: React.FC<IMyFormProps> = (props) => { | |
/** betterOnChange 永遠不會變,但實際上會呼叫最新的 props.onChange */ | |
const betterOnChange = useRefCallback(props.onChange); | |
const wrapOnChange = (e) => { | |
/** do smoething */ | |
betterOnChange(e); | |
}; | |
/** 使用以下代碼 props.onChange 變動會觸發 MemoedInput render */ | |
// const wrapOnChange = useCallback((e) => { | |
// /** do smoething */ | |
// props.onChange(e); | |
// }, [props.onChange]); | |
return ( | |
<form> | |
{/* onChange 的改變不會觸發 MemoedInput 的 render */} | |
<MemoedInput onChange={wrapOnChange} /> | |
</form> | |
); | |
}; | |
/** | |
* 合併 useRef + useEffect + useCallback 成一個 use function | |
* 原理: | |
* 函式內部產生 ref,並將 callback 儲存於 ref.current | |
* 使用 useEffect 監聽 callback 刷新 ref.current | |
* 使用 useCallback 回傳 memo 起來的函式,此函式的 reference 不會改變 | |
* 將此函式傳遞給子元件可以通過 component rerender 的檢查 | |
* 此函式執行的回傳值永遠是正確的 (ref.current) 的結果 | |
*/ | |
export function useRefCallback<C extends (...args: any[]) => void>(callback: C | undefined) { | |
/** 用 ref 追蹤最新的 callback */ | |
const ref = useRef(callback); | |
useEffect(() => { | |
ref.current = callback; | |
}, [callback]); | |
/** 封裝後的函數 */ | |
const tmp = ((...args: any[]) => { | |
if (ref.current instanceof Function) { | |
return ref.current(...args); | |
} | |
return void 0; | |
}) as C; | |
/** 只會回傳第一次呼叫所產生的 tmp */ | |
return useCallback(tmp, []); | |
} | |
/** 執行時期 props */ | |
export interface IMyFormProps { | |
onChange: ChangeEventHandler<HTMLInputElement>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment