From d91ebfffd6f444fbe8e7d2bac843a5e8a6c54913 Mon Sep 17 00:00:00 2001 From: janrywang Date: Tue, 14 Sep 2021 20:43:56 +0800 Subject: [PATCH] fix(react): fix x-component-props is not reactive --- packages/core/src/shared/internals.ts | 15 ++-- packages/json-schema/src/compiler.ts | 6 +- packages/json-schema/src/shared.ts | 15 ++-- .../src/__tests__/schema.markup.spec.tsx | 76 +++++++++++++++++++ .../react/src/components/ReactiveField.tsx | 14 +--- packages/reactive/src/externals.ts | 46 ++++------- packages/vue/src/components/ReactiveField.ts | 6 +- 7 files changed, 116 insertions(+), 62 deletions(-) diff --git a/packages/core/src/shared/internals.ts b/packages/core/src/shared/internals.ts index 8e141e48192..fcf6ad4d1e7 100644 --- a/packages/core/src/shared/internals.ts +++ b/packages/core/src/shared/internals.ts @@ -11,6 +11,7 @@ import { toArr, isNumberLike, shallowClone, + clone, isEqual, } from '@formily/shared' import { @@ -44,7 +45,7 @@ import { RESPONSE_REQUEST_DURATION, ReservedProperties, GlobalState, - NumberIndexReg + NumberIndexReg, } from './constants' export const isHTMLInputEvent = (event: any, stopPropagation = true) => { @@ -711,11 +712,11 @@ export const applyValuesPatch = ( path: Array, source: any ) => { - const merge = (path: Array, source: any) => { + const update = (path: Array, source: any) => { if (path.length) { - form.setValuesIn(path, toJS(source)) + form.setValuesIn(path, clone(source)) } else { - Object.assign(form.values, toJS(source)) + Object.assign(form.values, clone(source)) } } @@ -724,7 +725,7 @@ export const applyValuesPatch = ( const targetField = form.query(path).take() if (isEmpty(targetValue)) { if (isEmpty(source)) return - merge(path, source) + update(path, source) } else { const arrA = isArr(targetValue) const arrB = isArr(source) @@ -738,10 +739,10 @@ export const applyValuesPatch = ( } else { if (targetField) { if (!isVoidField(targetField) && !targetField.modified) { - merge(path, source) + update(path, source) } } else { - merge(path, source) + update(path, source) } } } diff --git a/packages/json-schema/src/compiler.ts b/packages/json-schema/src/compiler.ts index c192baa9cb8..eb85da7172f 100644 --- a/packages/json-schema/src/compiler.ts +++ b/packages/json-schema/src/compiler.ts @@ -95,10 +95,12 @@ export const patchCompile = ( sourceState: any, scope: any ) => { - traverse(sourceState, (value, path) => { + traverse(sourceState, (value, pattern) => { + const path = FormPath.parse(pattern) const compiled = compile(value, scope) + const key = path.segments[0] if (compiled === undefined) return - if (hasOwnProperty.call(targetState, path[0])) { + if (hasOwnProperty.call(targetState, key)) { untracked(() => FormPath.setIn(targetState, path, compiled)) } }) diff --git a/packages/json-schema/src/shared.ts b/packages/json-schema/src/shared.ts index 29606116a9d..2e8fb87c778 100644 --- a/packages/json-schema/src/shared.ts +++ b/packages/json-schema/src/shared.ts @@ -102,22 +102,25 @@ export const createDataSource = (source: any[]) => { export const patchStateFormSchema = ( targetState: any, - path: any[], + pattern: any[], compiled: any ) => { untracked(() => { - const isEnum = path[0] === 'enum' && isArr(compiled) - const schemaMapKey = SchemaStateMap[path[0]] + const path = FormPath.parse(pattern) + const segments = path.segments + const key = segments[0] + const isEnum = key === 'enum' && isArr(compiled) + const schemaMapKey = SchemaStateMap[key] if (schemaMapKey) { FormPath.setIn( targetState, - [schemaMapKey].concat(path.slice(1)), + [schemaMapKey].concat(segments.slice(1)), isEnum ? createDataSource(compiled) : compiled ) } else { - const isValidatorKey = SchemaValidatorKeys[path[0]] + const isValidatorKey = SchemaValidatorKeys[key] if (isValidatorKey) { - targetState['setValidatorRule'](path[0], compiled) + targetState['setValidatorRule'](key, compiled) } } }) diff --git a/packages/react/src/__tests__/schema.markup.spec.tsx b/packages/react/src/__tests__/schema.markup.spec.tsx index 186b3351e32..0d0e2398e0f 100644 --- a/packages/react/src/__tests__/schema.markup.spec.tsx +++ b/packages/react/src/__tests__/schema.markup.spec.tsx @@ -650,3 +650,79 @@ test('expression x-value', async () => { expect(queryByText('100')).not.toBeNull() }) }) + +test('nested update component props with expression', async () => { + const form = createForm({ + values: { + aaa: 'xxx', + }, + }) + const SchemaField = createSchemaField({ + components: { + Text: (props) =>
{props.aa?.bb?.cc}
, + }, + }) + + const { queryByText } = render( + + + + + + + ) + await waitFor(() => { + expect(queryByText('xxx')).not.toBeNull() + }) + act(() => { + form.values.aaa = '10' + }) + await waitFor(() => { + expect(queryByText('10')).not.toBeNull() + }) +}) + +test('nested update component props with x-reactions', async () => { + const form = createForm({ + values: { + aaa: 'xxx', + }, + }) + const SchemaField = createSchemaField({ + components: { + Text: (props) =>
{props.aa?.bb?.cc}
, + }, + }) + + const { queryByText } = render( + + + + + + + ) + await waitFor(() => { + expect(queryByText('xxx')).not.toBeNull() + }) + act(() => { + form.values.aaa = '10' + }) + await waitFor(() => { + expect(queryByText('10')).not.toBeNull() + }) +}) diff --git a/packages/react/src/components/ReactiveField.tsx b/packages/react/src/components/ReactiveField.tsx index 15a2baf8f14..3508fc1dc69 100644 --- a/packages/react/src/components/ReactiveField.tsx +++ b/packages/react/src/components/ReactiveField.tsx @@ -1,4 +1,5 @@ import React, { Fragment, useContext } from 'react' +import { toJS } from '@formily/reactive' import { observer } from '@formily/reactive-react' import { isFn, FormPath } from '@formily/shared' import { isVoidField, GeneralField, Form } from '@formily/core' @@ -45,12 +46,7 @@ const ReactiveInternal: React.FC = (props) => { return React.createElement( finalComponent, - { - ...field.decorator[1], - style: { - ...field.decorator[1]?.style, - }, - }, + toJS(field.decorator[1]), children ) } @@ -85,16 +81,12 @@ const ReactiveInternal: React.FC = (props) => { const finalComponent = FormPath.getIn(options?.components, field.component[0]) ?? field.component[0] - return React.createElement( finalComponent, { disabled, readOnly, - ...field.component[1], - style: { - ...field.component[1]?.style, - }, + ...toJS(field.component[1]), value, onChange, onFocus, diff --git a/packages/reactive/src/externals.ts b/packages/reactive/src/externals.ts index dcdf5d318c3..23b0d6fa271 100644 --- a/packages/reactive/src/externals.ts +++ b/packages/reactive/src/externals.ts @@ -84,40 +84,21 @@ export const raw = (target: T): T => ProxyRaw.get(target as any) export const toJS = (values: T): T => { const visited = new WeakSet() const _toJS: typeof toJS = (values: any) => { + if (visited.has(values)) { + return values + } if (isArr(values)) { - if (visited.has(values)) { - return values - } - const originValues = values - if (ProxyRaw.has(values as any)) { - values = ProxyRaw.get(values as any) + if (isObservable(values)) { + visited.add(values) + const res: any = [] + values.forEach((item: any) => { + res.push(_toJS(item)) + }) + return res } - visited.add(originValues) - const res: any = [] - values.forEach((item: any) => { - res.push(_toJS(item)) - }) - return res } else if (isPlainObj(values)) { - if (visited.has(values)) { - return values - } - const originValues = values - if (ProxyRaw.has(values as any)) { - values = ProxyRaw.get(values as any) - } - if ('$$typeof' in values && '_owner' in values) { - return values - } else if (values['_isAMomentObject']) { - return values - } else if (values['_isJSONSchemaObject']) { - return values - } else if (isFn(values['toJS'])) { - return values['toJS']() - } else if (isFn(values['toJSON'])) { - return values['toJSON']() - } else { - visited.add(originValues) + if (isObservable(values)) { + visited.add(values) const res: any = {} for (const key in values) { if (hasOwnProperty.call(values, key)) { @@ -126,9 +107,8 @@ export const toJS = (values: T): T => { } return res } - } else { - return values } + return values } return _toJS(values) diff --git a/packages/vue/src/components/ReactiveField.ts b/packages/vue/src/components/ReactiveField.ts index 36592ebcca7..f42ccc499ea 100644 --- a/packages/vue/src/components/ReactiveField.ts +++ b/packages/vue/src/components/ReactiveField.ts @@ -1,6 +1,6 @@ import { defineComponent, inject, ref } from 'vue-demi' import { isVoidField } from '@formily/core' -import { clone, FormPath } from '@formily/shared' +import { FormPath } from '@formily/shared' import { observer } from '@formily/reactive-vue' import { toJS } from '@formily/reactive' import { SchemaOptionsSymbol } from '../shared' @@ -95,7 +95,7 @@ export default observer( optionsRef.value?.components, field.decorator[0] ) ?? field.decorator[0]) as VueComponent - const decoratorData = clone(field.decorator[1]) || {} + const decoratorData = toJS(field.decorator[1]) || {} const style = decoratorData?.style delete decoratorData.style return { @@ -133,7 +133,7 @@ export default observer( optionsRef.value?.components, field.component[0] ) ?? field.component[0]) as VueComponent - const originData = clone(field.component[1]) || {} + const originData = toJS(field.component[1]) || {} const events = {} as Record const originChange = originData['@change'] || originData['onChange'] const originFocus = originData['@focus'] || originData['onFocus']