UNPKG

@formily/core

Version:

English | [简体中文](./README.zh-cn.md)

1,469 lines (1,387 loc) 42.8 kB
import { isFn, isEqual, toArr, isNum, isArr, clone, log, isValid, FormPath, FormPathPattern, BigData, each, isObj } from '@formily/shared' import { FormValidator, setValidationLanguage, setValidationLocale } from '@formily/validator' import { FormHeart } from './shared/lifecycle' import { FormGraph } from './shared/graph' import { FormState } from './state/form' import { VirtualFieldState } from './state/virtual-field' import { FieldState } from './state/field' import { IFormState, IFieldState, IVirtualFieldState, IFormCreatorOptions, IFieldStateProps, IVirtualFieldStateProps, IFormSubmitResult, IFormValidateResult, IFormResetOptions, IField, IVirtualField, isField, FormHeartSubscriber, LifeCycleTypes, isVirtualField, isFormState, isFieldState, isVirtualFieldState, IFormExtendedValidateFieldOptions } from './types' export * from './shared/lifecycle' export * from './types' export function createForm<FieldProps, VirtualFieldProps>( options: IFormCreatorOptions = {} ) { function onGraphChange({ type, payload }) { heart.publish(LifeCycleTypes.ON_FORM_GRAPH_CHANGE, graph) if (type === 'GRAPH_NODE_WILL_UNMOUNT') { validator.unregister(payload.path.toString()) } } function syncFieldValues(state: IFieldState) { const dataPath = FormPath.parse(state.name) const parent = graph.getLatestParent(state.path) const parentValue = getFormValuesIn(parent.path) const value = getFormValuesIn(state.name) /** * https://github.com/alibaba/formily/issues/267 dynamic remove node */ let removed = false if (isArr(parentValue) && !dataPath.existIn(parentValue, parent.path)) { if ( !parent.path .getNearestChildPathBy(state.path) .existIn(parentValue, parent.path) ) { graph.remove(state.path) removed = true } } else { each(env.removeNodes, (_, name) => { if (dataPath.includes(name)) { graph.remove(state.path) delete env.removeNodes[name] removed = true } }) } if (removed) return if (!isEqual(value, state.value)) { state.value = value } } function syncFieldIntialValues(state: IFieldState) { if (state.name === '') return const initialValue = getFormInitialValuesIn(state.name) if (!isEqual(initialValue, state.initialValue)) { state.initialValue = initialValue if (!isValid(state.value)) { state.value = initialValue } else if ( /array/gi.test(state.dataType) && state.value && state.value.length === 0 ) { state.value = initialValue } } } function notifyFormValuesChange() { if ( isFn(options.onChange) && state.state.mounted && !state.state.unmounted ) { clearTimeout(env.onChangeTimer) env.onChangeTimer = setTimeout(() => { if (state.state.unmounted) return options.onChange(clone(getFormValuesIn(''))) }) } heart.publish(LifeCycleTypes.ON_FORM_VALUES_CHANGE, state) } function notifyFormInitialValuesChange() { heart.publish(LifeCycleTypes.ON_FORM_INITIAL_VALUES_CHANGE, state) } function onFormChange(published: IFormState) { heart.publish(LifeCycleTypes.ON_FORM_CHANGE, state) const valuesChanged = state.isDirty('values') const initialValuesChanged = state.isDirty('initialValues') const unmountedChanged = state.isDirty('unmounted') const mountedChanged = state.isDirty('mounted') const initializedChanged = state.isDirty('initialized') const editableChanged = state.isDirty('editable') if (valuesChanged || initialValuesChanged) { const updateFields = (field: IField | IVirtualField) => { if (isField(field)) { field.setState(state => { if (valuesChanged) { syncFieldValues(state) } if (initialValuesChanged) { syncFieldIntialValues(state) } }) } } if (valuesChanged || initialValuesChanged) { /* * 考虑初始化的时候还没生成节点树 * 2种数据同步策略, * 1. 精确同步,基于操作节点,静默更新父子节点,同时不会回流onFormChange * 2. 批量同步,但是会采用异步节流方式合并同步任务 */ if (graph.size > 20) { clearTimeout(env.syncFormStateTimer) env.syncFormStateTimer = setTimeout(() => { graph.eachChildren(updateFields) }) } else { graph.eachChildren(updateFields) } // } } if (valuesChanged) { notifyFormValuesChange() } if (initialValuesChanged) { notifyFormInitialValuesChange() } } if (editableChanged) { graph.eachChildren((field: IField | IVirtualField) => { if (isField(field)) { field.setState(state => { state.formEditable = published.editable }) } }) } if (unmountedChanged && published.unmounted) { heart.publish(LifeCycleTypes.ON_FORM_UNMOUNT, state) } if (mountedChanged && published.mounted) { heart.publish(LifeCycleTypes.ON_FORM_MOUNT, state) } if (initializedChanged) { heart.publish(LifeCycleTypes.ON_FORM_INIT, state) } } function updateRecoverableShownState( parentState: | IVirtualFieldState<VirtualFieldProps> | IFieldState<FieldProps>, childState: IVirtualFieldState<VirtualFieldProps> | IFieldState<FieldProps>, name: 'visible' | 'display' ) { const lastShownState = env.lastShownStates[childState.path] const lastStateValue = childState[name] if (parentState[name] && lastShownState && lastShownState[name] === false) { childState[name] = false delete lastShownState[name] if ( !lastShownState.hasOwnProperty('visible') && !lastShownState.hasOwnProperty('display') ) { delete env.lastShownStates[childState.path] } } else { childState[name] = parentState[name] } if (!parentState[name] && !lastStateValue) { if (!lastShownState) { env.lastShownStates[childState.path] = {} } env.lastShownStates[childState.path][name] = false } } function onFieldChange({ field, path }) { function notifyTreeFromValues() { field.setState(syncFieldValues) graph.eachParent(path, (field: IField) => { if (isField(field)) { field.setState(syncFieldValues, true) } }) graph.eachChildren(path, (field: IField) => { if (isField(field)) { field.setState(syncFieldValues) } }) notifyFormValuesChange() } function notifyTreeFromInitialValues() { field.setState(syncFieldIntialValues) graph.eachParent(path, (field: IField) => { if (isField(field)) { field.setState(syncFieldIntialValues, true) } }) graph.eachChildren(path, (field: IField) => { if (isField(field)) { field.setState(syncFieldIntialValues) } }) notifyFormInitialValuesChange() } return (published: IFieldState<FieldProps>) => { const valueChanged = field.isDirty('value') const initialValueChanged = field.isDirty('initialValue') const visibleChanged = field.isDirty('visible') const displayChanged = field.isDirty('display') const unmountedChanged = field.isDirty('unmounted') const mountedChanged = field.isDirty('mounted') const initializedChanged = field.isDirty('initialized') const warningsChanged = field.isDirty('warnings') const errorsChanged = field.isDirty('errors') const editableChanged = field.isDirty('editable') if (initializedChanged) { heart.publish(LifeCycleTypes.ON_FIELD_INIT, field) const isEmptyValue = !isValid(published.value) const isEmptyInitialValue = !isValid(published.initialValue) if (isEmptyValue || isEmptyInitialValue) { field.setSourceState((state: IFieldState<FieldProps>) => { if (isEmptyValue) { const formValue = getFormValuesIn(state.name) state.value = isValid(formValue) ? formValue : state.value } if (isEmptyInitialValue) { const formInitialValue = getFormInitialValuesIn(state.name) state.initialValue = isValid(formInitialValue) ? formInitialValue : state.initialValue } }) } } const wasHidden = published.visible == false || published.unmounted === true if (valueChanged) { if (!wasHidden) { setFormValuesIn(path, published.value, true) notifyTreeFromValues() } heart.publish(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, field) } if (initialValueChanged) { if (!wasHidden) { setFormInitialValuesIn(path, published.initialValue, true) notifyTreeFromInitialValues() } heart.publish(LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE, field) } if (displayChanged || visibleChanged) { if (visibleChanged) { if (!published.visible) { if (isValid(published.value)) { field.setSourceState((state: IFieldState<FieldProps>) => { state.visibleCacheValue = published.value }) } deleteFormValuesIn(path) notifyTreeFromValues() } else { if (!existFormValuesIn(path)) { setFormValuesIn( path, isValid(published.visibleCacheValue) ? published.visibleCacheValue : published.initialValue, true ) notifyTreeFromValues() } } } graph.eachChildren(path, childState => { childState.setState((state: IFieldState<FieldProps>) => { if (visibleChanged) { updateRecoverableShownState(published, state, 'visible') } if (displayChanged) { updateRecoverableShownState(published, state, 'display') } }, true) }) } if ( unmountedChanged && (published.display !== false || published.visible === false) && published.unmountRemoveValue ) { if (published.unmounted) { if (isValid(published.value)) { field.setSourceState((state: IFieldState<FieldProps>) => { state.visibleCacheValue = published.value }) } deleteFormValuesIn(path, true) notifyTreeFromValues() } else { if (!existFormValuesIn(path)) { setFormValuesIn( path, isValid(published.visibleCacheValue) ? published.visibleCacheValue : published.initialValue, true ) notifyTreeFromValues() } } heart.publish(LifeCycleTypes.ON_FIELD_UNMOUNT, field) } if (mountedChanged && published.mounted) { heart.publish(LifeCycleTypes.ON_FIELD_MOUNT, field) } if (errorsChanged) { syncFormMessages('errors', published) } if (warningsChanged) { syncFormMessages('warnings', published) } if ( unmountedChanged || visibleChanged || displayChanged || editableChanged ) { //fix #682 resetFormMessages(published) } heart.publish(LifeCycleTypes.ON_FIELD_CHANGE, field) } } function onVirtualFieldChange({ field, path }) { return (published: IVirtualFieldState<VirtualFieldProps>) => { const visibleChanged = field.isDirty('visible') const displayChanged = field.isDirty('display') const mountedChanged = field.isDirty('mounted') const initializedChanged = field.isDirty('initialized') if (initializedChanged) { heart.publish(LifeCycleTypes.ON_FIELD_INIT, field) } if (visibleChanged || displayChanged) { graph.eachChildren(path, childState => { childState.setState( (state: IVirtualFieldState<VirtualFieldProps>) => { if (visibleChanged) { updateRecoverableShownState(published, state, 'visible') } if (displayChanged) { updateRecoverableShownState(published, state, 'display') } }, true ) }) } if (mountedChanged && published.mounted) { heart.publish(LifeCycleTypes.ON_FIELD_MOUNT, field) } heart.publish(LifeCycleTypes.ON_FIELD_CHANGE, field) } } function registerVirtualField({ name, path, props, display, visible, computeState, useDirty }: IVirtualFieldStateProps): IVirtualField { const nodePath = FormPath.parse(path || name) const dataPath = transformDataPath(nodePath) let field: IVirtualField const createField = (field?: IVirtualField) => { const alreadyHaveField = !!field field = field || new VirtualFieldState({ nodePath, dataPath, computeState, useDirty: isValid(useDirty) ? useDirty : options.useDirty }) field.subscription = { notify: onVirtualFieldChange({ field, path: nodePath }) } heart.publish(LifeCycleTypes.ON_FIELD_WILL_INIT, field) if (!alreadyHaveField) { graph.appendNode(nodePath, field) } heart.batch(() => { //fix #766 field.batch(() => { field.setState((state: IVirtualFieldState<VirtualFieldProps>) => { state.initialized = true state.props = props if (isValid(visible)) { state.visible = visible } if (isValid(display)) { state.display = display } }) batchRunTaskQueue(field, nodePath) }) }) return field } if (graph.exist(nodePath)) { field = graph.get(nodePath) //field = createField(field) 如果重置会导致#565的问题,目前还没想清楚不重置会有啥问题 if (isField(field)) { graph.replace(nodePath, field) } } else { field = createField() } return field } function registerField({ path, name, value, initialValue, required, rules, editable, visible, display, computeState, dataType, useDirty, unmountRemoveValue, props }: Exclude<IFieldStateProps, 'dataPath' | 'nodePath'>): IField { let field: IField const nodePath = FormPath.parse(path || name) const dataPath = transformDataPath(nodePath) const createField = (field?: IField) => { const alreadyHaveField = !!field field = field || new FieldState({ nodePath, dataPath, computeState, dataType, unmountRemoveValue, useDirty: isValid(useDirty) ? useDirty : options.useDirty }) field.subscription = { notify: onFieldChange({ field, path: nodePath }) } heart.publish(LifeCycleTypes.ON_FIELD_WILL_INIT, field) if (!alreadyHaveField) { graph.appendNode(nodePath, field) } heart.batch(() => { field.batch(() => { field.setState((state: IFieldState<FieldProps>) => { const formValue = getFormValuesIn(state.name) const formInitialValue = getFormInitialValuesIn(state.name) if (isValid(value)) { // value > formValue > initialValue state.value = value } else if ( existFormValuesIn(state.name) || formValue !== undefined ) { state.value = formValue } else if (isValid(initialValue)) { state.value = initialValue } if (isValid(initialValue)) { state.initialValue = initialValue } else if (isValid(formInitialValue)) { state.initialValue = formInitialValue } if (isValid(visible)) { state.visible = visible } if (isValid(display)) { state.display = display } if (isValid(props)) { state.props = props } if (isValid(required)) { state.required = required } if (isValid(rules)) { state.rules = rules as any } if (isValid(editable)) { state.selfEditable = editable } if (isValid(options.editable)) { state.formEditable = options.editable } state.initialized = true }) batchRunTaskQueue(field, nodePath) }) }) validator.register(nodePath, validate => { const { value, rules, editable, visible, unmounted, display } = field.getState() // 不需要校验的情况有: 非编辑态(editable),已销毁(unmounted), 逻辑上不可见(visible) if ( editable === false || visible === false || unmounted === true || display === false || (field as any).disabledValidate ) return validate(value, []) clearTimeout((field as any).validateTimer) ;(field as any).validateTimer = setTimeout(() => { field.setState(state => { state.validating = true }) }, 60) heart.publish(LifeCycleTypes.ON_FIELD_VALIDATE_START, field) return validate(value, rules).then(({ errors, warnings }) => { clearTimeout((field as any).validateTimer) return new Promise(resolve => { field.setState((state: IFieldState<FieldProps>) => { state.validating = false state.ruleErrors = errors state.ruleWarnings = warnings }) heart.publish(LifeCycleTypes.ON_FIELD_VALIDATE_END, field) resolve({ errors, warnings }) }) }) }) return field } if (graph.exist(nodePath)) { field = graph.get(nodePath) //field = createField(field) 如果重置会导致#565的问题,目前还没想清楚不重置会有啥问题 if (isVirtualField(field)) { graph.replace(nodePath, field) } } else { field = createField() } return field } function resetFormMessages(fieldState: IFieldState) { const { path, visible, display, unmounted, editable } = fieldState if ( editable === false || visible === false || unmounted === true || display === false ) { state.setSourceState(state => { state.errors = state.errors || [] state.warnings = state.warnings || [] state.errors = state.errors.reduce((buf: any, item: any) => { if (item.path === path) { return buf } else { return buf.concat(item) } }, []) state.warnings = state.warnings.reduce((buf: any, item: any) => { if (item.path === path) { return buf } else { return buf.concat(item) } }, []) if (state.errors.length) { state.invalid = true state.valid = false } else { state.invalid = false state.valid = true } }) } } //实时同步Form Messages function syncFormMessages(type: string, fieldState: IFieldState) { const { name, path } = fieldState const messages = fieldState[type] state.setSourceState(state => { let foundField = false state[type] = state[type] || [] state[type] = state[type].reduce((buf: any, item: any) => { if (item.path === path) { foundField = true return messages.length ? buf.concat({ path, messages }) : buf } else { return buf.concat(item) } }, []) if (!foundField && messages.length) { state[type].push({ name, path, messages }) } if (state.errors.length) { state.invalid = true state.valid = false } else { state.invalid = false state.valid = true } }) } function transformDataPath(path: FormPathPattern) { const newPath = FormPath.getPath(path) return newPath.reduce((path: FormPath, key: string, index: number) => { if (index >= newPath.length - 1) return path.concat([key]) const realPath = newPath.slice(0, index + 1) const dataPath = path.concat([key]) const selected = graph.get(realPath) if (isVirtualField(selected)) { return path } return dataPath }, FormPath.getPath('')) } function setFormIn( path: FormPathPattern, key: string, value: any, silent?: boolean ) { const method = silent ? 'setSourceState' : 'setState' state[method](state => { FormPath.setIn(state[key], transformDataPath(path), value) if (key === 'values') { state.modified = true } }, silent) } function deleteFormIn(path: FormPathPattern, key: string, silent?: boolean) { const method = silent ? 'setSourceState' : 'setState' state[method](state => { FormPath.deleteIn(state[key], transformDataPath(path)) if (key === 'values') { state.modified = true } }, silent) } function deleteFormValuesIn(path: FormPathPattern, silent?: boolean) { deleteFormIn(path, 'values', silent) } function setFormValuesIn( path: FormPathPattern, value?: any, silent?: boolean ) { return setFormIn(path, 'values', value, silent) } function setFormInitialValuesIn( path: FormPathPattern, value?: any, silent?: boolean ) { return setFormIn(path, 'initialValues', value, silent) } function getFormIn(path: FormPathPattern, key?: string) { return state.getState(state => FormPath.getIn(state[key], transformDataPath(path)) ) } function getFormValuesIn(path: FormPathPattern) { return getFormIn(path, 'values') } function existFormValuesIn(path: FormPathPattern) { return state.getState(state => FormPath.existIn(state.values, transformDataPath(path)) ) } function getFormInitialValuesIn(path: FormPathPattern) { return getFormIn(path, 'initialValues') } /** * * @param input IField | FormPathPattern */ function createMutators(input: any) { let field: IField if (!isField(input)) { const selected = graph.select(input) if (selected) { field = selected } else { throw new Error( 'The `createMutators` can only accept FieldState instance or FormPathPattern.' ) } } else { field = input } function setValue(...values: any[]) { field.setState((state: IFieldState<FieldProps>) => { state.value = values[0] state.values = values }) heart.publish(LifeCycleTypes.ON_FIELD_INPUT_CHANGE, field) heart.publish(LifeCycleTypes.ON_FORM_INPUT_CHANGE, state) } function removeValue(key: string | number) { const nodePath = field.getSourceState(state => state.path) if (isValid(key)) { const childNodePath = FormPath.parse(nodePath).concat(key) env.removeNodes[childNodePath.toString()] = true deleteFormValuesIn(childNodePath) field.notify(field.getState()) } else { const parent = graph.selectParent(nodePath) env.removeNodes[nodePath.toString()] = true deleteFormValuesIn(nodePath) if (parent) { parent.notify(parent.getState()) } } heart.publish(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, field) heart.publish(LifeCycleTypes.ON_FIELD_INPUT_CHANGE, field) heart.publish(LifeCycleTypes.ON_FORM_INPUT_CHANGE, state) } function getValue() { return field.getSourceState(state => state.value) } function onGraphChange(callback: () => void) { let timer = null const id = graph.subscribe(() => { clearTimeout(timer) timer = setTimeout(() => { graph.unsubscribe(id) callback() }) }) } //1. 无法自动交换通过移动来新增删除子列表元素的状态 //2. 暂时不支持通过setFieldState修改值场景的状态交换 function swapState($from: number, $to: number) { const keys: string[] = ['initialValue', 'visibleCacheValue', 'values'] const arrayName = field.getSourceState(state => state.name) const fromFieldsName = `${arrayName}.${$from}.*` const toFieldsName = `${arrayName}.${$to}.*` const cache = {} const calculatePath = (name: string, $from: number, $to: number) => { return name.replace(`${arrayName}.${$from}`, `${arrayName}.${$to}`) } graph.select(fromFieldsName, field => { field.setSourceState((state: IFieldState) => { const targetState = getFieldState(calculatePath(state.name, $from, $to)) || {} keys.forEach(key => { cache[state.name] = cache[state.name] || {} cache[state.name][key] = state[key] state[key] = targetState && targetState[key] }) }) }) graph.select(toFieldsName, field => { field.setSourceState((state: IFieldState) => { const cacheState = cache[calculatePath(state.name, $to, $from)] || {} keys.forEach(key => { state[key] = cacheState[key] }) }) }) } function swapAfterState(start: number, arrayLength: number, step = 1) { for (let i = arrayLength - 1; i >= start + 1; i -= step) { swapState(i, i - 1) } } const mutators = { change(...values: any[]) { setValue(...values) return values[0] }, focus() { field.setState((state: IFieldState<FieldProps>) => { state.active = true }) }, blur() { field.setState((state: IFieldState<FieldProps>) => { state.active = false state.visited = true }) }, push(value?: any) { const arr = toArr(getValue()).slice() arr.push(value) setValue(arr) return arr }, pop() { const arr = toArr(getValue()).slice() arr.pop() setValue(arr) return arr }, insert(index: number, value: any) { const arr = toArr(getValue()).slice() arr.splice(index, 0, value) setValue(arr) onGraphChange(() => { swapAfterState(index, arr.length) }) return arr }, remove(index?: number | string) { let val = getValue() if (isNum(index) && isArr(val)) { val = [].concat(val) const lastIndex = val.length - 1 val.splice(index, 1) if (index < lastIndex) { swapState(Number(index), Number(index) + 1) } setValue(val) } else { removeValue(index) } }, exist(index?: number | string) { const newPath = field.getSourceState(state => FormPath.parse(state.path) ) const val = getValue() return (isValid(index) ? newPath.concat(index) : newPath).existIn( val, newPath ) }, unshift(value: any) { return mutators.insert(0, value) }, shift() { const arr = toArr(getValue()).slice() arr.shift() swapState(0, 1) setValue(arr) return arr }, move($from: number, $to: number) { const arr = toArr(getValue()).slice() const item = arr[$from] arr.splice($from, 1) arr.splice($to, 0, item) swapState($from, $to) setValue(arr) return arr }, moveUp(index: number) { const len = toArr(getValue()).length return mutators.move(index, index - 1 < 0 ? len - 1 : index - 1) }, moveDown(index: number) { const len = toArr(getValue()).length return mutators.move(index, index + 1 > len ? 0 : index + 1) }, validate(opts?: IFormExtendedValidateFieldOptions) { return validate( field.getSourceState(state => state.path), { ...opts, hostRendering: false } ) } } return mutators } function clearErrors(pattern: FormPathPattern = '*') { // 1. 指定路径或全部子路径清理 hostUpdate(() => { graph.eachChildren('', pattern, field => { if (isField(field)) { field.setState(state => { state.ruleErrors = [] state.ruleWarnings = [] state.effectErrors = [] state.effectWarnings = [] }) } }) }) } async function reset({ selector = '*', forceClear = false, validate = true, clearInitialValue = false }: IFormResetOptions = {}): Promise<void | IFormValidateResult> { hostUpdate(() => { graph.eachChildren('', selector, (field: IField) => { ;(field as any).disabledValidate = true field.setState((state: IFieldState<FieldProps>) => { state.modified = false state.ruleErrors = [] state.ruleWarnings = [] state.effectErrors = [] state.effectWarnings = [] if (clearInitialValue) { state.initialValue = undefined } // forceClear仅对设置initialValues的情况下有意义 if (forceClear || !isValid(state.initialValue)) { if (isArr(state.value)) { state.value = [] } else if (!isObj(state.value)) { state.value = undefined } } else { const value = clone(state.initialValue) if (isArr(state.value)) { if (isArr(value)) { state.value = value } else { state.value = [] } } else if (isObj(state.value)) { if (isObj(value)) { state.value = value } else { state.value = {} } } else { state.value = value } } }) ;(field as any).disabledValidate = false }) }) if (isFn(options.onReset) && !state.state.unmounted) { options.onReset() } heart.publish(LifeCycleTypes.ON_FORM_RESET, state) let validateResult: void | IFormValidateResult if (validate) { validateResult = await formApi.validate(selector, { throwErrors: false }) } return validateResult } async function submit( onSubmit?: (values: IFormState['values']) => any | Promise<any> ): Promise<IFormSubmitResult> { // 重复提交,返回前一次的promise if (state.getState(state => state.submitting)) return env.submittingTask heart.publish(LifeCycleTypes.ON_FORM_SUBMIT_START, state) onSubmit = onSubmit || options.onSubmit state.setState(state => { state.submitting = true }) env.submittingTask = async () => { // 增加onFormSubmitValidateStart来明确submit引起的校验开始了 heart.publish(LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_START, state) await validate('', { throwErrors: false, hostRendering: true }) const validated: IFormValidateResult = state.getState(state => ({ errors: state.errors, warnings: state.warnings })) const { errors } = validated // 校验失败 if (errors.length) { // 由于校验失败导致submit退出 state.setState(state => { state.submitting = false }) // 增加onFormSubmitValidateFailed来明确结束submit的类型 heart.publish(LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_FAILED, state) heart.publish(LifeCycleTypes.ON_FORM_SUBMIT_END, state) if (isFn(options.onValidateFailed) && !state.state.unmounted) { options.onValidateFailed(validated) } throw errors } // 增加onFormSubmitValidateSucces来明确submit引起的校验最终的结果 heart.publish(LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_SUCCESS, state) heart.publish(LifeCycleTypes.ON_FORM_SUBMIT, state) let payload, values = state.getState(state => clone(state.values)) if (isFn(onSubmit) && !state.state.unmounted) { try { payload = await Promise.resolve(onSubmit(values)) heart.publish(LifeCycleTypes.ON_FORM_ON_SUBMIT_SUCCESS, payload) } catch (e) { heart.publish(LifeCycleTypes.ON_FORM_ON_SUBMIT_FAILED, e) new Promise(() => { throw e }) } } state.setState(state => { state.submitting = false }) heart.publish(LifeCycleTypes.ON_FORM_SUBMIT_END, state) return { values, validated, payload } } return env.submittingTask() } async function validate( path?: FormPathPattern, opts?: IFormExtendedValidateFieldOptions ): Promise<IFormValidateResult> { const { throwErrors = true, hostRendering } = opts || {} if (!state.getState(state => state.validating)) { state.setSourceState(state => { state.validating = true }) // 渲染优化 clearTimeout(env.validateTimer) env.validateTimer = setTimeout(() => { state.notify() }, 60) } heart.publish(LifeCycleTypes.ON_FORM_VALIDATE_START, state) if (graph.size > 100 && hostRendering) env.hostRendering = true const payload = await validator.validate(path, opts) clearTimeout(env.validateTimer) state.setState(state => { state.validating = false }) heart.publish(LifeCycleTypes.ON_FORM_VALIDATE_END, state) if (graph.size > 100 && hostRendering) { heart.publish(LifeCycleTypes.ON_FORM_HOST_RENDER, state) env.hostRendering = false } // 增加name透出真实路径,和0.x保持一致 const result = { errors: payload.errors.map(item => ({ ...item, name: getFieldState(item.path).name })), warnings: payload.warnings.map(item => ({ ...item, name: getFieldState(item.path).name })) } const { errors, warnings } = result // 打印warnings日志从submit挪到这里 if (warnings.length) { log.warn(warnings) } if (errors.length > 0) { if (throwErrors) { throw result } else { return result } } else { return result } } function setFormState( callback?: (state: IFormState) => any, silent?: boolean ) { state.setState(callback, silent) } function getFormState(callback?: (state: IFormState) => any) { return state.getState(callback) } function batchRunTaskQueue( field: IField | IVirtualField, nodePath: FormPath ) { for (let index = 0; index < env.taskQueue.length; index++) { const { pattern, callbacks } = env.taskQueue[index] let removed = false if (matchStrategy(pattern, nodePath)) { callbacks.forEach(callback => { field.setState(callback) }) if (!pattern.isWildMatchPattern && !pattern.isMatchPattern) { env.taskQueue.splice(index--, 1) removed = true } } if (!removed) { env.taskIndexes[pattern.toString()] = index } else { delete env.taskIndexes[pattern.toString()] } } } function pushTaskQueue(pattern: FormPath, callback: () => void) { const id = pattern.toString() const taskIndex = env.taskIndexes[id] if (isValid(taskIndex)) { if ( env.taskQueue[taskIndex] && !env.taskQueue[taskIndex].callbacks.some(fn => isEqual(fn, callback) ? fn === callback : false ) ) { env.taskQueue[taskIndex].callbacks.push(callback) } } else { env.taskIndexes[id] = env.taskQueue.length env.taskQueue.push({ pattern, callbacks: [callback] }) } } function setFieldState( path: FormPathPattern, callback?: (state: IFieldState<FieldProps>) => void, silent?: boolean ) { if (!isFn(callback)) return let matchCount = 0 const pattern = FormPath.getPath(path) graph.select(pattern, field => { field.setState(callback, silent) matchCount++ }) if (matchCount === 0 || pattern.isWildMatchPattern) { pushTaskQueue(pattern, callback) } } function setFieldValue(path: FormPathPattern, value?: any, silent?: boolean) { setFieldState( path, state => { state.value = value }, silent ) } function getFieldValue(path?: FormPathPattern) { return getFieldState(path, state => { return state.value }) } function setFieldInitialValue( path?: FormPathPattern, value?: any, silent?: boolean ) { setFieldState( path, state => { state.initialValue = value }, silent ) } function getFieldInitialValue(path?: FormPathPattern) { return getFieldState(path, state => { return state.initialValue }) } function getFieldState( path: FormPathPattern, callback?: (state: IFieldState<FieldProps>) => any ) { const field = graph.select(path) return field && field.getState(callback) } function getFormGraph() { return graph.map(node => { return node.getState() }) } function setFormGraph(nodes: {}) { each( nodes, ( node: IFieldState<FieldProps> | IVirtualFieldState<VirtualFieldProps>, key ) => { let nodeState: any if (graph.exist(key)) { nodeState = graph.get(key) nodeState.setSourceState(state => { Object.assign(state, node) }) } else { if (node.displayName === 'VirtualFieldState') { nodeState = registerVirtualField({ path: key }) nodeState.setSourceState(state => { Object.assign(state, node) }) } else if (node.displayName === 'FieldState') { nodeState = registerField({ path: key }) nodeState.setSourceState(state => { Object.assign(state, node) }) } } if (nodeState) { nodeState.notify(state.getState()) } } ) } function matchStrategy(pattern: FormPathPattern, nodePath: FormPathPattern) { const matchPattern = FormPath.parse(pattern) const node = graph.get(nodePath) if (!node) return false return node.getSourceState(state => matchPattern.matchAliasGroup(state.name, state.path) ) } //在subscribe中必须同步使用,否则会监听不到变化 function hasChanged(target: any, path: FormPathPattern): boolean { if (env.publishing[target ? target.path : ''] === false) { throw new Error( 'The watch function must be used synchronously in the subscribe callback.' ) } if (isFormState(target)) { return state.hasChanged(path) } else if (isFieldState(target) || isVirtualFieldState(target)) { const node = graph.get(target.path) return node && node.hasChanged(path) } else { throw new Error( 'Illegal parameter,You must pass the correct state object(FormState/FieldState/VirtualFieldState).' ) } } function isHostRendering() { return env.hostRendering } function hostUpdate(callback?: () => any): any { if (isFn(callback)) { if (graph.size > 100) env.hostRendering = true const result = callback() if (graph.size > 100) { heart.publish(LifeCycleTypes.ON_FORM_HOST_RENDER, state) env.hostRendering = false } return result } } const state = new FormState(options) const validator = new FormValidator({ ...options, matchStrategy }) const graph: FormGraph = new FormGraph({ matchStrategy }) const formApi = { submit, reset, hasChanged, clearErrors, validate, setFormState, getFormState, setFieldState, getFieldState, registerField, registerVirtualField, createMutators, getFormGraph, setFormGraph, setFieldValue, unsafe_do_not_use_transform_data_path: transformDataPath, //eslint-disable-line getFieldValue, setFieldInitialValue, getFieldInitialValue, isHostRendering, hostUpdate, subscribe: (callback?: FormHeartSubscriber) => { return heart.subscribe(callback) }, unsubscribe: (id: number) => { heart.unsubscribe(id) }, notify: <T>(type: string, payload: T) => { heart.publish(type, payload) } } const heart = new FormHeart({ ...options, context: formApi, beforeNotify: payload => { env.publishing[payload.path || ''] = true }, afterNotify: payload => { env.publishing[payload.path || ''] = false } }) const env = { validateTimer: null, syncFormStateTimer: null, onChangeTimer: null, graphChangeTimer: null, hostRendering: false, publishing: {}, taskQueue: [], taskIndexes: {}, removeNodes: {}, lastShownStates: {}, submittingTask: undefined } heart.publish(LifeCycleTypes.ON_FORM_WILL_INIT, state) state.subscription = { notify: onFormChange } graph.appendNode('', state) state.setState((state: IFormState) => { state.initialized = true }) graph.subscribe(onGraphChange) return formApi } export const registerValidationFormats = FormValidator.registerFormats export const registerValidationRules = FormValidator.registerRules export const registerValidationMTEngine = FormValidator.registerMTEngine export { setValidationLanguage, setValidationLocale, BigData, FormPath, FormPathPattern, FormGraph } export default createForm