formily使用小结

1,894 阅读4分钟

基本库使用介绍

先直接上代码

import React, { useState, useMemo } from 'react'
import { createForm, Field, GeneralField, onFieldChange, registerValidateRules } from '@formily/core'
import {
  FormProvider,
  createSchemaField,
  connect,
  FormConsumer,
  ISchema,
  mapProps,
} from '@formily/react'
import { FormLayout, FormItem, Input, FormGrid } from '@formily/antd'
import { Button, Input, Select } from 'antd'
import styles from './index.module.less'
import useDictMap from './hooks/useDictMap'
import TransTypeRadio from './components/TransTypeRadio'
import { DefaultSchema, OtherSchema } from './schemaConfig'

// 全局注入自定义校验
registerValidateRules({
  custom1: function(value, rule, ctx, render) {
    console.log(value, rule, ctx, render)
    return 'sssssss'
  }
})

const DynamicRenderer: React.FC = (props) => {
  const [schema, setSchema] = useState<ISchema>(DefaultSchema)
  // 通过接口获取字典
  const dictMap = useDictMap()

  const form = useMemo(() => createForm({
    effects() {
      // 当transType字段发生改变时切换表单元素
      onFieldChange('transType', (field: Field) => {
        if (field.value === 'I') {
          setSchema(OtherSchema)
        } else {
          setSchema(DefaultSchema)
        }
      })
    }
  }), [])

  // 通过字典设置options
  // connect提供props转换映射。可以使用回调方法映射,也可以使用对象直接映射
  const DynamicCSelect = useMemo(() => connect(
    Select,
    // mapProps({a: 'hahahah'}, (props, field) => {...props}) 
    mapProps({}, (props: any, field: Field) => {
      const options = props.options ?? dictMap[props.dictCode]
      return {
        ...props,
        options: options || [],
      }
    }),
    // 因为大多数第三方组件都不支持阅读态,如果想要快速支持阅读态的话,即可使用 mapReadPretty 函数来映射一个阅读态组件
    // mapReadPretty(TargetComponent, { readPretty: true })
    
    // dictMap为字典转换的key--options
  ), [dictMap])

  const SchemaField = createSchemaField({
    components: {
      FormItem,
      CInput,
      CSelect: DynamicCSelect,
      Input,
      FormGrid,
      TransTypeRadio,
    },
  })

  return (
    <FormProvider form={form}>
      <FormLayout
        // 样式自定义修改
        className={styles.layout}
        layout="inline"
        labelAlign="right"
        labelWrap
        labelWidth={133}
        fullness
        gridColumnGap={8}
      >
        <SchemaField schema={schema} />
      </FormLayout>
      <Button onClick={() => {
        console.log(form.values)
      }}>click</Button>
    </FormProvider>
  )
}

export default DynamicRenderer

schema

import { ISchema } from '@formily/react'
import { Field, IGeneralFieldState } from '@formily/core'

type SchemaReactionEffect =
  | 'onFieldInit'
  | 'onFieldMount'
  | 'onFieldUnmount'
  | 'onFieldValueChange'
  | 'onFieldInputValueChange'
  | 'onFieldInitialValueChange'
  | 'onFieldValidateStart'
  | 'onFieldValidateEnd'
  | 'onFieldValidateFailed'
  | 'onFieldValidateSuccess'

type SchemaReaction<Field = any> =
  | {
      dependencies?: string[] | Record<string, string> //依赖的字段路径列表,只能以点路径描述依赖,支持相对路径,如果是数组格式,那么读的时候也是数组格式,如果是对象格式,读的时候也是对象格式,只是对象格式相当于是一个alias
      when?: string | boolean //联动条件
      target?: string //要操作的字段路径,支持FormPathPattern路径语法,注意:不支持相对路径!!
      effects?: SchemaReactionEffect[] //主动模式下的独立生命周期钩子
      fulfill?: {
        //满足条件
        state?: IGeneralFieldState //更新状态
        schema?: ISchema //更新Schema
        run?: string //执行语句
      }
      otherwise?: {
        //不满足条件
        state?: IGeneralFieldState //更新状态
        schema?: ISchema //更新Schema
        run?: string //执行语句
      }
    }
  | ((field: Field) => void) //可以复杂联动

