UNPKG

zent

Version:

一套前端设计语言和基于React的实现

332 lines (319 loc) 8.18 kB
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] ); }