@ohkit/react-helper
Version:
some utils and hooks for react
77 lines (70 loc) • 3.03 kB
text/typescript
import {useState, useCallback} from 'react';
import {useRuntime} from '../runtime';
import {useCompatibleEffect} from '../compatible-effect';
export interface ISyncPropsStateOptions<T, K extends keyof T, Val extends T[K] = T[K]> {
defaultValue?: Val;
onChange?: (val: Val extends undefined ? T[K] : Required<T>[K]) => void;
// layoutEffect?: boolean;
/**
* 自定义比较函数
* @param pre 前一个值
* @param cur 当前值
*/
compare?: (pre: T[K], cur: T[K]) => boolean;
}
/**
* 使用组件的 props 属性同步组件状态
*
* @description T[K] 不能是个函数类型,在 React 中,useState 通常用于管理非函数类型的状态(如数字、字符串、对象等)
* 如果你需要管理一个函数类型的状态,直接使用 useState 可能会导致一些问题,因为 React 会将函数视为惰性初始化的函数,而不是状态值本身。
*
* @param key 要同步的 props 的键名
* @param props 组件的 props 对象
* @param options 可选配置参数
* @returns 返回内部状态和更新状态的函数
*/
export function useSyncPropsState<T extends object, K extends keyof T, Val extends T[K] = T[K]>(
props: T,
key: K,
options?: ISyncPropsStateOptions<T, K, Val>
): [Val extends undefined ? T[K] : Required<T>[K], (val: T[K] extends undefined ? T[K] : Required<T>[K], emitChange?: boolean) => void] {
const propVal = props[key];
const isFuncState = typeof propVal === 'function';
// const isObjectState = typeof propVal === 'object';
const {
onChange,
// layoutEffect = false,
defaultValue = propVal,
compare = (pre: T[K], cur: T[K]) => pre === cur,
} = options || {};
// 考虑可能有 function 类型的 state 惰性初始化
const [inner, setInner] = useState(() => typeof propVal !== 'undefined' ? propVal : defaultValue);
const [runtime] = useRuntime({
onChange,
compare,
inner,
isFuncState,
}, ['compare', 'onChange', 'inner', 'isFuncState']);
const setFinalInner = useCallback((val: typeof propVal, emitChange = true) => {
if (!runtime.compare?.(runtime.inner, val)) {
// console.log('[useSyncPropsState] real change pre => cur: ', runtime.inner, val);
// function 类型的state 需要惰性更新
setInner(runtime.isFuncState ? () => val : val);
// init 时,不应触发change
emitChange && runtime.onChange?.(val);
}
}, [runtime]);
// const effect = layoutEffect ? useLayoutEffect : useEffect;
useCompatibleEffect(() => {
// console.log('[useSyncPropsState] propVal useEffect change: ', propVal);
let newVal = propVal;
if (
typeof propVal === 'undefined'
&& typeof defaultValue !== 'undefined'
) {
newVal = defaultValue;
}
setFinalInner(newVal, false);
}, [defaultValue, propVal]);
return [inner, setFinalInner];
}