type SchemaReactions<Field = any> = SchemaReaction<Field> | SchemaReaction<Field>[]

export const DefaultSchema: ISchema = {
  type: 'object',
  properties: {
    grid: {
      type: 'void',
      'x-component': 'FormGrid',
      // 布局,3列
      'x-component-props': {
        minColumns: 1,
        maxColumns: 3
      },
      properties: {
        transType: {
          type: 'string',
          title: '境内外标志',
          default: 'O',
          'x-component': 'TransTypeRadio',
          'x-decorator': 'FormItem',
          'x-decorator-props': {
            // 独占三列
            gridSpan: 3
          },
        },
        remittanceName1: {
          type: 'string',
          title: '账号1',
          'x-component': 'Select',
          'x-decorator': 'FormItem',
          'x-component-props': {
            dictCode: 'ovs_clear_type',
            // options: [
            //   { label: 'ajsl', value: '1' },
            //   { label: 'sfgr', value: '2' },
            // ],
          },
          'x-reactions': [
            {
              dependencies: ['remittanceName2.remittanceName3'],
              fulfill: {
                schema: {
                  // 'x-visible': "{{$self.value === $deps[0]}}"
                  // 'x-visible': "{{'123' == $deps[0]}}",
                  required: "{{$deps[0] === '222'}}",
                },
              },
            },
            (field: Field) => {
              const remittanceName = field.query('.remittanceName').value()
              if (remittanceName === '周奇霖') {
                field.setComponentProps({
                  options: [{ label: '智障', value: 'zz' }],
                })
                field.setValue('zz')
              } else {
                field.setComponentProps({
                  options: undefined,
                })
                field.setValue(undefined)
              }
            },
          ],
        },
        remittanceName2: {
          type: 'void',
          title: '账号2',
          'x-decorator': 'FormItem',
          'x-component-props': { style: { width: 360 } },
          properties: {
            ['remittanceName2.remittanceName3']: {
              type: 'string',
              'x-component': 'Input',
              'x-component-props': { style: { width: 180 } },
            },
            ['remittanceName2.remittanceName4']: {
              type: 'string',
              'x-component': 'Input',
              'x-component-props': { style: { width: 180 } },
            },
          },
        },

        remittanceName5: {
          type: 'string',
          title: '账号',
          'x-component': 'Input',
          'x-decorator': 'FormItem',
          'x-component-props': { style: { width: 360 } },
        },
        remittanceName6: {
          type: 'string',
          title: '账号',
          'x-component': 'Input',
          'x-validator': [
            {
              max: 5,
              message: '最长5位',
            },
          ],
          'x-decorator': 'FormItem',
          'x-component-props': { style: { width: 360 } },
        },
        remittanceName7: {
          type: 'string',
          title: '账号',
          'x-component': 'Input',
          'x-decorator': 'FormItem',
          'x-component-props': { style: { width: 360 } },
        },
      },
    },
  },
}

export const OtherSchema: ISchema = {
  type: 'object',
  properties: {
    grid: {
      type: 'void',
      'x-component': 'FormGrid',
      'x-component-props': {
        minColumns: 1,
        maxColumns: 3
      },
      properties: {
        transType: {
          type: 'string',
          title: '境内外标志',
          default: 'O',
          'x-component': 'TransTypeRadio',
          'x-decorator': 'FormItem',
          'x-decorator-props': {
            gridSpan: 3
          },
        },
      },
    },
  },
}

自定义组件控制TransType组件

import { Radio, Modal } from 'antd'
import React, { useEffect, useState } from 'react'
import { FormInstance } from 'antd'
import _ from 'lodash'
import { observer, useField, useForm } from '@formily/react'

