diff --git a/docs/zh-cn/SUMMARY.md b/docs/zh-cn/SUMMARY.md index ce5da502242..27d22b61890 100644 --- a/docs/zh-cn/SUMMARY.md +++ b/docs/zh-cn/SUMMARY.md @@ -19,6 +19,7 @@ - [性能优化实践](./schema-develop/performance.md) - [管理业务逻辑](./schema-develop/manage-business.md) - [实现递归渲染组件](./schema-develop/recursive-render.md) + - [玩转查询列表](./schema-develop/form-query.md) - [玩转自增列表组件](./schema-develop/complext-self-inc-component.md) - [实现超复杂自定义组件](./schema-develop/create-complex-field-component.md) - [FAQ](./schema-develop/faq.md) diff --git a/docs/zh-cn/schema-develop/form-query.md b/docs/zh-cn/schema-develop/form-query.md new file mode 100644 index 00000000000..c1049cb64c7 --- /dev/null +++ b/docs/zh-cn/schema-develop/form-query.md @@ -0,0 +1,717 @@ +# 玩转查询列表 + +## 中间件 + +想必大家在快速开始文档里已经了解到 Formily 提供了一个`useFormTableQuery`的 React Hook,当然,那只是冰山一角的功能,我们还有更强大的能力,middleware 扩展能力,为什么需要扩展?主要原因有以下几点: + +- 希望在不同查询阶段对数据做不同的处理 +- 希望在不同查询阶段查询前做一些异步取数再进行查询 +- 希望在不同查询阶段的查询前依赖某个表单字段交互动作再进行查询 + +整理一下需求,其实总结下来就需要 3 个能力: + +- 支持查询请求流程的中间件能力 +- 对查询请求流程的分类 +- 查询请求可以被联动阻塞 + +下面直接看例子 + +```jsx +import React from 'react' +import ReactDOM from 'react-dom' +import { + SchemaForm, + SchemaMarkupField as Field, + useFormTableQuery, + FormButtonGroup, + Submit, + Reset +} from '@formily/antd' // 或者 @formily/next +import { Input } from '@formily/antd-components' // 或者@formily/next-components +import { fetch } from 'mfetch' +import { Table } from 'antd' +import 'antd/dist/antd.css' + +const service = ({ values, pagination, sorter = {}, filters = {} }) => { + return fetch({ + url: 'https://randomuser.me/api', + data: { + results: pagination.pageSize, + sortField: sorter.field, + sortOrder: sorter.order, + page: pagination.current, + ...values, + ...filters + } + }) + .then(res => res.json()) + .then(({ results, info }) => { + return { + dataSource: results, + pageSize: 10, + ...pagination, + total: 200 + } + }) +} + +const columns = [ + { + title: 'Name', + dataIndex: 'name', + sorter: true, + render: name => `${name.first} ${name.last}`, + width: '20%' + }, + { + title: 'Gender', + dataIndex: 'gender', + filters: [ + { text: 'Male', value: 'male' }, + { text: 'Female', value: 'female' } + ], + width: '20%' + }, + { + title: 'Email', + dataIndex: 'email' + } +] + +const SubmitResetPlugin = () => ({ context }) => ({ + onFormSubmitQuery(payload, next) { + context.setPagination({ + ...context.pagination, + current: 1 + }) + return next(payload) + } +}) + +const App = () => { + const { form, table } = useFormTableQuery(service, [SubmitResetPlugin()], { + pagination: { + pageSize: 5 + } + }) + return ( + <> + + + + 查询 + 重置 + + + record.login.uuid} + /> + + ) +} + +ReactDOM.render(, document.getElementById('root')) +``` + +**案例解析** + +- 在 useFormTableQuery 第二个参数里传入 middleware 函数 +- middleware 函数是一个高阶函数,接收一个上下文参数,要求返回一个查询生命周期钩子 +- 每个生命周期钩子都是类似于 koa 的中间件机制,可以使用 async/await 机制来实现一些异步数据处理 + +## 查询生命周期 + +因为我们抽象了中间件的概念,但是查询其实又是有分类的,如果用户希望针对不同的查询阶段做不同的定制,是需要根据分类来定制的,所以,针对查询流程,我们定义了以下几种生命周期流程: + +- onFormFirstQuery 首次查询 +- onFormSubmitQuery 点击查询按钮的查询 +- onFormResetQuery 点击重置按钮的查询 +- onPageQuery 分页操作时的查询 +- onFilterQuery 过滤操作时的查询 +- onSorterQuery 排序操作时的查询 +- onFormWillQuery 查询即将开始 +- onFormDidQuery 查询结束 +- onFormQueryFailed 查询失败 + +我们再看一下第一个例子,其实是针对点击查询按钮的查询做了一次定制,希望每次点击查询按钮的时候重置页码。 + +## 联动阻塞 + +```jsx +import React from 'react' +import ReactDOM from 'react-dom' +import { + SchemaForm, + SchemaMarkupField as Field, + useFormTableQuery, + FormButtonGroup, + Submit, + Reset, + FormPath +} from '@formily/antd' // 或者 @formily/next +import { Input } from '@formily/antd-components' // 或者@formily/next-components +import { fetch } from 'mfetch' +import { Table } from 'antd' +import 'antd/dist/antd.css' + +const service = ({ values, pagination, sorter = {}, filters = {} }) => { + return fetch({ + url: 'https://randomuser.me/api', + data: { + results: pagination.pageSize, + sortField: sorter.field, + sortOrder: sorter.order, + page: pagination.current, + ...values, + ...filters + } + }) + .then(res => res.json()) + .then(({ results, info }) => { + return { + dataSource: results, + pageSize: 10, + ...pagination, + total: 200 + } + }) +} + +const columns = [ + { + title: 'Name', + dataIndex: 'name', + sorter: true, + render: name => `${name.first} ${name.last}`, + width: '20%' + }, + { + title: 'Gender', + dataIndex: 'gender', + filters: [ + { text: 'Male', value: 'male' }, + { text: 'Female', value: 'female' } + ], + width: '20%' + }, + { + title: 'Email', + dataIndex: 'email' + } +] + +const SubmitResetPlugin = () => ({ context }) => ({ + onFormSubmitQuery(payload, next) { + context.setPagination({ + ...context.pagination, + current: 1 + }) + return next(payload) + } +}) + +const matchFieldValuePlugin = (pattern, value) => ({ waitFor }) => ({ + async onFormFirstQuery(payload, next) { + await waitFor('onFieldValueChange', state => { + return ( + FormPath.parse(pattern).matchAliasGroup(state.name, state.path) && + state.value === value + ) + }) + return next(payload) + } +}) + +const App = () => { + const { form, table } = useFormTableQuery( + service, + [SubmitResetPlugin(), matchFieldValuePlugin('gender', 'male')], + { + pagination: { + pageSize: 5 + } + } + ) + return ( + <> + + + + + 查询 + 重置 + + +
record.login.uuid} + /> + + ) +} + +ReactDOM.render(, document.getElementById('root')) +``` + +## 异步默认值 + +```jsx +import React from 'react' +import ReactDOM from 'react-dom' +import { + SchemaForm, + SchemaMarkupField as Field, + useFormTableQuery, + FormButtonGroup, + Submit, + Reset +} from '@formily/antd' // 或者 @formily/next +import { Input } from '@formily/antd-components' // 或者@formily/next-components +import { fetch } from 'mfetch' +import { Table } from 'antd' +import 'antd/dist/antd.css' + +const service = ({ values, pagination, sorter = {}, filters = {} }) => { + return fetch({ + url: 'https://randomuser.me/api', + data: { + results: pagination.pageSize, + sortField: sorter.field, + sortOrder: sorter.order, + page: pagination.current, + ...values, + ...filters + } + }) + .then(res => res.json()) + .then(({ results, info }) => { + return { + dataSource: results, + pageSize: 10, + ...pagination, + total: 200 + } + }) +} + +const columns = [ + { + title: 'Name', + dataIndex: 'name', + sorter: true, + render: name => `${name.first} ${name.last}`, + width: '20%' + }, + { + title: 'Gender', + dataIndex: 'gender', + filters: [ + { text: 'Male', value: 'male' }, + { text: 'Female', value: 'female' } + ], + width: '20%' + }, + { + title: 'Email', + dataIndex: 'email' + } +] + +const SubmitResetPlugin = () => ({ context }) => ({ + onFormSubmitQuery(payload, next) { + context.setPagination({ + ...context.pagination, + current: 1 + }) + return next(payload) + } +}) + +const asyncDefaultPlugin = service => ({ actions }) => ({ + async onFormFirstQuery(payload, next) { + await service(actions) + return next(payload) + } +}) + +const sleep = (duration = 100) => + new Promise(resolve => { + setTimeout(resolve, duration) + }) + +const App = () => { + const { form, table } = useFormTableQuery( + service, + [ + SubmitResetPlugin(), + asyncDefaultPlugin(async actions => { + await sleep(5000) + actions.setFieldState('gender', state => { + state.value = 'male' + state.props.enum = ['male', 'female'] + }) + }) + ], + { + pagination: { + pageSize: 5 + } + } + ) + return ( + <> + + + + + 查询 + 重置 + + +
record.login.uuid} + /> + + ) +} + +ReactDOM.render(, document.getElementById('root')) +``` + +## Fusion 例子 + +```jsx +import React from 'react' +import ReactDOM from 'react-dom' +import { + SchemaForm, + SchemaMarkupField as Field, + useFormTableQuery, + FormButtonGroup, + Submit, + Reset +} from '@formily/next' // 或者 @formily/antd +import { Input } from '@formily/next-components' // 或者@formily/antd-components +import { fetch } from 'mfetch' +import { Table, Pagination } from '@alifd/next' +import '@alifd/next/dist/next.css' + +const service = ({ values, pagination, sorter = {}, filters = {} }) => { + return fetch({ + url: 'https://randomuser.me/api', + data: { + results: pagination.pageSize, + sortField: sorter.field, + sortOrder: sorter.order, + page: pagination.current, + ...values, + ...filters + } + }) + .then(res => res.json()) + .then(({ results, info }) => { + return { + dataSource: results, + pageSize: 10, + ...pagination, + total: 200 + } + }) +} + +const SubmitResetPlugin = () => ({ context }) => ({ + onFormSubmitQuery(payload, next) { + context.setPagination({ + ...context.pagination, + current: 1 + }) + return next(payload) + } +}) + +const asyncDefaultPlugin = service => ({ actions }) => ({ + async onFormFirstQuery(payload, next) { + await service(actions) + return next(payload) + } +}) + +const sleep = (duration = 100) => + new Promise(resolve => { + setTimeout(resolve, duration) + }) + +const App = () => { + const { form, table, pagination } = useFormTableQuery( + service, + [ + SubmitResetPlugin(), + asyncDefaultPlugin(async actions => { + await sleep(5000) + actions.setFieldState('gender', state => { + state.value = 'male' + state.props.enum = ['male', 'female'] + }) + }) + ], + { + pagination: { + pageSize: 5 + } + } + ) + return ( + <> + + + + + 查询 + 重置 + + +
+ `${name.first} ${name.last}`} + width="20%" + /> + + +
+ + + ) +} + +ReactDOM.render(, document.getElementById('root')) +``` + +## 基本 API + +### Ant Design + +```typescript +interface IEffectMiddlewareAPI { + waitFor: ( + type: string, + filter: (payload: TPayload) => boolean + ) => Promise + actions: TActions + context?: TContext +} + +interface IEffectMiddleware { + (options: IEffectMiddlewareAPI): { + [key: string]: ( + payload: TPayload, + next: (payload: any) => Promise + ) => Promise + } +} + +interface IQueryParams { + pagination: { + total: number + pageSize: number + current: number + } + sorter?: { + order: string + field: string + columnKey: string + column: any + } + filters?: { + [dataIndex: string]: any + } + values: any +} + +interface IQueryResponse { + dataSource: any[] + total: number + pageSize: number + current: number +} + +interface IQueryContext { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] + setPagination?: (pagination: IQueryParams['pagination']) => void + setFilters?: (filters: IQueryParams['filters']) => void + setSorter?: (sorter: IQueryParams['sorter']) => void +} + +interface IQueryProps { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] +} + +type useFormTableQuery = ( + service: (payload: IQueryParams) => IQueryResponse | Promise, //主请求函数 + middlewares?: IEffectMiddleware[], //查询中间件 + defaultProps: IQueryProps = {} //默认参数 +) => { + setPagination: (pagination: IQueryParams['pagination']) => void //设置页码 + setFilters: (filters: IQueryParams['filters']) => void //设置过滤信息 + setSorter: (sorter: IQueryParams['sorter']) => void //设置排序信息 + trigger: (type: string = 'onFormSubmitQuery') => void //发起新查询流程,或者走默认onFormSubmitQuery流程 + form: { + effects: IEffects //这就是Formily标准effects函数 + } + table: { + //标准ant design table属性 + loading: boolean + dataSource: any[] + pagination: IQueryParams['pagination'] + onChange: ( + pagination: IQueryParams['pagination'], + filters: IQueryParams['filters'], + sorter: IQueryParams['sorters'] + ) => void + } +} +``` + +### Fusion Next + +```typescript + +interface IEffectMiddlewareAPI { + waitFor: (type: string, filter: (payload: TPayload) => boolean) => Promise; + actions: TActions; + context?: TContext; +} + +interface IEffectMiddleware { + (options: IEffectMiddlewareAPI): { + [key: string]: (payload: TPayload, next: (payload: any) => Promise) => Promise; + }; +} + +interface IQueryParams { + pagination: { + total: number + pageSize: number + current: number + } + sorter?: { + order: string + field: string + columnKey?: string + column?: any + } + filters?: { + [dataIndex: string]: any + } + values: any +} + +interface IQueryResponse { + dataSource: any[] + total: number + pageSize: number + current: number +} + +interface IQueryContext { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] + setPagination?: (pagination: IQueryParams['pagination']) => void + setFilters?: (filters: IQueryParams['filters']) => void + setSorter?: (sorter: IQueryParams['sorter']) => void +} + +interface IQueryProps { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] +} + +type useFormTableQuery = ( + service: (payload: IQueryParams) => IQueryResponse | Promise, //主请求函数 + middlewares?: IEffectMiddleware[], //查询中间件 + defaultProps: IQueryProps = {} //默认参数 +) =>{ + setPagination: (pagination: IQueryParams['pagination']) => void //设置页码 + setFilters: (filters: IQueryParams['filters']) => void //设置过滤信息 + setSorter: (sorter: IQueryParams['sorter']) => void //设置排序信息 + trigger:(type:string='onFormSubmitQuery')=>void //发起新查询流程,或者走默认onFormSubmitQuery流程 + form:{ + effects:IEffects //这就是Formily标准effects函数 + }, + table: { + loading, + dataSource: any[], + onSort: (field: string, order: string) =>void, + onFilter: (filters: any) => void + }, + pagination: { + current: response.current || 1, + pageSize: response.pageSize || 20, + total: response.total || 0, + onPageSizeChange(pageSize: number) :void, + onChange(current: number) :void + } +} + +``` + +从上面两种组件库的`useFormTableQuery` API 来看,因为 Fusion Table 没有内置 Pagination,所以我们需要分别传递 pagination,相比之下,Ant Design Table 使用起来会更简单一些 diff --git a/docs/zh-cn/schema-develop/form-schema.md b/docs/zh-cn/schema-develop/form-schema.md index d8b26e6b137..8ceceb9cfe4 100644 --- a/docs/zh-cn/schema-develop/form-schema.md +++ b/docs/zh-cn/schema-develop/form-schema.md @@ -94,12 +94,13 @@ Schema 开发,最核心的就是 Schema,只有我们理解了这套协议之 ## x-props 扩展属性 -| 属性名 | 描述 | 类型 | -| ----------------------- | ------------------------------------------------- | --------- | -| `x-props.addonAfter` | FormItem 的尾随内容 | ReactNode | -| `x-props.itemStyle` | FormItem 的 style 属性 | Object | -| `x-props.itemClassName` | FormItem 的 className 属性 | String | -| `x-props.triggerType` | 配置校验触发类型 `"onChange" | "onBlur" | "none"` | String | +| 属性名 | 描述 | 类型 | +| -------------------------- | ------------------------------------------------- | --------- | +| `x-props.addonAfter` | FormItem 的尾随内容 | ReactNode | +| `x-props.itemStyle` | FormItem 的 style 属性 | Object | +| `x-props.itemClassName` | FormItem 的 className 属性 | String | +| `x-props.triggerType` | 配置校验触发类型 `"onChange" | "onBlur" | "none"` | String | +| 针对组件库的 FormItem 属性 | 比如 labelCol/wrapperCol 等 | | ## Form Schema 表达式 diff --git a/packages/antd/src/hooks/useFormTableQuery.tsx b/packages/antd/src/hooks/useFormTableQuery.tsx index 231f3a63fc8..d232c8bd4d1 100644 --- a/packages/antd/src/hooks/useFormTableQuery.tsx +++ b/packages/antd/src/hooks/useFormTableQuery.tsx @@ -4,8 +4,9 @@ import { IEffectMiddleware, ISchemaFormActions } from '@formily/react-schema-renderer' +import { defaults } from '@formily/shared' -type IQueryParams = { +interface IQueryParams { pagination: { total: number pageSize: number @@ -23,37 +24,76 @@ type IQueryParams = { values: any } -type IQueryResponse = { +interface IQueryResponse { dataSource: any[] total: number pageSize: number current: number } +interface IQueryContext { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] + setPagination?: (pagination: IQueryParams['pagination']) => void + setFilters?: (filters: IQueryParams['filters']) => void + setSorter?: (sorter: IQueryParams['sorter']) => void +} + +interface IQueryProps { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] +} + export const useFormTableQuery = ( service: (payload: IQueryParams) => IQueryResponse | Promise, - middlewares?: IEffectMiddleware[] + middlewares?: IEffectMiddleware[], + defaultProps: IQueryProps = {} ) => { - const ref = useRef({}) - const [pagination, setPagination] = useState({ - current: 1, - total: 0, - pageSize: 20 - }) - const [sorter, setSorter] = useState() - const [filters, setFilters] = useState() - const { effects, trigger, loading, response } = useFormQuery(async values => { - return service({ - values, - pagination: ref.current.pagination, - sorter: ref.current.sorter, - filters: ref.current.filters - }) - }, middlewares) + const ref = useRef({}) + const [pagination, setPagination] = useState( + defaults( + { + current: 1, + total: 0, + pageSize: 20 + }, + defaultProps.pagination + ) + ) + const [sorter, setSorter] = useState( + defaultProps.sorter + ) + const [filters, setFilters] = useState( + defaultProps.filters + ) + const { effects, trigger, loading, response } = useFormQuery< + IQueryParams, + IQueryResponse, + IQueryContext + >( + async values => { + return service({ + values, + pagination: ref.current.pagination, + sorter: ref.current.sorter, + filters: ref.current.filters + }) + }, + middlewares, + ref.current + ) ref.current.pagination = pagination ref.current.sorter = sorter ref.current.filters = filters + ref.current.setPagination = setPagination + ref.current.setSorter = setSorter + ref.current.setFilters = setFilters return { + setPagination, + setSorter, + setFilters, trigger, form: { effects @@ -68,10 +108,20 @@ export const useFormTableQuery = ( total: response.total || 0 }, onChange: (pagination: any, filters: any, sorter: any) => { - if (pagination) setPagination(pagination) - if (filters) setFilters(filters) - if (sorter) setSorter(sorter) - trigger() + let type = '' + if (pagination) { + setPagination(pagination) + type = 'onPageQuery' + } + if (filters) { + setFilters(filters) + type = 'onFilterQuery' + } + if (sorter) { + setSorter(sorter) + type = 'onSorterQuery' + } + trigger(type) } } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fff249e0556..1d425b55ac2 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -588,6 +588,8 @@ export function createForm(options: IFormCreatorOptions = {}) { const { value, rules, + errors, + warnings, editable, visible, unmounted, @@ -599,7 +601,8 @@ export function createForm(options: IFormCreatorOptions = {}) { visible === false || unmounted === true || display === false || - (field as any).disabledValidate + (field as any).disabledValidate || + (rules.length === 0 && errors.length === 0 && warnings.length === 0) ) return validate(value, []) clearTimeout((field as any).validateTimer) diff --git a/packages/next/src/hooks/useFormTableQuery.ts b/packages/next/src/hooks/useFormTableQuery.ts index 68b2f14e7c0..a8a76ce6134 100644 --- a/packages/next/src/hooks/useFormTableQuery.ts +++ b/packages/next/src/hooks/useFormTableQuery.ts @@ -4,8 +4,9 @@ import { IEffectMiddleware, ISchemaFormActions } from '@formily/react-schema-renderer' +import { defaults } from '@formily/shared' -type IQueryParams = { +interface IQueryParams { pagination: { total: number pageSize: number @@ -23,37 +24,76 @@ type IQueryParams = { values: any } -type IQueryResponse = { +interface IQueryResponse { dataSource: any[] total: number pageSize: number current: number } +interface IQueryContext { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] + setPagination?: (pagination: IQueryParams['pagination']) => void + setFilters?: (filters: IQueryParams['filters']) => void + setSorter?: (sorter: IQueryParams['sorter']) => void +} + +interface IQueryProps { + pagination?: IQueryParams['pagination'] + sorter?: IQueryParams['sorter'] + filters?: IQueryParams['filters'] +} + export const useFormTableQuery = ( service: (payload: IQueryParams) => IQueryResponse | Promise, - middlewares?: IEffectMiddleware[] + middlewares?: IEffectMiddleware[], + defaultProps: IQueryProps = {} ) => { - const ref = useRef({}) - const [pagination, setPagination] = useState({ - current: 1, - total: 0, - pageSize: 20 - }) - const [sorter, setSorter] = useState() - const [filters, setFilters] = useState() - const { effects, trigger, loading, response } = useFormQuery(async values => { - return service({ - values, - pagination: ref.current.pagination, - sorter: ref.current.sorter, - filters: ref.current.filters - }) - }, middlewares) + const ref = useRef({}) + const [pagination, setPagination] = useState( + defaults( + { + current: 1, + total: 0, + pageSize: 20 + }, + defaultProps.pagination + ) + ) + const [sorter, setSorter] = useState( + defaultProps.sorter + ) + const [filters, setFilters] = useState( + defaultProps.filters + ) + const { effects, trigger, loading, response } = useFormQuery< + IQueryParams, + IQueryResponse, + IQueryContext + >( + async values => { + return service({ + values, + pagination: ref.current.pagination, + sorter: ref.current.sorter, + filters: ref.current.filters + }) + }, + middlewares, + ref.current + ) ref.current.pagination = pagination ref.current.sorter = sorter ref.current.filters = filters + ref.current.setPagination = setPagination + ref.current.setSorter = setSorter + ref.current.setFilters = setFilters return { + setPagination, + setSorter, + setFilters, trigger, form: { effects @@ -66,11 +106,11 @@ export const useFormTableQuery = ( field, order }) - trigger() + trigger('onSortQuery') }, onFilter: (filters: any) => { setFilters(filters) - trigger() + trigger('onFilterQuery') } }, pagination: { @@ -82,14 +122,14 @@ export const useFormTableQuery = ( ...pagination, pageSize }) - trigger() + trigger('onPageQuery') }, onChange(current: number) { setPagination({ ...pagination, current }) - trigger() + trigger('onPageQuery') } } } diff --git a/packages/react/src/hooks/useFormQuery.ts b/packages/react/src/hooks/useFormQuery.ts index da381764d04..45d73f1b6c0 100644 --- a/packages/react/src/hooks/useFormQuery.ts +++ b/packages/react/src/hooks/useFormQuery.ts @@ -1,15 +1,17 @@ import { useMemo, useState, useRef } from 'react' -import { createQueryEffects } from '../shared' +import { createQueryEffects, ON_FORM_QUERY } from '../shared' import { toArr } from '@formily/shared' import { IEffectMiddleware, IFormActions } from '../types' export const useFormQuery = < TQueryPayload = any, TQueryResult = any, + TContext = any, TActions extends IFormActions = any >( resource: (payload: TQueryPayload) => TQueryResult | Promise, - middlewares: IEffectMiddleware[] + middlewares: IEffectMiddleware[], + context?: TContext ) => { const ref = useRef() const [state, setState] = useState({ @@ -23,13 +25,13 @@ export const useFormQuery = < response: ref.current.response, error: ref.current.error, ...useMemo(() => { - let dispatch: any + let trigger: any const effects = createQueryEffects( resource, [ ({ actions }) => { - dispatch = () => { - actions.dispatch('onFormSubmit', actions.getFormState()) + trigger = (type: string = 'onFormSubmitQuery') => { + actions.dispatch(ON_FORM_QUERY, type) } return { async onFormWillQuery(payload, next) { @@ -61,13 +63,14 @@ export const useFormQuery = < return response } }) - ] + ], + context ) return { effects, - trigger() { - if (dispatch) { - dispatch() + trigger(type?: string) { + if (trigger) { + trigger(type) } } } diff --git a/packages/react/src/shared.ts b/packages/react/src/shared.ts index 03c528ec3c4..a080ece766f 100644 --- a/packages/react/src/shared.ts +++ b/packages/react/src/shared.ts @@ -1,5 +1,6 @@ import { isFn, + isStr, FormPath, Subscribable, isValid, @@ -302,7 +303,10 @@ export const FormEffectHooks = { ) } -export const createEffectsProvider = ( +export const createEffectsProvider = < + TActions extends IFormActions = any, + TContext = any +>( callback: IEffectProviderHandler, middlewares?: IEffectMiddleware[], context?: TContext @@ -311,6 +315,10 @@ export const createEffectsProvider = ( const resolves = {} + const resolvePayload = (payload: any) => { + return isFn(payload.getState) ? payload.getState() : payload + } + const waitFor = async ( type: string, filter: (payload: TPayload) => boolean @@ -327,24 +335,16 @@ export const createEffectsProvider = ( }) } - const triggerTo = async ( - type: string, - payload: TPayload - ): Promise => { + const triggerTo = (type: string, payload: TPayload): void => { if (resolves[type]) { + payload = resolvePayload(payload) if (resolves[type].filter) { if (resolves[type].filter(payload)) { - return resolves[type].resolve(payload) - } else { - return + resolves[type].resolve(payload) } + } else { + resolves[type].resolve(payload) } - return resolves[type].resolve(payload) - } else { - promises[type] = new Promise(resolve => { - resolves[type] = { resolve } - }) - return resolves[type].resolve(payload) } } @@ -371,84 +371,66 @@ export const createEffectsProvider = ( let i = 0 const next = (payload: TPayload) => { if (!queue[type][i]) return payload - return Promise.resolve(queue[type][i++](payload, next)) + const response = queue[type][i++](payload, next) + if (response === undefined) { + return new Promise(() => {}) + } + return Promise.resolve(response) } - return await next(payload) + return await next(resolvePayload(payload)) } return payload } - const subscribe = (type: string) => { - $(type).subscribe(async (payload: any) => { + $('onFormInit').subscribe(() => { + actions.subscribe(async ({ type, payload }) => { await applyMiddlewares(type, payload) - await triggerTo(type, payload) + triggerTo(type, payload) }) - } - - subscribe('onFieldInit') - subscribe('onFieldChange') - subscribe('onFieldInputChange') - subscribe('onFormChange') - subscribe('onFormMount') - subscribe('onFormSubmit') - subscribe('onFormReset') + }) callback({ ...runtime, applyMiddlewares, triggerTo, waitFor })($, actions) } } +export const ON_FORM_QUERY = '@@__ON_FORM_QUERY__@@' + export const createQueryEffects = < TQueryPayload = any, TQueryResult = any, TActions extends IFormActions = any, TContext = any >( - query: (payload: TQueryPayload) => TQueryResult | Promise, + resource: (payload: TQueryPayload) => TQueryResult | Promise, middlewares?: IEffectMiddleware[], context?: TContext ) => { return createEffectsProvider( ({ applyMiddlewares, actions }) => $ => { - $('onFormMount').subscribe(async () => { + $(ON_FORM_QUERY).subscribe(async (type: string) => { + if (!isStr(type)) return let values = await applyMiddlewares( 'onFormWillQuery', actions.getFormState(state => state.values) ) - values = await applyMiddlewares('onFormFirstQuery', values) + values = await applyMiddlewares(type, values) try { - await applyMiddlewares('onFormDidQuery', await query(values)) + await applyMiddlewares('onFormDidQuery', await resource(values)) } catch (e) { await applyMiddlewares('onFormQueryFailed', e) throw e } }) + $('onFormMount').subscribe(async () => { + actions.dispatch(ON_FORM_QUERY, 'onFormFirstQuery') + }) $('onFormSubmit').subscribe(async () => { - let values = await applyMiddlewares( - 'onFormWillQuery', - actions.getFormState(state => state.values) - ) - values = await applyMiddlewares('onFormSubmitQuery', values) - try { - await applyMiddlewares('onFormDidQuery', await query(values)) - } catch (e) { - await applyMiddlewares('onFormQueryFailed', e) - throw e - } + actions.dispatch(ON_FORM_QUERY, 'onFormSubmitQuery') }) $('onFormReset').subscribe(async () => { - let values = await applyMiddlewares( - 'onFormWillQuery', - actions.getFormState(state => state.values) - ) - values = await applyMiddlewares('onFormResetQuery', values) - try { - await applyMiddlewares('onFormDidQuery', await query(values)) - } catch (e) { - await applyMiddlewares('onFormQueryFailed', e) - throw e - } + actions.dispatch(ON_FORM_QUERY, 'onFormResetQuery') }) }, middlewares, diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 1c17d088dbd..530890ac4cc 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -144,7 +144,7 @@ type OMitActions = | 'unsafe_do_not_use_transform_data_path' export type IFormActions = Omit & { - dispatch: (type: string, payload: any) => void + dispatch: (type: string, payload?: any) => void } type WrapPromise< @@ -162,10 +162,7 @@ export interface IEffectProviderAPI { type: string, filter: (payload: TPayload) => boolean ) => Promise - triggerTo: ( - type: string, - payload: TPayload - ) => Promise + triggerTo: (type: string, payload: TPayload) => void applyMiddlewares: ( type: string, payload: TPayload