zent
Version:
一套前端设计语言和基于React的实现
332 lines (319 loc) • 8.18 kB
text/typescript
import {
CompositionEventHandler,
useCallback,
useEffect,
useMemo,
useRef,
} from 'react';
import {
CallbackNode,
unstable_IdlePriority as IdlePriority,
unstable_scheduleCallback as scheduleCallback,
} from 'scheduler';
import identity from '../../utils/identity';
import { FieldModel } from './models/field';
import { ValidateOption } from './validate';
import { last } from './utils';
export function multi<T, R>(...funcs: ((t: T) => R)[]): (t: T) => void {
return (t: T) => {
funcs.forEach(func => func(t));
};
}
export function useMulti<T, R>(
func1: (t: T) => R,
func2: (t: T) => R,
deps?: any[]
): (t: T) => void;
export function useMulti<T, R>(
func1: (t: T) => R,
func2: (t: T) => R,
func3: (t: T) => R,
deps?: any[]
): (t: T) => void;
export function useMulti<T, R>(
func1: (t: T) => R,
func2: (t: T) => R,
func3: (t: T) => R,
func4: (t: T) => R,
deps?: any[]
): (t: T) => void;
export function useMulti<T, R>(
func1: (t: T) => R,
func2: (t: T) => R,
func3: (t: T) => R,
func4: (t: T) => R,
func5: (t: T) => R,
deps?: any[]
): (t: T) => void;
export function useMulti<T, R>(
func1: (t: T) => R,
func2: (t: T) => R,
func3: (t: T) => R,
func4: (t: T) => R,
func5: (t: T) => R,
func6: (t: T) => R,
deps?: any[]
): (t: T) => void;
export function useMulti<T, R>(
func1: (t: T) => R,
func2: (t: T) => R,
func3: (t: T) => R,
func4: (t: T) => R,
func5: (t: T) => R,
func6: (t: T) => R,
func7: (t: T) => R,
deps?: any[]
): (t: T) => void;
export function useMulti<T, R>(
func1: (t: T) => R,
func2: (t: T) => R,
func3: (t: T) => R,
func4: (t: T) => R,
func5: (t: T) => R,
func6: (t: T) => R,
func7: (t: T) => R,
func8: (t: T) => R,
deps?: any[]
): (t: T) => void;
export function useMulti<T>(...funcs: any[]): (t: T) => void {
let deps: any[];
if (Array.isArray(last(funcs))) {
deps = funcs.pop()!;
} else {
deps = funcs;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
return useCallback(multi(...funcs), deps);
}
export type Func<Args extends any[], R> = (...args: Args) => R;
export type Middleware<F> = (next: F) => F;
/**
* ## example middleware
* ```tsx
* function when<Value>(condition: () => boolean | Promise<boolean>): Middleware<IValidator<Value>> {
* return (next: IValidator<Value>) =>
* createAsyncValidator((value, ctx) => {
* return of(condition()).pipe(
* switchMap(cond => {
* if (cond) {
* return isAsyncValidator(next) ? next.validator(value, ctx) ?? of(null) : of(next(value, ctx));
* }
* return of(null);
* }),
* );
* });
* }
* }
*
* const validator = compose(when(() => Math.random() > 0.5))(required('required if random number great then 5'));
*
* <Field validators={[validator]}></Field>
* ```
*/
export function compose<Target>(...middlewares: Middleware<Target>[]) {
return (target: Target) =>
middlewares.reduceRight(
(currentTarget, middleware) => middleware(currentTarget),
target
);
}
/**
* @deprecated use `useMulti` instead
*
* `const callback = useMAppend(foo, bar, baz)`
*
* is equal to
* ```js
* const callback = useCallback(arg => {
* foo(arg);
* bar(arg);
* baz(arg);
* }, [foo, bar, baz])
* ```
*/
export function useMAppend<T>(...fns: ((t: T) => void)[]): (t: T) => void {
return useCallback((value: T) => {
for (let i = 0; i < fns.length; i += 1) {
const f = fns[i];
f(value);
}
}, fns); // eslint-disable-line react-hooks/exhaustive-deps
}
/**
* `const callback = usePipe(foo, bar, baz)`
*
* is equal to
* ```js
* const callback = useMemo(() => arg => {
* return baz(bar(foo(arg)))
* }, [foo, bar, baz])
* ```
*/
export function usePipe<T0, T1, T2>(
fn0: (t0: T0) => T1,
fn1: (t1: T1) => T2,
deps?: any[]
): (t0: T0) => T2;
export function usePipe<T0, T1, T2, T3>(
fn0: (t0: T0) => T1,
fn1: (t1: T1) => T2,
fn2: (t2: T2) => T3,
deps?: any[]
): (t0: T0) => T3;
export function usePipe<T0, T1, T2, T3, T4>(
fn0: (t0: T0) => T1,
fn1: (t1: T1) => T2,
fn2: (t2: T2) => T3,
fn3: (t3: T3) => T4,
deps?: any[]
): (t0: T0) => T4;
export function usePipe<T0, T1, T2, T3, T4, T5>(
fn0: (t0: T0) => T1,
fn1: (t1: T1) => T2,
fn2: (t2: T2) => T3,
fn3: (t3: T3) => T4,
fn4: (t4: T4) => T5,
deps?: any[]
): (t0: T0) => T5;
export function usePipe<T0, T1, T2, T3, T4, T5, T6>(
fn0: (t0: T0) => T1,
fn1: (t1: T1) => T2,
fn2: (t2: T2) => T3,
fn3: (t3: T3) => T4,
fn4: (t4: T4) => T5,
fn5: (t5: T5) => T6,
deps?: any[]
): (t0: T0) => T6;
export function usePipe<T0, T1, T2, T3, T4, T5, T6, T7>(
fn0: (t0: T0) => T1,
fn1: (t1: T1) => T2,
fn2: (t2: T2) => T3,
fn3: (t3: T3) => T4,
fn4: (t4: T4) => T5,
fn5: (t5: T5) => T6,
fn6: (t6: T6) => T7,
deps?: any[]
): (t0: T0) => T7;
export function usePipe<T0, T1, T2, T3, T4, T5, T6, T7, T8>(
fn0: (t0: T0) => T1,
fn1: (t1: T1) => T2,
fn2: (t2: T2) => T3,
fn3: (t3: T3) => T4,
fn4: (t4: T4) => T5,
fn5: (t5: T5) => T6,
fn6: (t6: T6) => T7,
fn7: (t7: T7) => T8,
deps?: any[]
): (t0: T0) => T8;
export function usePipe<T, R>(...args: any[]): (v: T) => R {
const l = last(args);
let deps: any[];
if (Array.isArray(l)) {
args.pop();
deps = l;
} else {
deps = args;
}
return useMemo(() => {
const fn = args.reduceRight(
(next, f) => (arg: any) => next(f(arg)),
identity
);
return (t: T): R => fn(t);
}, deps); // eslint-disable-line react-hooks/exhaustive-deps
}
/**
* 生成一个默认的`onChange`回调,这个回调会触发`model.validate`
* 如果不需要在onChange的时候触发校验,如下即可:
* ```js
* const onChange = useCallback(value => model.value = value, [model]);
* ```
* 例如是一个`input`:
* ```ts
* const onChange = useCallback((value: React.ChangeEvent<HTMLInputElement>) => {
* model.value = e.target.value;
* }, [model]);
* ```
* 可以配合usePipe使用:
* ```js
* function mapEventToValue(e) {
* return e.target.value;
* }
* function Foo() {
* const onChange = FieldUtils.usePipe(
* mapEventToValue,
* FieldUtils.useChangeHandler(model),
* );
* }
* ```
*/
export function useChangeHandler<Value>(
model: FieldModel<Value>,
option = ValidateOption.Default | ValidateOption.ExcludePristine,
callback?: (value: Value) => void
) {
const taskRef = useRef<CallbackNode | null>(null);
const ref = useRef({
option,
callback,
});
useEffect(() => {
ref.current = {
option,
callback,
};
}, [option, callback]);
return useCallback(
(value: Value) => {
model.value = value;
ref.current.callback?.(value);
if (model.isCompositing) {
return;
}
if (!taskRef.current) {
taskRef.current = scheduleCallback(IdlePriority, () => {
taskRef.current = null;
model.validate(ref.current.option);
});
}
},
[model]
);
}
/**
* @deprecated
* It's an alias of `useChangeHandler`, use the latter instead.
*/
export const makeChangeHandler = useChangeHandler;
export interface ICompositionHandlers<E = Element> {
onCompositionStart: CompositionEventHandler<E>;
onCompositionEnd: CompositionEventHandler<E>;
}
/**
* 生成一组 `onCompositionStart` 和 `onCompositionEnd` 的回调函数,用于跟踪输入法 composition 的状态,
* 这个状态会写到 `model.isCompositing` 字段上。
* @param model 用于记录状态的 `model` 对象
* @param callbacks 上层传入的被代理的回调
*/
export function useCompositionHandler<Value, E = Element>(
model: FieldModel<Value>,
callbacks?: Partial<ICompositionHandlers<E>>
): ICompositionHandlers<E> {
const ref = useRef(callbacks);
useEffect(() => {
ref.current = callbacks;
});
return useMemo(
() => ({
onCompositionStart(e) {
model.isCompositing = true;
ref.current?.onCompositionStart?.(e);
},
onCompositionEnd(e) {
model.isCompositing = false;
ref.current?.onCompositionEnd?.(e);
},
}),
[model]
);
}