import * as React from 'react'; import { merge, never, of, Observable } from 'rxjs'; import { filter, switchMap } from 'rxjs/operators'; import { useFormContext, FormContext, IFormContext } from './context'; import { useValue$ } from './hooks'; import { FieldModel, FieldArrayModel, BasicModel, isFieldSetModel, isFieldModel, isFieldArrayModel, isModelRef, ModelRef, } from './models'; import { noop, $MergeProps } from './utils'; export interface IFieldSetValueProps { name: string; children?: React.ReactNode; } function getModelFromContext( ctx: IFormContext, name: string | undefined, model: Model | undefined, check: (m: any) => m is Model, ): Model | null { const { parent } = ctx; const m = React.useMemo(() => { if (typeof name === 'string') { const m = parent.get(name); if (check(m)) { return m; } } if (check(model)) { return model; } return null; }, [ctx, name, model, check, parent]); const [maybeModel, setModel] = React.useState(m); React.useEffect(() => { if (!name) { return noop; } const m = parent.get(name); check(m) && setModel(m); const $ = merge(parent.childRegister$, parent.childRemove$) .pipe(filter(change => change === name)) .subscribe(name => { const candidate = parent.get(name); if (check(candidate)) { setModel(candidate); } }); return () => $.unsubscribe(); }, [name, parent, m]); return maybeModel; } /** * 根据 `name` 订阅 `FieldSet` 的值 */ export function FieldSetValue({ name, children }: IFieldSetValueProps) { const ctx = useFormContext(); const model = getModelFromContext(ctx, name, undefined, isFieldSetModel); const childContext = React.useMemo( () => ({ ...ctx, parent: model!, }), [ctx, model], ); if (model) { return ( {children} ); } return null; } export interface IFieldValueCommonProps { /** * render props,参数是 Field 当前的值 */ children?: (value: T | null) => React.ReactElement | null; } export interface IFieldValueViewDrivenProps extends IFieldValueCommonProps { name: string; } export interface IFieldValueModelDrivenProps extends IFieldValueCommonProps { model: FieldModel; } export type IFieldValueProps = IFieldValueModelDrivenProps | IFieldValueViewDrivenProps; export function useFieldValue(field: string | FieldModel): T | null { const ctx = useFormContext(); const [model, setModel] = React.useState | ModelRef> | null>( isFieldModel(field) || isModelRef>(field) ? field : null, ); React.useEffect(() => { if (typeof field !== 'string') { setModel(isFieldModel(field) || isModelRef(field) ? field : null); return noop; } const m = ctx.parent.get(field); if (isFieldModel(m)) { setModel(m); } const $ = merge(ctx.parent.childRegister$, ctx.parent.childRemove$) .pipe(filter(change => change === field)) .subscribe(name => { const candidate = ctx.parent.get(name); if (isFieldModel(candidate)) { setModel(candidate); } }); return () => $.unsubscribe(); }, [field, ctx.parent]); if (!model) { useValue$(never(), null); return null; } else if (isModelRef>(model)) { const [value, setValue] = React.useState(null); React.useEffect(() => { const $ = model.model$ .pipe( switchMap | null, Observable>(it => { if (isFieldModel(it)) { return it.value$; } return of(null); }), ) .subscribe((value: T | null) => setValue(value)); return () => $.unsubscribe(); }, [model]); return value; } return useValue$(model.value$, model.value); } /** * 根据 `name` 或者 `model` 订阅字段的更新 */ export function FieldValue(props: IFieldValueProps): React.ReactElement | null { const { name, model, children } = props as $MergeProps>; const value = useFieldValue(model || name); if (children) { return children(value); } return (value as unknown) as React.ReactElement; } /** * 根据 `name` 或者 `model` 订阅 `FieldArray` 的更新 */ export function useFieldArrayValue>(field: string | FieldArrayModel) { const ctx = useFormContext(); const model = getModelFromContext( ctx, field as string | undefined, field as FieldArrayModel | undefined, isFieldArrayModel, ); if (!model) { useValue$(never(), null); return null; } const children = useValue$(model.children$, model.children); return children; }