// 同步observer实现动态监听,可以将observer的值作为useEffect的deps使用
// 类似于直接用antd的useWatch。只不过这里observer双向数据流更方便使用
const TransTypeRadio: React.FC<Props> = observer((props) => {
  const { value, onChange } = props
  const field = useField()
  const form = useForm()

  // 境内外标志切换
  const handleTransTypeChange = (e) => {
    const res = form.values
    const initObj = form.initialValues

    const initObjKeys = Object.keys(initObj)
    const formKeys = Object.keys(res)
    const checkKeys = formKeys.filter((item) => initObjKeys.includes(item)) // 需要校验初始值的key
    const checkObj = {} // form中对应字段的值组成的对象
    checkKeys.forEach((item) => {
      checkObj[item] = res[item]
    })
    const noCheckKeys = formKeys
      ?.filter((item) => !initObjKeys?.includes(item))
      ?.filter((item) => !['transType']?.includes(item)) // 不需要校验初始值的key
    let checkFlag = true
    noCheckKeys?.forEach((item) => {
      if (!_.isEmpty(res[item])) {
        checkFlag = false
      }
    })
    const initFlag = JSON.stringify(initObj) === JSON.stringify(checkObj) && checkFlag
    if (!initFlag) {
      Modal.confirm({
        title: `确认切换至境${e.target.value === 'O' ? '外' : '内'}汇款?`,
        content: '切换后将保留已填内容中可用部分',
        okText: '确认',
        cancelText: '取消',
        centered: true,
        onOk: () => {
          if (e.target.value === 'O') {
            form.setValues({
              facilitationFlag: 'N',
              remittanceWay: '1',
              capFlag: 'N',
              residentCountry: ''
            })
          } else {
              form.setValues({
                residentCountry: 'CN',
                remittanceWay: res.facilitationFlag === 'Y' ? '3' : '2',
              })
          }
          // 重置通过代理行
          form.setValues({ proxyFlag: 'N', capFlag: 'N' })
          onChange(e.target.value)
        },
        onCancel: () => {},
      })
    } else {
      onChange(e.target.value)
      if (e.target.value === 'O') {
        form.setValues({ facilitationFlag: 'N', remittanceWay: '1', capFlag: 'N', residentCountry: '' })
      } else {
          form.setValues({
            residentCountry: 'CN',
            remittanceWay: res.facilitationFlag === 'Y' ? '3' : '2',
          })
      }
      // 重置通过代理行
      form.setValues({ proxyFlag: 'N', capFlag: 'N' })
    }
  }

  return (
    <Radio.Group value={value} onChange={handleTransTypeChange}>
      <Radio value={'O'}>境外汇款</Radio>
      <Radio value={'I'}>境内汇款</Radio>
    </Radio.Group>
  )
})

export default TransTypeRadio

type Props = {
  value
  onChange
}

@formily/core使用

core.formilyjs.org/zh-CN
createForm: 创建一个 Form 实例,作为 ViewModel 给 UI 框架层消费

effects
core核心还提供各种字段变化的回调,栗子中的onFiledChange等effects均是core核心中提供的可以作为主动联动的方法

query core核心提供query查询,其强大的路径系统可以很方便的在各种控制逻辑中获取到ProxyValue

@formily/react

react.formilyjs.org/zh-CN 里面看官网结合栗子即可得出使用方法

@formily/antd

直接接上antd组件库,炫酷

表单联动

  1. 被动联动:Field Reaction
    可以参考文档: react.formilyjs.org/zh-CN/api/s…

  2. 主动联动:effect hooks
    a)通过core核心的effects方法,在createForom里对field进行控制。
    b)通过自定义组件使用

import React from 'react'
import { createForm, onFieldReact } from '@formily/core'
import { FormProvider, Field, useFormEffects } from '@formily/react'
import { Input, Form } from 'antd'

const form = createForm({
  effects() {
    onFieldReact('custom.aa', (field) => {
      field.value = field.query('input').get('value')
    })
  },
})

const Custom = () => {
  useFormEffects(() => {
    onFieldReact('custom.bb', (field) => {
      field.value = field.query('.aa').get('value')
    })
  })
  return (
    <div>
      <Field name="aa" decorator={[Form.Item]} component={[Input]} />
      <Field name="bb" decorator={[Form.Item]} component={[Input]} />
    </div>
  )
}

export default () => (
  <FormProvider form={form}>
    <Field name="input" decorator={[Form.Item]} component={[Input]} />
    <Field name="custom" decorator={[Form.Item]} component={[Custom]} />
  </FormProvider>
)