表单开发模式

掌握了 value/onChange,就掌握了表单编写的精髓。

管理端的组件,只有两个目的:

  1. 向用户搜集数据
  2. 向用户展示数据

向用户搜集数据,存在三种数据格式:

  1. 原始数据类型的值(比如 string/number 等)
  2. 对象
  3. 数组

所以,向用户搜集任何数据的开发模式是:

  1. 三种输入框,一种接口:value/onChange。
    1. 搜集原始数据类型的输入框(大部分 antd 提供)
    2. 搜集对象的输入框
    3. 搜集数组的输入框
  2. form 是分发对象到下一级 input 的便利工具;form 本身对上一级是一个输出对象的 input。
  3. 搜集嵌套对象表单的开发,就是逐级下降,开发能搜集每个对象的 input。这是一个递归的过程,而且能够被自动化。

例子

搜集无嵌套对象

搜集下面的对象:

{
  "name": "奖励规则名",
  "desc": "奖励规则说明"
}

直接用 antd form,分发到搜集原始值的 input 即可:

Form:
  Input: name
  Input: desc

搜集嵌套对象

搜集下面的对象:

{
  "name": "奖励规则名",
  "desc": "奖励规则说明"
  "msg": {
    "template_id": "323ll1w",
    "app_id": "app12323"
  }
}

用 antd form,分发到 msg 时:

Form:
  Input: name
  Input: desc
  一个能搜集对象的输入框: msg

我们需要一个能搜集对象的输入框,而且它的接口符合 value/onChange。

回顾开发模式第二条:

form 是分发对象到下一级 input 的便利工具;form 本身对上一级是一个输出对象的 input。

form 本身就是一个可以输出对象的组件,只需要把它的接口改造成 value/onChange 即可。

能搜集 msg 对象的输入框组件:

import { Form, Input } from 'antd'
import React, { FC } from 'react'

export const MsgInput: FC<{
  value?: any,
  onChange?: (value: string) => void
}> = (props) => {
  const { value, onChange = (() => { }) } = props
  const [form] = Form.useForm()
  const fields = [{
    label: '模板 ID',
    key: 'template_id',
    jsx: <Input />,
   
  }, {
    label: 'APPID',
    key: 'app_id',
    jsx: <Input />,
  }]
  const getFields = (fields) => {
    const children = fields.map(one => (
      <Form.Item
        name={one.key}
        label={one.label}
        rules={one.rules}
      >
        {one.jsx ? one.jsx : <Input />}
      </Form.Item>
    ))

    return children
  }

  return (
    <Form
      form={form}
      initialValues={value}
      name='object_input'
      onValuesChange={(_, values) => {
        onChange(values)
      }}
    >
      {getFields(fields)}
    </Form>
  )
}

而这个【能搜集 msg 对象的输入框】,离成为【搜集任何对象的输入框】已经不远,比如:

export const CreateObjectInput = (fields) => {
   return (props) => {
      const { value, onChange = (() => { }) } = props
      const [form] = Form.useForm()
      const getFields = (fields) => {
        const children = fields.map(one => (
          <Form.Item
            name={one.key}
            label={one.label}
            rules={one.rules}
          >
            {one.jsx ? one.jsx : <Input />}
          </Form.Item>
        ))

        return children
      }

      return (
        <Form
          form={form}
          initialValues={value}
          name='object_input'
          onValuesChange={(_, values) => {
            onChange(values)
          }}
        >
          {getFields(fields)}
        </Form>
      )
  }
}

搜集嵌套数组的对象

比如:

{
  "name": "奖励规则名",
  "desc": "奖励规则说明"
  "msg": {
    "template_id": "323ll1w",
    "app_id": "app12323"
  },
  "msgs": [
    {
      "template_id": "323ll1w",
      "app_id": "app12323"
    },
    {
      "template_id": "323ll1w",
      "app_id": "app12323"
    },
  ]
}

用 antd form,分发到 msgs 时:

Form:
  Input: name
  Input: desc
  MsgInput: msg
  一个能搜集数组的输入框: msgs

能搜集 msgs 数组的输入框组件:

import { Form } from 'antd'
import React, { FC } from 'react'
export const MsgsInput: FC<{
  value?: any,
  onChange?: (value: any) => void
}> = (props) => {
  const { value, onChange = (() => { }) } = props
  const add = () => {
    onChange([...value, {}])
  }
  const del = () => {
    const newVal = [...value]
    newVal.pop()
    onChange(newVal)
  }
  if (!value) {
    // 如果为空,先通知外部,改为空数组,至少渲染一个表格
    onChange([])
    return <Button type='primary' onClick={add}>
      新增
    </Button>
  }

  const onOneInputChange = (v, i) => {
    const copy = [...value]
    copy[i] = v
    onChange(copy)
  }

  return (
    <>
      {value.map((one, index) => <MsgInput key={index} value={one} onChange={(value) => onOneInputChange(value, index)} />)}
      <Button type='primary' onClick={add}>
        新增
      </Button>
      {(value?.length > 1) ? <Button type='default' onClick={del}>
        删除
      </Button> : ''}
    </>
  )
}

而这个【能搜集 msgs 数组的输入框】,离成为【搜集任何数组的输入框】已经不远,比如:

export const CreateArrayInput = (OneInput) => {
  return (props) => {
    const { value, onChange = (() => { }) } = props
    const add = () => {
      onChange([...value, {}])
    }
    const del = () => {
      const newVal = [...value]
      newVal.pop()
      onChange(newVal)
    }
    if (!value) {
      // 如果为空,先通知外部,改为空数组,至少渲染一个表格
      onChange([])
      return <Button type='primary' onClick={add}>
        新增
      </Button>
    }

    const onOneInputChange = (v, i) => {
      const copy = [...value]
      copy[i] = v
      onChange(copy)
    }

    return (
      <>
        {value.map((one, index) => <OneInput key={index} value={one} onChange={(value) => onOneInputChange(value, index)} />)}
        <Button type='primary' onClick={add}>
          新增
        </Button>
        {(value?.length > 1) ? <Button type='default' onClick={del}>
          删除
        </Button> : ''}
      </>
    )
  }
}

后续

如果三种输入框都可以通用化,而且模式固定,那么,给定一个待搜集的数据,能自动化输出表单吗?

待下回分解。

拓展:共享和复用机制

复用的关键是:分类。

  1. 把自己的需求,落到一个类别,
  2. 然后根据这个类别,找到符合需求的组件。

所以,分类的本质是:快速定位解决方案的一种技术。

当提供一系列组件时,最重要的是确立了一套分类标准:

  1. 每个类别具有明确的边界,互斥不重叠
  2. 简明有限的分类步骤,能对一个需求执行快速分类。

而这个开发模式最大的意义在于:

确立了以待搜集数据作为组件的分类标准 —— 明确而且直接。

明确到,具备程序执行分类和匹配的可能:

输入一个数据,通过推断类型,自动匹配能输出相应类型的组件。

正文完