[メモ] useState をラップしたようなフック
useState
をラップしたようなフックです。
親子関係のコンポーネントにて子側で props.value
を処理するような際に使用します。
- 親側からの
props.value
の変更を受けて値を同期- 値の比較により同期するかを制御
-
propsOnChange
を関数として確定したonChange
を第三要素として返す
useValue
import { useState, useEffect, Dispatch, SetStateAction } from 'react'
/** オプション指定 */
type Options = {
/** `propsValue` に変更があった場合 `setValue` による同期を行う (デフォルトは `true` 扱い) */
sync?: boolean
/**
* `useEffect` にて `value` と `propsValue` を比較して差があった場合に更新する制御
* - `boolean` 指定の場合そのまま更新制御に使用
*/
diff?: (<T = unknown>(value: T, propsValue: T) => boolean) | boolean
}
/** 値の比較にデフォルトで使用する関数 */
const diffDefault = <T>(val: T, pVal: T): boolean => {
if (Array.isArray(val) && Array.isArray(pVal)) {
const unified = Array.from(new Set([...val, ...pVal]))
return unified.length !== val.length || unified.length !== pVal.length
}
// オブジェクトの場合の比較
if (typeof val === 'object' || typeof pVal === 'object') {
return JSON.stringify(val) !== JSON.stringify(pVal)
}
return val !== pVal
}
/**
* `useState` をラップしたような関数
* - `propsValue` の変更が立った場合 `options?.sync` 次第で同期する (デフォルト同期)
* - 戻り値として `onChange` を関数として確定したものを返す
* - `options` の構成
* -- `sync`: `boolean`\
* `propsValue` に変更がある場合に同期するフラグ
* -- `diff`: `((value: unknown, propsValue: unknown) => boolean) | boolean`\
* 関数指定により `propsValue` との差分抽出のロジックを指定可能
*/
const useValue = <T, F extends Function | undefined>(
/** useState に渡す値 */
propsValue: T,
/** props などで渡される `onChange` を `Function` として確定させてから返す */
propsOnChange: F,
/** オプション指定 */
options?: Options,
): [T, Dispatch<SetStateAction<T>>, NonNullable<F>] => {
const [value, setValue] = useState<T>(propsValue)
const sync = options?.sync ?? true
const onChange = propsOnChange ?? (() => {})
const optionsDiff = options?.diff ?? diffDefault
const diff = typeof optionsDiff === 'boolean' ? () => optionsDiff : optionsDiff
useEffect(() => {
if (sync && diff(value, propsValue)) {
setValue(propsValue)
}
}, [propsValue, sync])
return [value, setValue, onChange as NonNullable<F>]
}
export default useValue
options.diff
の指定の事例
options.diff
const [value, setValue, onChange] = useValue(props.value, props.onChange, {
diff<T = string>(val: T, pVal: T) {
return val !== pVal
},
})