From 9440ac4511cf6c964edb5044f68d716798b9582f Mon Sep 17 00:00:00 2001 From: Janry Date: Tue, 26 Nov 2019 11:59:59 +0800 Subject: [PATCH] feat(@uform/react): support useFieldState/useFormState (#433) * feat(@uform/antd/next): improve form step * feat(@uform/react): support useFieldState/useFormState --- docs/Examples/antd/Layout.md | 16 ++++ docs/Examples/next/Layout.md | 81 ++++++++++++++----- packages/antd/src/components/FormStep.tsx | 66 +++++++-------- packages/next/src/components/FormStep.tsx | 71 +++++++--------- packages/react/src/components/Field.tsx | 26 ++++-- .../react/src/components/VirtualField.tsx | 27 ++++--- packages/react/src/context.ts | 3 + packages/react/src/hooks/useField.ts | 1 + packages/react/src/hooks/useFieldState.ts | 24 ++++++ packages/react/src/hooks/useFormState.ts | 36 +++++++++ packages/react/src/hooks/useVirtualField.ts | 1 + packages/react/src/index.ts | 2 + packages/react/src/types.ts | 6 +- 13 files changed, 240 insertions(+), 120 deletions(-) create mode 100644 packages/react/src/hooks/useFieldState.ts create mode 100644 packages/react/src/hooks/useFormState.ts diff --git a/docs/Examples/antd/Layout.md b/docs/Examples/antd/Layout.md index 0fff0803570..659b47a9b02 100644 --- a/docs/Examples/antd/Layout.md +++ b/docs/Examples/antd/Layout.md @@ -297,6 +297,8 @@ const { onFormInit$ } = FormEffectHooks const actions = createFormActions() +let cache = {} + export default () => ( { @@ -340,6 +342,20 @@ export default () => ( + + ) diff --git a/docs/Examples/next/Layout.md b/docs/Examples/next/Layout.md index 6a7aef7ccac..864d0612f22 100644 --- a/docs/Examples/next/Layout.md +++ b/docs/Examples/next/Layout.md @@ -171,33 +171,55 @@ const App = () => { - - ​ - ​​ - - - - ​ - ​​ - - + + ​ + ​​ + + + + ​ + ​​ + + - - - - + + + + - ​ - + ​提交 @@ -258,7 +280,6 @@ ReactDOM.render(, document.getElementById('root')) ## 分步表单 - ```jsx import { SchemaForm, @@ -284,6 +305,8 @@ const { onFormInit$ } = FormEffectHooks const actions = createFormActions() +let cache = {} + export default () => ( { @@ -305,9 +328,9 @@ export default () => ( @@ -327,6 +350,20 @@ export default () => ( + + ) diff --git a/packages/antd/src/components/FormStep.tsx b/packages/antd/src/components/FormStep.tsx index e38fe19a5f2..e1c5f058af7 100644 --- a/packages/antd/src/components/FormStep.tsx +++ b/packages/antd/src/components/FormStep.tsx @@ -3,7 +3,8 @@ import { createControllerBox, ISchemaVirtualFieldComponentProps, createEffectHook, - useFormEffects + useFormEffects, + useFieldState } from '@uform/react-schema-renderer' import { toArr } from '@uform/shared' import { Steps } from 'antd' @@ -30,13 +31,10 @@ type StepComponentExtendsProps = StateMap export const FormStep: React.FC & StepComponentExtendsProps = createControllerBox( 'step', - ({ - form, - path, - schema, - current, - children - }: ISchemaVirtualFieldComponentProps) => { + ({ form, schema, children }: ISchemaVirtualFieldComponentProps) => { + const [{ current }, setFieldState] = useFieldState({ + current: 0 + }) const ref = useRef(current) const { dataSource, ...stepProps } = schema.getExtendsComponentProps() const items = toArr(dataSource) @@ -45,18 +43,17 @@ export const FormStep: React.FC & value: cur, preValue: current }) - form.setFieldState(path, state => { - state.current = cur + setFieldState({ + current: cur }) } - current = current || 0 - useFormEffects(({ setFieldState }) => { + useFormEffects(($, { setFieldState }) => { items.forEach(({ name }, index) => { setFieldState(name, (state: any) => { state.display = index === current }) }) - EffectHooks.onStepCurrentChange$().subscribe(({ value }) => { + $(StateMap.ON_FORM_STEP_CURRENT_CHANGE).subscribe(({ value }) => { items.forEach(({ name }, index) => { if (!name) throw new Error('FormStep dataSource must include `name` property') @@ -65,34 +62,27 @@ export const FormStep: React.FC & }) }) }) - }) - useMemo(() => { - update(ref.current) - form.subscribe(({ type, payload }) => { - switch (type) { - case StateMap.ON_FORM_STEP_NEXT: - form.validate().then(({ errors }) => { - if (errors.length === 0) { - update( - ref.current + 1 > items.length - 1 - ? ref.current - : ref.current + 1 - ) - } - }) - break - case StateMap.ON_FORM_STEP_PREVIOUS: - update(ref.current - 1 < 0 ? ref.current : ref.current - 1) - break - case StateMap.ON_FORM_STEP_GO_TO: - if (!(payload < 0 || payload > items.length)) { - update(payload) - } - break + $(StateMap.ON_FORM_STEP_NEXT).subscribe(() => { + form.validate().then(({ errors }) => { + if (errors.length === 0) { + update( + ref.current + 1 > items.length - 1 ? ref.current : ref.current + 1 + ) + } + }) + }) + + $(StateMap.ON_FORM_STEP_PREVIOUS).subscribe(() => { + update(ref.current - 1 < 0 ? ref.current : ref.current - 1) + }) + + $(StateMap.ON_FORM_STEP_GO_TO).subscribe(payload => { + if (!(payload < 0 || payload > items.length)) { + update(payload) } }) - }, []) + }) ref.current = current return ( diff --git a/packages/next/src/components/FormStep.tsx b/packages/next/src/components/FormStep.tsx index b98926f068a..31958ddbe3b 100644 --- a/packages/next/src/components/FormStep.tsx +++ b/packages/next/src/components/FormStep.tsx @@ -1,9 +1,10 @@ -import React, { useMemo, useRef, Fragment } from 'react' +import React, { useRef, Fragment } from 'react' import { createControllerBox, ISchemaVirtualFieldComponentProps, createEffectHook, - useFormEffects + useFormEffects, + useFieldState } from '@uform/react-schema-renderer' import { toArr } from '@uform/shared' import { Step } from '@alifd/next' @@ -30,70 +31,58 @@ type StepComponentExtendsProps = StateMap export const FormStep: React.FC & StepComponentExtendsProps = createControllerBox( 'step', - ({ - props, - form, - path, - current, - children - }: ISchemaVirtualFieldComponentProps) => { + ({ form, schema, children }: ISchemaVirtualFieldComponentProps) => { + const [{ current }, setFieldState] = useFieldState({ + current: 0 + }) const ref = useRef(current) - const { dataSource, ...stepProps } = props['x-component-props'] || {} + const { dataSource, ...stepProps } = schema.getExtendsComponentProps() const items = toArr(dataSource) const update = (cur: number) => { form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, { value: cur, preValue: current }) - form.setFieldState(path, state => { - state.current = cur + setFieldState({ + current: cur }) } - current = current || 0 - useFormEffects(({ setFieldState }) => { + useFormEffects(($, { setFieldState }) => { items.forEach(({ name }, index) => { setFieldState(name, (state: any) => { state.display = index === current }) }) - EffectHooks.onStepCurrentChange$().subscribe(({ value }) => { + $(StateMap.ON_FORM_STEP_CURRENT_CHANGE).subscribe(({ value }) => { items.forEach(({ name }, index) => { if (!name) throw new Error('FormStep dataSource must include `name` property') - setFieldState(name, (state: any) => { state.display = index === value }) }) }) - }) - useMemo(() => { - update(ref.current) - form.subscribe(({ type, payload }) => { - switch (type) { - case StateMap.ON_FORM_STEP_NEXT: - form.validate().then(({ errors }) => { - if (errors.length === 0) { - update( - ref.current + 1 > items.length - 1 - ? ref.current - : ref.current + 1 - ) - } - }) - break - case StateMap.ON_FORM_STEP_PREVIOUS: - update(ref.current - 1 < 0 ? ref.current : ref.current - 1) - break - case StateMap.ON_FORM_STEP_GO_TO: - if (!(payload < 0 || payload > items.length)) { - update(payload) - } - break + $(StateMap.ON_FORM_STEP_NEXT).subscribe(() => { + form.validate().then(({ errors }) => { + if (errors.length === 0) { + update( + ref.current + 1 > items.length - 1 ? ref.current : ref.current + 1 + ) + } + }) + }) + + $(StateMap.ON_FORM_STEP_PREVIOUS).subscribe(() => { + update(ref.current - 1 < 0 ? ref.current : ref.current - 1) + }) + + $(StateMap.ON_FORM_STEP_GO_TO).subscribe(payload => { + if (!(payload < 0 || payload > items.length)) { + update(payload) } }) - }, []) + }) ref.current = current return ( diff --git a/packages/react/src/components/Field.tsx b/packages/react/src/components/Field.tsx index d5f75d29b95..39b73dd035c 100644 --- a/packages/react/src/components/Field.tsx +++ b/packages/react/src/components/Field.tsx @@ -2,19 +2,29 @@ import React from 'react' import { useField } from '../hooks/useField' import { isFn } from '@uform/shared' import { IFieldStateUIProps } from '../types' +import { FieldContext } from '../context' export const Field: React.FC = props => { - const { state, props: innerProps, mutators, form } = useField(props) + const { state, field, props: innerProps, mutators, form } = useField(props) + if (!state.visible || !state.display) return if (isFn(props.children)) { - return props.children({ - form, - state, - props: innerProps, - mutators - }) + return ( + + {props.children({ + form, + state, + props: innerProps, + mutators + })} + + ) } else { - return {props.children} + return ( + + {props.children} + + ) } } diff --git a/packages/react/src/components/VirtualField.tsx b/packages/react/src/components/VirtualField.tsx index 84bacbdb4fe..ff46e98a91a 100644 --- a/packages/react/src/components/VirtualField.tsx +++ b/packages/react/src/components/VirtualField.tsx @@ -2,20 +2,27 @@ import React from 'react' import { useVirtualField } from '../hooks/useVirtualField' import { isFn } from '@uform/shared' import { IVirtualFieldProps } from '../types' +import { FieldContext } from '../context' -export const VirtualField: React.FunctionComponent< - IVirtualFieldProps -> = props => { - const { state, props: innerProps, form } = useVirtualField(props) +export const VirtualField: React.FunctionComponent = props => { + const { state, field, props: innerProps, form } = useVirtualField(props) if (!state.visible || !state.display) return if (isFn(props.children)) { - return props.children({ - form, - state, - props: innerProps - }) + return ( + + {props.children({ + form, + state, + props: innerProps + })} + + ) } else { - return props.children + return ( + + {props.children} + + ) } } diff --git a/packages/react/src/context.ts b/packages/react/src/context.ts index a90a7c3f514..3200d372c11 100644 --- a/packages/react/src/context.ts +++ b/packages/react/src/context.ts @@ -1,7 +1,10 @@ import { createContext } from 'react' import { Broadcast } from './shared' import { IForm } from '@uform/core' +import { IField, IVirtualField } from '@uform/core' export const BroadcastContext = createContext(null) +export const FieldContext = createContext(null) + export default createContext(null) diff --git a/packages/react/src/hooks/useField.ts b/packages/react/src/hooks/useField.ts index fbf8c47c155..861dc2e4a66 100644 --- a/packages/react/src/hooks/useField.ts +++ b/packages/react/src/hooks/useField.ts @@ -101,6 +101,7 @@ export const useField = (options: IFieldStateUIProps): IFieldHook => { const state = ref.current.field.getState() return { form, + field: ref.current.field, state: { ...state, errors: state.errors.join(', ') diff --git a/packages/react/src/hooks/useFieldState.ts b/packages/react/src/hooks/useFieldState.ts new file mode 100644 index 00000000000..e562614f8b5 --- /dev/null +++ b/packages/react/src/hooks/useFieldState.ts @@ -0,0 +1,24 @@ +import { useContext, useMemo, useRef } from 'react' +import { IFieldState } from '@uform/core' +import { FieldContext } from '../context' + +export const useFieldState = (defaultState: T) => { + const ref = useRef() + const field = useContext(FieldContext) + useMemo(() => { + field.unsafe_setSourceState(state => { + Object.assign(state, defaultState) + }) + }, []) + + ref.current = field.getState() + return [ + ref.current, + (nextState?: {}) => { + if (!nextState) return + field.setState(state => { + Object.assign(state, nextState) + }) + } + ] +} diff --git a/packages/react/src/hooks/useFormState.ts b/packages/react/src/hooks/useFormState.ts new file mode 100644 index 00000000000..3d246aa1bb1 --- /dev/null +++ b/packages/react/src/hooks/useFormState.ts @@ -0,0 +1,36 @@ +import { useContext, useEffect, useMemo, useRef } from 'react' +import { LifeCycleTypes, IFormState } from '@uform/core' +import { useForceUpdate } from './useForceUpdate' +import FormContext from '../context' + +export const useFormState = (defaultState: T) => { + const forceUpdate = useForceUpdate() + const ref = useRef<{ state: IFormState; subscribeId: number }>() + const form = useContext(FormContext) + ref.current.subscribeId = useMemo(() => { + form.setFormState(state => { + Object.assign(state, defaultState) + }, true) + return form.subscribe(({ type }) => { + if (type === LifeCycleTypes.ON_FORM_CHANGE) { + forceUpdate() + } + }) + }, []) + + ref.current.state = form.getFormState() + useEffect(() => { + return () => { + form.unsubscribe(ref.current.subscribeId) + } + }, []) + return [ + ref.current.state, + (nextState?: {}) => { + if (!nextState) return + form.setFormState(state => { + Object.assign(state, nextState) + }) + } + ] +} diff --git a/packages/react/src/hooks/useVirtualField.ts b/packages/react/src/hooks/useVirtualField.ts index 3f4adace206..964ae11c8fc 100644 --- a/packages/react/src/hooks/useVirtualField.ts +++ b/packages/react/src/hooks/useVirtualField.ts @@ -63,6 +63,7 @@ export const useVirtualField = ( const state = ref.current.field.getState() return { state, + field: ref.current.field, form, props: state.props } diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index df70b023a5d..17b08c39c73 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -11,6 +11,8 @@ export * from './components/VirtualField' export * from './components/FormSpy' export * from './components/FormProvider' export * from './components/FormConsumer' +export * from './hooks/useFieldState' +export * from './hooks/useFormState' export * from './hooks/useForm' export * from './hooks/useField' export * from './hooks/useVirtualField' diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index a69d2c106e6..186067331f2 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -10,7 +10,9 @@ import { IFormResetOptions, IFormSubmitResult, FormHeartSubscriber, - IFormGraph + IFormGraph, + IField, + IVirtualField } from '@uform/core' import { FormPathPattern } from '@uform/shared' import { Observable } from 'rxjs/internal/Observable' @@ -112,6 +114,7 @@ export interface IFormConsumerProps { export interface IFieldHook { form: IForm + field: IField state: IFieldState props: {} mutators: IMutators @@ -119,6 +122,7 @@ export interface IFieldHook { export interface IVirtualFieldHook { form: IForm + field: IVirtualField state: IFieldState props: {} }