npm package: @jzl/aa-form
Description: ## 起因
Latest version: 0.0.6

# auto-antd-form

## 起因

Form 使用双向绑定，开发效率高，代码可读性好。

1. 双向绑定表达，表达简单直观。支持嵌套字段

2. 提供 validate 机制，配合 `<FormContext>`和`<FormButton>`, 自动管理相关状态。

3. 组件使用方式和 antd 一致，几乎没有学习成本，可以直接参考 antd 的文档。

4. 扩展性强，符合 value，onChange 模式的组件，通过简单的包装，可以快速融入进这套模式中

5. 性能。每次修改，只有被编辑的那个组件会触发渲染，form 再大，也不会有性能问题。

## 使用

0. 首先安装 react react-dom immer -react antd
1. yarn add @jzl/aa-form
1. 如果使用 antd 的按需加载，为了样式生效，需要在 webpack 的 modules 的 rules 中 配置添加

```js
  {
    test: /node_modules[\\/]@jzl[\\/]aa-form[\\/].*\.(j|t)sx?$/,
    use: {
      loader: "babel-loader",
      options: {
        // your options
      }
    }
  },
```

## Basic Usage

```jsx
import { FormContext, FormInput, FormCheckbox } from '@jzl/aa-form';

// function component
return (
  <FormContext
    initState={{
      text: '',
      done: false,
    }}
  >
    <FormInput
      className="input-class"
      label="text"
      path="text"
      rules={(value: string) => {
        // return 非空字符串表示验证有错误，提示就是这个字符串
        if (value === 'todo') {
          return '不能输入 todo';
        }
      }}
      placeholder="输入todo文本"
    />
    <FormCheckbox label="done" path="done" />
  </FormContext>
);
```

## 一个复杂的例子

