From 0adf07ab91f2844555511036c46729464022a723 Mon Sep 17 00:00:00 2001 From: Janry Date: Wed, 3 Nov 2021 11:10:26 +0800 Subject: [PATCH] fix(core): fix default is not work when name is length (#2387) * refactor(core): revert field unmount to skip validate * chore(core): improve code * fix(core): fix default is not work when name is length --- packages/core/src/__tests__/effects.spec.ts | 2 +- packages/core/src/__tests__/field.spec.ts | 22 +++++ packages/core/src/__tests__/internals.spec.ts | 8 +- packages/core/src/effects/onFieldEffects.ts | 2 +- packages/core/src/effects/onFormEffects.ts | 2 +- packages/core/src/models/Form.ts | 2 +- packages/core/src/shared/constants.ts | 28 +++++- .../src/shared/{effectbox.ts => effective.ts} | 0 packages/core/src/shared/externals.ts | 2 +- packages/core/src/shared/internals.ts | 95 ++++++++----------- packages/reactive/docs/api/observe.md | 1 + packages/reactive/docs/api/observe.zh-CN.md | 1 + .../reactive/src/__tests__/observable.spec.ts | 14 +++ packages/reactive/src/annotations/box.ts | 2 +- packages/reactive/src/annotations/computed.ts | 2 +- packages/reactive/src/annotations/ref.ts | 2 +- packages/reactive/src/environment.ts | 2 +- packages/reactive/src/externals.ts | 12 +++ packages/reactive/src/internals.ts | 2 +- packages/reactive/src/model.ts | 2 +- packages/reactive/src/observe.ts | 2 +- .../reactive/src/{datatree.ts => tree.ts} | 2 + packages/reactive/src/types.ts | 2 +- 23 files changed, 137 insertions(+), 72 deletions(-) rename packages/core/src/shared/{effectbox.ts => effective.ts} (100%) rename packages/reactive/src/{datatree.ts => tree.ts} (96%) diff --git a/packages/core/src/__tests__/effects.spec.ts b/packages/core/src/__tests__/effects.spec.ts index a874be01e43..c33b2fcbf95 100644 --- a/packages/core/src/__tests__/effects.spec.ts +++ b/packages/core/src/__tests__/effects.spec.ts @@ -36,7 +36,7 @@ import { onFormValuesChange, isVoidField, } from '../' -import { runEffects } from '../shared/effectbox' +import { runEffects } from '../shared/effective' import { attach, sleep } from './shared' test('onFormInit/onFormMount/onFormUnmount', () => { diff --git a/packages/core/src/__tests__/field.spec.ts b/packages/core/src/__tests__/field.spec.ts index 81ca74aa842..70a07996779 100644 --- a/packages/core/src/__tests__/field.spec.ts +++ b/packages/core/src/__tests__/field.spec.ts @@ -1638,3 +1638,25 @@ test('reactions initialValue and value', () => { ) expect(form.values.aa.input).toEqual('111') }) + +test('field name is length in initialize', () => { + const form = attach(createForm()) + const field = attach( + form.createField({ + name: 'length', + initialValue: 123, + }) + ) + expect(field.value).toEqual(123) +}) + +test('field name is length in dynamic assign', () => { + const form = attach(createForm()) + const field = attach( + form.createField({ + name: 'length', + }) + ) + field.initialValue = 123 + expect(field.value).toEqual(123) +}) diff --git a/packages/core/src/__tests__/internals.spec.ts b/packages/core/src/__tests__/internals.spec.ts index a65c7e6977e..023f0a60556 100644 --- a/packages/core/src/__tests__/internals.spec.ts +++ b/packages/core/src/__tests__/internals.spec.ts @@ -2,7 +2,7 @@ import { getValuesFromEvent, matchFeedback, patchFieldStates, - setModelState, + serialize, isHTMLInputEvent, } from '../shared/internals' @@ -28,10 +28,10 @@ test('patchFieldStates', () => { expect(fields).toEqual({}) }) -test('setModelState', () => { - expect(setModelState(null, null)).toBeUndefined() +test('serialize', () => { + expect(serialize(null, null)).toBeUndefined() expect( - setModelState( + serialize( {}, { parent: null, diff --git a/packages/core/src/effects/onFieldEffects.ts b/packages/core/src/effects/onFieldEffects.ts index 94b4c5ff77d..c9030e5a6cb 100644 --- a/packages/core/src/effects/onFieldEffects.ts +++ b/packages/core/src/effects/onFieldEffects.ts @@ -8,7 +8,7 @@ import { DataField, IFieldState, } from '../types' -import { createEffectHook, useEffectForm } from '../shared/effectbox' +import { createEffectHook, useEffectForm } from '../shared/effective' import { onFormUnmount } from './onFormEffects' function createFieldEffect( diff --git a/packages/core/src/effects/onFormEffects.ts b/packages/core/src/effects/onFormEffects.ts index 8debfb84b25..1d01cd512f8 100644 --- a/packages/core/src/effects/onFormEffects.ts +++ b/packages/core/src/effects/onFormEffects.ts @@ -1,7 +1,7 @@ import { autorun, batch } from '@formily/reactive' import { Form } from '../models' import { LifeCycleTypes } from '../types' -import { createEffectHook } from '../shared/effectbox' +import { createEffectHook } from '../shared/effective' function createFormEffect(type: LifeCycleTypes) { return createEffectHook( diff --git a/packages/core/src/models/Form.ts b/packages/core/src/models/Form.ts index 304f13d5c30..226192ddb2d 100644 --- a/packages/core/src/models/Form.ts +++ b/packages/core/src/models/Form.ts @@ -50,7 +50,7 @@ import { getValidFormValues, } from '../shared/internals' import { isVoidField } from '../shared/checkers' -import { runEffects } from '../shared/effectbox' +import { runEffects } from '../shared/effective' import { ArrayField } from './ArrayField' import { ObjectField } from './ObjectField' import { VoidField } from './VoidField' diff --git a/packages/core/src/shared/constants.ts b/packages/core/src/shared/constants.ts index 03901c340ef..5d5f9ab150c 100644 --- a/packages/core/src/shared/constants.ts +++ b/packages/core/src/shared/constants.ts @@ -10,13 +10,39 @@ export const ReservedProperties = { indexes: true, fields: true, lifecycles: true, - originValues: true, componentType: true, componentProps: true, decoratorType: true, decoratorProps: true, } +export const ReadOnlyProperties = { + address: true, + path: true, + valid: true, + invalid: true, + selfValid: true, + selfInvalid: true, + errors: true, + successes: true, + warnings: true, + validateStatus: true, +} + +const SELF_DISPLAY = 'selfDisplay' +const SELF_PATTERN = 'selfPattern' + +export const MutuallyExclusiveProperties = { + pattern: SELF_PATTERN, + editable: SELF_PATTERN, + readOnly: SELF_PATTERN, + readPretty: SELF_PATTERN, + disabled: SELF_PATTERN, + display: SELF_DISPLAY, + hidden: SELF_DISPLAY, + visible: SELF_DISPLAY, +} + export const RESPONSE_REQUEST_DURATION = 100 export const GlobalState = { diff --git a/packages/core/src/shared/effectbox.ts b/packages/core/src/shared/effective.ts similarity index 100% rename from packages/core/src/shared/effectbox.ts rename to packages/core/src/shared/effective.ts diff --git a/packages/core/src/shared/externals.ts b/packages/core/src/shared/externals.ts index b7106564d37..bd554f5bcb4 100644 --- a/packages/core/src/shared/externals.ts +++ b/packages/core/src/shared/externals.ts @@ -13,7 +13,7 @@ import { createEffectHook, createEffectContext, useEffectForm, -} from './effectbox' +} from './effective' import { isArrayField, isArrayFieldState, diff --git a/packages/core/src/shared/internals.ts b/packages/core/src/shared/internals.ts index 4e77abd90c5..d418fd24792 100644 --- a/packages/core/src/shared/internals.ts +++ b/packages/core/src/shared/internals.ts @@ -20,6 +20,7 @@ import { import { autorun, batch, + contains, toJS, isObservable, DataChange, @@ -48,8 +49,10 @@ import { import { RESPONSE_REQUEST_DURATION, ReservedProperties, + MutuallyExclusiveProperties, NumberIndexReg, GlobalState, + ReadOnlyProperties, } from './constants' export const isHTMLInputEvent = (event: any, stopPropagation = true) => { @@ -274,6 +277,8 @@ export const validateToFeedbacks = async ( return results } +const hasOwnProperty = Object.prototype.hasOwnProperty + export const setValidatorRule = (field: Field, name: string, value: any) => { if (!isValid(value)) return const hasRule = parseValidatorDescriptions(field.validator).some( @@ -285,7 +290,7 @@ export const setValidatorRule = (field: Field, name: string, value: any) => { if (hasRule) { if (isArr(field.validator)) { field.validator = field.validator.map((desc: any) => { - if (Object.prototype.hasOwnProperty.call(desc, name)) { + if (hasOwnProperty.call(desc, name)) { desc[name] = value return desc } @@ -588,76 +593,52 @@ export const subscribeUpdate = ( } } -export const setModelState = (model: any, setter: any) => { +export const serialize = (model: any, setter: any) => { if (!model) return - const isSkipProperty = (key: string) => { - if (key === 'address' || key === 'path') return true - if (key === 'valid' || key === 'invalid') return true - if (key === 'componentType' || key === 'componentProps') return true - if (key === 'decoratorType' || key === 'decoratorProps') return true - if (key === 'validateStatus') return true - if (key === 'errors' || key === 'warnings' || key === 'successes') - return true - if ( - (key === 'display' || key === 'visible' || key === 'hidden') && - 'selfDisplay' in setter && - !isValid(setter.selfDisplay) - ) { - return true - } - if ( - (key === 'pattern' || - key === 'editable' || - key === 'disabled' || - key === 'readOnly' || - key === 'readPretty') && - 'selfPattern' in setter && - !isValid(setter.selfPattern) - ) { - return true - } - return false - } if (isFn(setter)) { setter(model) + return model } else { - Object.keys(setter || {}).forEach((key: string) => { - const value = setter[key] + each(setter, (value, key) => { if (isFn(value)) return - if (ReservedProperties[key]) return - if (isSkipProperty(key)) return + if (ReadOnlyProperties[key] || ReservedProperties[key]) return + const MutuallyExclusiveKey = MutuallyExclusiveProperties[key] + if ( + MutuallyExclusiveKey && + hasOwnProperty.call(setter, MutuallyExclusiveKey) && + !isValid(setter[MutuallyExclusiveKey]) + ) + return model[key] = value }) } return model } -export const getModelState = (model: any, getter?: any) => { +export const deserialize = (model: any, getter?: any) => { if (isFn(getter)) { return getter(model) } else { - return Object.keys(model || {}).reduce((buf, key: string) => { - const value = model[key] - if (isFn(value)) { - return buf - } - if (ReservedProperties[key]) return buf + const results = {} + each(model, (value, key) => { + if (isFn(value)) return + if (ReservedProperties[key]) return if (key === 'address' || key === 'path') { - buf[key] = value.toString() - return buf + results[key] = value.toString() + return } - buf[key] = toJS(value) - return buf - }, {}) + results[key] = toJS(value) + }) + return results } } export const createStateSetter = (model: any) => { - return batch.bound((state?: any) => setModelState(model, state)) + return batch.bound((setter?: any) => serialize(model, setter)) } export const createStateGetter = (model: any) => { - return (getter?: any) => getModelState(model, getter) + return (getter?: any) => deserialize(model, getter) } export const createBatchStateSetter = (form: Form) => { @@ -701,8 +682,11 @@ export const triggerFormInitialValuesChange = ( change: DataChange ) => { const path = change.path - if (path[path.length - 1] === 'length') return - if (path[0] === 'initialValues') { + if (Array.isArray(change.object) && change.key === 'length') return + if ( + contains(form.initialValues, change.object) || + contains(form.initialValues, change.value) + ) { if (change.type === 'add' || change.type === 'set') { patchFormValues(form, path.slice(1), change.value) } @@ -713,9 +697,12 @@ export const triggerFormInitialValuesChange = ( } export const triggerFormValuesChange = (form: Form, change: DataChange) => { - const path = change.path - if (path[path.length - 1] === 'length') return - if (path[0] === 'values' && form.initialized) { + if (Array.isArray(change.object) && change.key === 'length') return + if ( + (contains(form.values, change.object) || + contains(form.values, change.value)) && + form.initialized + ) { form.notify(LifeCycleTypes.ON_FORM_VALUES_CHANGE) } } @@ -997,7 +984,7 @@ export const getValidFormValues = (values: any) => { } export const getValidFieldDefaultValue = (value: any, initialValue: any) => { - if (allowAssignDefaultValue(value, initialValue)) return initialValue + if (allowAssignDefaultValue(value, initialValue)) return clone(initialValue) return value } diff --git a/packages/reactive/docs/api/observe.md b/packages/reactive/docs/api/observe.md index d160147de67..f58f68e3287 100644 --- a/packages/reactive/docs/api/observe.md +++ b/packages/reactive/docs/api/observe.md @@ -27,6 +27,7 @@ type OperationType = interface IChange { key?: PropertyKey path?: ObservablePath + object?: object value?: any oldValue?: any type?: OperationType diff --git a/packages/reactive/docs/api/observe.zh-CN.md b/packages/reactive/docs/api/observe.zh-CN.md index 0b0a0b0b775..a9eb0f4bb6e 100644 --- a/packages/reactive/docs/api/observe.zh-CN.md +++ b/packages/reactive/docs/api/observe.zh-CN.md @@ -27,6 +27,7 @@ type OperationType = interface IChange { key?: PropertyKey path?: ObservablePath + object?: object value?: any oldValue?: any type?: OperationType diff --git a/packages/reactive/src/__tests__/observable.spec.ts b/packages/reactive/src/__tests__/observable.spec.ts index eebbd8031c9..b266194bc69 100644 --- a/packages/reactive/src/__tests__/observable.spec.ts +++ b/packages/reactive/src/__tests__/observable.spec.ts @@ -1,7 +1,21 @@ import { observable } from '../' +import { contains } from '../externals' test('array mutation', () => { const arr = observable([1, 2, 3, 4]) arr.splice(2, 1) expect(arr).toEqual([1, 2, 4]) }) + +test('observable contains', () => { + const element = { aa: 123 } + const other = { bb: 321 } + const arr = observable([element, 2, 3, 4]) + const obj = observable({}) + expect(contains(arr, arr[0])).toBeTruthy() + expect(contains(obj, obj.other)).toBeFalsy() + obj.other = other + obj.arr = arr + expect(contains(obj, obj.other)).toBeTruthy() + expect(contains(obj, obj.arr)).toBeFalsy() +}) diff --git a/packages/reactive/src/annotations/box.ts b/packages/reactive/src/annotations/box.ts index 87abf3d38e6..741a0e20834 100644 --- a/packages/reactive/src/annotations/box.ts +++ b/packages/reactive/src/annotations/box.ts @@ -1,6 +1,6 @@ import { ProxyRaw, RawProxy } from '../environment' import { createAnnotation } from '../internals' -import { buildDataTree } from '../datatree' +import { buildDataTree } from '../tree' import { bindTargetKeyWithCurrentReaction, runReactionsFromTargetKey, diff --git a/packages/reactive/src/annotations/computed.ts b/packages/reactive/src/annotations/computed.ts index 15ff00e96f5..a58c2a273d9 100644 --- a/packages/reactive/src/annotations/computed.ts +++ b/packages/reactive/src/annotations/computed.ts @@ -1,6 +1,6 @@ import { ProxyRaw, RawProxy, ReactionStack } from '../environment' import { createAnnotation } from '../internals' -import { buildDataTree } from '../datatree' +import { buildDataTree } from '../tree' import { bindTargetKeyWithCurrentReaction, runReactionsFromTargetKey, diff --git a/packages/reactive/src/annotations/ref.ts b/packages/reactive/src/annotations/ref.ts index 28e9cf13b07..213789e8581 100644 --- a/packages/reactive/src/annotations/ref.ts +++ b/packages/reactive/src/annotations/ref.ts @@ -1,6 +1,6 @@ import { ProxyRaw, RawProxy } from '../environment' import { createAnnotation } from '../internals' -import { buildDataTree } from '../datatree' +import { buildDataTree } from '../tree' import { bindTargetKeyWithCurrentReaction, runReactionsFromTargetKey, diff --git a/packages/reactive/src/environment.ts b/packages/reactive/src/environment.ts index 50083e04912..5c5f61efc6b 100644 --- a/packages/reactive/src/environment.ts +++ b/packages/reactive/src/environment.ts @@ -1,6 +1,6 @@ import { ObservableListener, Reaction, ReactionsMap } from './types' import { ArraySet } from './array' -import { DataNode } from './datatree' +import { DataNode } from './tree' export const ProxyRaw = new WeakMap() export const RawProxy = new WeakMap() diff --git a/packages/reactive/src/externals.ts b/packages/reactive/src/externals.ts index eb092a971e4..aaa8fd46bfb 100644 --- a/packages/reactive/src/externals.ts +++ b/packages/reactive/src/externals.ts @@ -12,6 +12,7 @@ import { ProxyRaw, MakeObservableSymbol, DependencyCollected, + RawNode, } from './environment' import { Annotation } from './types' @@ -117,6 +118,17 @@ export const toJS = (values: T): T => { return _toJS(values) } +export const contains = (target: any, property: any) => { + const targetRaw = ProxyRaw.get(target) || target + const propertyRaw = ProxyRaw.get(property) || property + if (targetRaw === propertyRaw) return true + const targetNode = RawNode.get(targetRaw) + const propertyNode = RawNode.get(propertyRaw) + if (!targetNode) return false + if (!propertyNode) return false + return targetNode.contains(propertyNode) +} + export const hasCollected = (callback?: () => void) => { DependencyCollected.value = false callback?.() diff --git a/packages/reactive/src/internals.ts b/packages/reactive/src/internals.ts index 24a472276e7..47d25a080db 100644 --- a/packages/reactive/src/internals.ts +++ b/packages/reactive/src/internals.ts @@ -7,7 +7,7 @@ import { RawNode, } from './environment' import { baseHandlers, collectionHandlers } from './handlers' -import { buildDataTree } from './datatree' +import { buildDataTree } from './tree' import { isSupportObservable } from './externals' import { PropertyKey, IVisitor, BoundaryFunction } from './types' diff --git a/packages/reactive/src/model.ts b/packages/reactive/src/model.ts index ecad2c285ec..d9b9a02bdc8 100644 --- a/packages/reactive/src/model.ts +++ b/packages/reactive/src/model.ts @@ -1,5 +1,5 @@ import { isFn } from './checkers' -import { buildDataTree } from './datatree' +import { buildDataTree } from './tree' import { observable } from './observable' import { getObservableMaker } from './internals' import { isObservable, isAnnotation, isSupportObservable } from './externals' diff --git a/packages/reactive/src/observe.ts b/packages/reactive/src/observe.ts index bbe38ea39ca..cd2af1d31f9 100644 --- a/packages/reactive/src/observe.ts +++ b/packages/reactive/src/observe.ts @@ -1,7 +1,7 @@ import { IOperation } from './types' import { RawNode, ProxyRaw, ObserverListeners } from './environment' import { isFn } from './checkers' -import { DataChange } from './datatree' +import { DataChange } from './tree' export const observe = ( target: object, diff --git a/packages/reactive/src/datatree.ts b/packages/reactive/src/tree.ts similarity index 96% rename from packages/reactive/src/datatree.ts rename to packages/reactive/src/tree.ts index d0492bdb493..4f0d66b51c2 100644 --- a/packages/reactive/src/datatree.ts +++ b/packages/reactive/src/tree.ts @@ -4,12 +4,14 @@ import { ObservablePath, PropertyKey, IOperation } from './types' export class DataChange { path: ObservablePath key: PropertyKey + object: object type: string value: any oldValue: any constructor(operation: IOperation, node: DataNode) { this.key = operation.key this.type = operation.type + this.object = operation.target this.value = operation.value this.oldValue = operation.oldValue this.path = node.path.concat(operation.key) diff --git a/packages/reactive/src/types.ts b/packages/reactive/src/types.ts index 0a6be573f85..f0fcca41185 100644 --- a/packages/reactive/src/types.ts +++ b/packages/reactive/src/types.ts @@ -1,6 +1,6 @@ import { ArraySet } from './array' -export * from './datatree' +export * from './tree' export type PropertyKey = string | number | symbol