import { useEffect, useMemo, useState } from 'react'; import { IFormContext, useFormContext } from './context'; import { $FieldSetValue, BasicModel, FieldSetModel, FormStrategy, isFieldSetModel, isModelRef, ModelRef, } from './models'; import { IValidators } from './validate'; import { useDestroyOnUnmount, UnknownFieldSetModelChildren } from './utils'; import { get, isSome, or } from './maybe'; import { createModelNotFoundError, createUnexpectedModelTypeError, } from './error'; import isPlainObject from '../../utils/isPlainObject'; import { getFieldSetChildChangeObservable } from './listeners/set'; import { useObservableEagerState } from 'observable-hooks'; export type IUseFieldSet = [ IFormContext, FieldSetModel ]; function useFieldSetModel( field: | string | FieldSetModel | ModelRef<$FieldSetValue, any, FieldSetModel>, parent: FieldSetModel, strategy: FormStrategy ) { const model = useMemo(() => { let model: FieldSetModel; if (typeof field === 'string') { const m = parent.get(field); if (strategy === FormStrategy.View) { if (!m || !isFieldSetModel(m)) { model = new FieldSetModel({} as T); let v: Partial<$FieldSetValue> = {}; const potential = parent.getPatchedValue(field); if (isSome(potential)) { const inner = get(potential); if (isPlainObject(inner)) { v = inner as any; } } model.patchedValue = v; parent.registerChild(field, model as BasicModel); } else { model = m; } } else { if (!m) { throw createModelNotFoundError(field); } else if (!isFieldSetModel(m)) { throw createUnexpectedModelTypeError(field, 'FieldSetModel', m); } else { model = m; } } } else if (isModelRef<$FieldSetValue, any, FieldSetModel>(field)) { const m = field.getModel(); if (!m || !isFieldSetModel(m)) { model = new FieldSetModel({} as T); model.patchedValue = or(field.patchedValue, () => or(field.initialValue, () => ({})) ); field.setModel(model); } else { model = m; } } else { model = field; } return model; }, [field, parent, strategy]); return model; } /** * 创建一个 `FieldSet` * * `Model` 模式下传入字符串类型的 `field` 时, `validators` 无效。 * * @param field 字段名 * @param validators 校验函数数组 */ export function useFieldSet( field: string | ModelRef<$FieldSetValue, any, FieldSetModel>, validators?: IValidators<$FieldSetValue> ): IUseFieldSet; /** * 创建一个 `FieldSet` * * @param field model 对象 */ export function useFieldSet( field: FieldSetModel ): IUseFieldSet; export function useFieldSet( field: | string | FieldSetModel | ModelRef<$FieldSetValue, any, FieldSetModel>, validators: IValidators<$FieldSetValue> = [] ): IUseFieldSet { const { parent, strategy, form } = useFormContext(); const model = useFieldSetModel(field, parent, strategy); // Only update validators in View mode if ( strategy === FormStrategy.View && (typeof field === 'string' || isModelRef(field)) ) { model.validators = validators; } const childContext = useMemo( () => ({ strategy, form, parent: model as unknown as FieldSetModel, }), [strategy, form, model] ); /** * ignore returned value * user can get the value from model */ useObservableEagerState(model.error$); useDestroyOnUnmount(field, model, parent); return [childContext, model]; } /** * 订阅名为 `name` 的子 model 变更。 * 变更包括增加/删除该子 model,但不包括子 model 内部数据的变化。 * @param fieldSet 订阅 child 的 `FieldSetModel` * @param name child 的名字 */ export function useNamedChildModel< T extends UnknownFieldSetModelChildren, K extends keyof T = keyof T >(fieldSet: FieldSetModel, name: K) { const [child, setChild] = useState(fieldSet.get(name)); useEffect(() => { const $ = getFieldSetChildChangeObservable( fieldSet, name as string ).subscribe(n => { setChild(fieldSet.get(n as K)); }); return () => $.unsubscribe(); }, [fieldSet, name]); return child; }