[demo](https://github.com/jzlxiaohei/aa-form/blob/master/packages/demo/src/aa-uncontrol-mode-npm.tsx)

`FormContext`

```typescript
// 使用 state 为受控模式，需要在 onContextChange 对state 进行更新
// 传入state后，会忽略 initState
{
  initState?: M;
  state?: M;
  validateAtFirst?: boolean; // 默认是点击过 FormButton 后才开始做validate 检查，如果开始就要检查，设置为true
  onSubmit({ state }: { state: M }, e: React.FormEvent<HTMLFormElement>): void; // 只有 validate 检查成功后，才会触发
  onContextChange?({ state }: { state: M }): void; // 这个form的state，有任何的改变
}
```

## 提供的组件和设施 (待添加)

1. 组件

```
FormInput, FormTextArea,FormInputNumber

FormCheckbox

FormSelect, FormRadioGroup, FormTagGroup

FormDate, FormDateRange

```

2. 设施

```
// 下面两个用于提供上下文（公共model)，状态的检验和 submit 事件等
FormContext: 提供共享上下文，比如 model（如果组件提供的 model，优先级更改）, 一些通用样式，比如 label 占多宽

FormButton: 和 FormContext 配合，自动管理验证和提交

FormWrapper: 自定义复杂类型，比如需要动态修改的数组，Map。请参考本文档的 `一个复杂的例子`.
```

## API

`From` 组件,如 Input, Select, Radio 等,

```typescript
export type ValidateFn<M> = ((value: any, model: M) => string | void) | string;

export interface ICommonOption {
  key?: string;
  title: string;
  value: any;
}
// 通用属性如下
// 一般情况，你只需要关注2，3个属性
{
  path?: string; // 字段名
  onChange?(param: IChangeParam<M>): void; // onChange 事件，此时 value 已经修改
  beforeChange?(
    param: IChangeParam<M> & {
      oldValue?: any;
    },
  ): boolean | void; // change 发生前，可以拿到 old value. 函数return false, 阻止修改
  transformViewToModel?(value: any, props: ICommonFormOuterProps<M>): any;
  transformModelToView?(mValue: any, props: ICommonFormOuterProps<M>): any;
  label?: string | React.ReactNode;
  itemProps?: FormItemProps; // antd Form.Item 的 props
  noFormItem?: boolean; // 默认所以的组件都会包裹在 <Form.Item>里，设置 true，直接返回组件
  rules?: ValidateFn<M> | ValidateFn<M>[]; // validate 规则
  suffixTip?: React.ReactNode; // 组件后面的提示文案
  [x: string]: any; // 其他组件属性
}
```

FormWrapper 要求 children 是一个函数，函数的参数如下 model 定义如下

```typescript
export type IAaFormModel<M> = {
  state: M; // 最新的 state
  setKey(key: string, value: any): M; // 修改某一个字段，返回新的 state
  update(updateFn: (draft: Draft<M>) => void): M; // 包装了 immer 的produce，在函数里直接修改 state，返回新的 state
};
```

## Validate

在组件上添加 rules 接受 `(value: any, Model: M) => string | any`
返回值只要是非空字符串，就表明出错，提示信息就是返回的字符串

```tsx
// yourRuleFn 接受 value, 以及 state: M, 通过这两个
// 返回值只要是非空字符串，就表明出错，
<FormInput path="text" rules={yourRuleFn} />
```

点击按钮后，检查所有规则，如果有错，阻止提交，给出提示（提示的样式见 antd Form.Item） 同时 disable `FormButton`，直到满足所有规则。

组件可以自己提供 `defaultRuleFn: (value, model) => string | any`
通过 defaultRuleFn 去控制状态默认的的合法性，比如上传组件，正在上传中，最近的状态不是合法的，可以设置为 `请等待上传完毕`。

## 对 Antd 的修改

1. 统一 onChange 的参数（antd 中 Input 传的是 Event，CheckBox 传的 checked value）。不过双向绑定的机制，几乎不需要关注 onChange 方法。
2. FormDate 默认的 value 是时间戳，为 number 类型，13 位数字。传入 `unix={true}` value 是 10 位数字。
3. label 直接设置，其他 Antd 的 Form.Item 的属性，通过 itemProps 传入, 具体见[antd 文档](https://ant.design/components/form-cn/#Form.Item)
4. 所有接受选项的组件，统一成传入 `options` 数组, 每一项提供 title 和 value `{ title: string, value: number|string }`, 便于生成代码生成

```jsx
  <FormInput label="input label" itemProps={...customItemProps}>
```

## value 的取值规则

1. 默认 `_.get(mode, path)` (path 支持嵌套)
2. 组件提供，比如 `FormDate` 和 `FormDateRange` 会把 antd 返回的 moment 改为 timestamp

```jsx
function FormDate(props: IFormComponentProps) {// ...}
FormDate.transformModelToView = function(timestamp: number, props: IProps) {
  // 把model上的value，转成antd 需要的moment
};

FormDate.transformViewToModel = function(momentInst: Moment, props: IProps) {
  // 把 antd 组件的 moment 转成时间戳
};
```

3. 使用组件时传入`transformModelToView` 和 `transformViewToModel`

上面 3 个规则，优先级越来越高。

## 自定义

只要符合 value, onChange 模式的组件，可以快速融入这套体系，比如基于`react-color`的改造，只需要下面几行代码。

```jsx
import * as React from 'react';
import { FormRender } from '@jzl/auto-form';
import { SketchPicker, ColorResult } from 'react-color';
import { IRenderProps } from '@jzl/auto-form/lib/form-render';
import { ICommonFormOuterProps } from '@jzl/auto-form/lib/types';

export function FormColor<M>(rawProps: ICommonFormOuterProps<M>) {
  return (
    <FormRender
      {...rawProps}
      render={(props: IRenderProps<M>) => {
        function handleChange(e: ColorResult) {
          props.onChange(e.hex);
        }
        return (
          <SketchPicker onChangeComplete={handleChange} color={props.value} />
        );
      }}
    ></FormRender>
  );
}
```