Skip to content

Instantly share code, notes, and snippets.

@xpsteven
Last active April 19, 2019 02:01
Show Gist options
  • Save xpsteven/8663c36fd176b75b81ad5caa02afac8f to your computer and use it in GitHub Desktop.
Save xpsteven/8663c36fd176b75b81ad5caa02afac8f to your computer and use it in GitHub Desktop.
React Function Component Pattern 2:用 useRef / useEffect / useCallback 封裝 callback 函數
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