深刻學習並手寫 React Ant Design4 表單核心庫 rc-field-form

前言

最近有一個很是複雜的表單需求,可能須要對錶單作「任何事情」,現有的 UI 組件庫選用的是 Ant Design 簡稱 antd 。它的 Form 表單已經幫咱們把「表單項校驗」、「表單項錯誤信息」等常見操做所有封裝好了。使用起來很是便捷。翻看了 antd Form  源碼發現其核心能力都是經過 rc-field-form 庫,提供出來的。所以閱讀它的源碼將是做者項目開始前必需要作的。

本文將模擬 rc-field-form 庫,手寫一個「學習版」 ,深刻學習其思想。javascript

若是本文對你有所幫助,請點個👍 吧!html

工程搭建

rc-field-form 使用的是 Dumi  和 father-build 對組件庫進行打包,爲了保持一致,做者也將使用這兩個工具來完成項目。前端

Dumi

dumi 中文發音嘟米,是一款爲組件開發場景而生的文檔工具,與 father-builder 一塊兒爲開發者提供一站式的組件開發體驗, father-builder 負責構建,而 dumi 負責組件開發及組件文檔生成。
java

father-build

father-build 屬於 father (集文檔與組件打包一體的庫)的一部分,專一於組件打包。
node

腳手架建立項目

使用 @umijs/create-dumi-lib 來初始化項目。這個腳手架整合了上面說起的兩個工具。react

mkdir lion-form // 建立lion-form文件夾
cd lion-form // 進入文件夾
npm init -y // 初始化 package.json
npx @umijs/create-dumi-lib // 初始化總體項目結構
複製代碼

項目結構說明

├──README.md // 文檔說明
├──node_modules // 依賴包文件夾
├──package.json // npm 包管理
├──.editorconfig // 編輯器風格統一配置文件
├──.fatherrc.ts // 打包配置
├──.umirc.ts // 文檔配置
├──.prettierrc // 文本格式化配置
├──tsconfig.json // ts 配置
└──docs // 倉庫公共文檔
	└──index.md // 組件庫文檔首頁
└──src
	└──index.js // 組件庫入口文件
複製代碼

啓動項目

npm start 或 yarn start 
複製代碼

image.png
集文檔,打包爲一體的組件庫就這樣快速的搭建完成了。下面就讓咱們來手寫一個 rc-field-form  吧。

完整代碼地址git

源碼編寫

rc-field-form

對於常用 react 開發的同窗來講, antd 應該都不會陌生。開發中常常遇到的表單大多會使用 antd 中的 Form 系列組件完成,而 rc-field-form 又是 antd Form 的重要組成部分,或者說 antd Form 是對 rc-field-form 的進一步的封裝。

想要學習它的源碼,首先仍是得知道如何使用它,否則難以理解源碼的一些深層次的含義。
github

簡單的示例

首先來實現以下圖所示的表單,相似於咱們寫過的登陸註冊頁面。
image.png

代碼示例:npm

import React, { Component, useEffect} from 'react'
import Form, { Field } from 'rc-field-form'
import Input from './Input'
// name 字段校驗規則
const nameRules = {required: true, message: '請輸入姓名!'}
// password 字段校驗規則
const passwordRules = {required: true, message: '請輸入密碼!'}

export default function FieldForm(props) {
  // 獲取 form 實例
  const [form] = Form.useForm()
  // 提交表單時觸發
  const onFinish = (val) => {
    console.log('onFinish', val)
  }
  // 提交表單失敗時觸發
  const onFinishFailed = (val) => {
    console.log('onFinishFailed', val)
  }
  // 組件初始化時觸發,它是React原生Hook
  useEffect(() => {
    form.setFieldsValue({username: 'lion'})
  }, [])
	
  return (
    <div> <h3>FieldForm</h3> <Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}> <Field name='username' rules={[nameRules]}> <Input placeholder='請輸入姓名' /> </Field> <Field name='password' rules={[passwordRules]}> <Input placeholder='請輸入密碼' /> </Field> <button>Submit</button> </Form> </div>
  )
}

// input簡單封裝
const Input = (props) => {
  const { value,...restProps } = props;
  return <input {...restProps} value={value} />;
};
複製代碼

這種寫法仍是很是便捷的,再也不須要像 antd3 同樣使用高階函數包裹一層。而是直接經過 Form.useForm() 獲取到 formInstance 實例, formInstance 實例身上承載了表單須要的全部數據及方法。

經過 form.setFieldsValue({username: 'lion'}) 這段代碼就不難發現,能夠經過 form 去手動設置 username 的初始值。也能夠理解成全部的表單項都被 formInstance 實例接管了,可使用 formInstance 實例作到任何操做表單項的事情。 formInstance 實例也是整個庫的核心。json

基礎框架搭建

經過對 rc-field-form 源碼的學習,咱們先來搭建一個基礎框架。

useForm

  • 經過 Form.useForm() 獲取 formInstance  實例;
  • formInstance 實例對外提供了全局的方法如 setFieldsValue 、 getFieldsValue ;
  • 經過 context 讓全局能夠共享 formInstance 實例。


src/useForm.tsx 

import React , {useRef} from "react";

class FormStore {
  // stroe 用來存儲表單數據,它的格式:{"username": "lion"}
  private store: any = {};
  // 用來存儲每一個 Field 的實例數據,所以在store中能夠經過 fieldEntities 來訪問到每一個表單項
  private fieldEntities: any = [];

  // 表單項註冊到 fieldEntities
  registerField = (entity:any)=>{
    this.fieldEntities.push(entity)
    return () => {
      this.fieldEntities = this.fieldEntities.filter((item:any) => item !== entity)
      delete this.store[entity.props.name]
    }
  }
  // 獲取單個字段值
  getFieldValue = (name:string) => {
    return this.store[name]
  }
  // 獲取全部字段值
  getFieldsValue = () => {
    return this.store
  }
  // 設置字段的值
  setFieldsValue = (newStore:any) => {
    // 更新store的值
    this.store = {
      ...this.store,
      ...newStore,
    }
  // 經過 fieldEntities 獲取到全部表單項,而後遍歷去調用表單項的 onStoreChange 方法更新表單項
    this.fieldEntities.forEach((entity:any) => {
      const { name } = entity.props
      Object.keys(newStore).forEach(key => {
        if (key === name) {
          entity.onStoreChange()
        }
      })
    })
  }
  // 提交數據,這裏只簡單的打印了store中的數據。
  submit = ()=>{
    console.log(this.getFieldsValue());
  }
  // 提供FormStore實例方法
  getForm = (): any => ({
    getFieldValue: this.getFieldValue,
    getFieldsValue: this.getFieldsValue,
    setFieldsValue: this.setFieldsValue,
    registerField: this.registerField,
    submit: this.submit,
  });
}
// 建立單例formStore
export default function useForm(form:any) {
  const formRef = useRef();
  if (!formRef.current) {
    if (form) {
      formRef.current = form;
    } else {
      const formStore = new FormStore();
      formRef.current = formStore.getForm() as any;
    }
  }
  return [formRef.current]
}
複製代碼

其中 FormStore 是用來存儲全局數據和方法的。 useForm 是對外暴露 FormStore 實例的。從 useForm  的實現能夠看出,藉助 useRef 實現了 FormStore 實例的單例模式。

FieldContext

定義了全局 context 。

import * as React from 'react';

const warningFunc: any = () => {
  console.log("warning");
};

const Context = React.createContext<any>({
  getFieldValue: warningFunc,
  getFieldsValue: warningFunc,
  setFieldsValue: warningFunc,
  registerField: warningFunc,
  submit: warningFunc,
});

export default Context;
複製代碼

Form 組件

  • 傳遞 FieldContext
  • 攔截處理 submit 事件;
  • 渲染子節點。


src/Form.tsx 

import React from "react";
import useForm from "./useForm";
import FieldContext  from './FieldContext';

export default function Form(props:any) {
  const {form, children, ...restProps} = props;
  const [formInstance] = useForm(form) as any;
	
  return <form {...restProps} onSubmit={(event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); event.stopPropagation(); // 調用了formInstance 提供的submit方法 formInstance.submit(); }} > {/* formInstance 當作全局的 context 傳遞下去 */} <FieldContext.Provider value={formInstance}>{children}</FieldContext.Provider> </form> } 複製代碼

Field 組件

  • 把本身註冊到 FormStore 中;
  • 攔截子元素爲其添加 value 以及 onChange 屬性。


src/Field.tsx 

import React,{Component} from "react";
import FieldContext from "./FieldContext";

export default class Field extends Component {
  // Filed 組件獲取 FieldContext
  static contextType = FieldContext;

  private cancelRegisterFunc:any;
  // Field 掛載時,把本身註冊到FieldContext中,也就是上面說起的 fieldEntities 數組中。
  componentDidMount() {
    const { registerField } = this.context;
    this.cancelRegisterFunc = registerField(this);
  }
  // Field 組件卸載時,調用取消註冊,就是從 fieldEntities 中刪除。
  componentWillUnmount() {
    if (this.cancelRegisterFunc) {
      this.cancelRegisterFunc()
    }
  }
  // 每一個 Field 組件都應該包含 onStoreChange 方法,用來更新本身
  onStoreChange = () => {
    this.forceUpdate()
  }
  // Field 中傳進來的子元素變爲受控組件,也就是主動添加上 value 和 onChange 屬性方法
  getControlled = () => {
    const { name } = this.props as any;
    const { getFieldValue, setFieldsValue } = this.context
    return {
      value: getFieldValue(name),
      onChange: (event:any) => {
        const newValue = event.target.value
        setFieldsValue({[name]: newValue})
      },
    }
  }
	
  render() {
    const {children} = this.props as any;
    return React.cloneElement(children, this.getControlled())
  }
}
複製代碼

Form 組件的基礎框架就此搭建完成了,它已經能夠實現一些簡單的效果,下面咱們在 docs 目錄寫個例子。

docs/examples/basic.tsx 

...省略了部分代碼

export default function BasicForm(props) {
  const [form] = Form.useForm()

  useEffect(() => {
    form.setFieldsValue({username: 'lion'})
  }, [])

  return (
    <Form form={form}> <Field name='username'> <Input placeholder='請輸入姓名' /> </Field> <Field name='password'> <Input placeholder='請輸入密碼' /> </Field> <button>提交</button> </Form>
  )
}
複製代碼

解析:

  1. 組件初始化時調用 form.setFieldsValue({username: 'lion'}) 方法;
  2. setFieldsValue 根據傳入的參數,更新了 store 值,並經過 name 找到相應的 Field 實例;
  3. 調用 Field 實例的 onStoreChange 方法,更新組件;
  4. 組件更新,初始值就展現到界面上了。


image.png

點擊查看本小節代碼

Form

Form 組件獲取 ref

antd 文檔上有這麼一句話:「咱們推薦使用 Form.useForm 建立表單數據域進行控制。若是是在 class component 下,你也能夠經過 ref 獲取數據域」。

使用方式以下:

export default class extends React.Component {
  formRef = React.createRef()

  componentDidMount() {
    this.formRef.current.setFieldsValue({username: 'lion'})
  }

  render() {
    return (
      <Form ref={this.formRef}> <Field name='username'> <Input /> </Field> <Field name='password'> <Input /> </Field> <button>Submit</button> </Form>
    )
  }
}
複製代碼

經過傳遞 formRef 給 Form 組件。獲取 Form 的 ref 實例,可是咱們知道 Form 是經過函數組件建立的,函數組件沒有實例,沒法像類組件同樣能夠接收 ref 。所以須要藉助 React.forwardRef 與 useImperativeHandle 。

src/Form.tsx 

export default React.forwardRef((props: any, ref) => {
  ... 省略
  const [formInstance] = useForm(form) as any;

  React.useImperativeHandle(ref, () => formInstance);
  
  ... 省略
})

複製代碼
  • React.forwardRef 解決了,函數組件沒有實例,沒法像類組件同樣能夠接收 ref 屬性的問題;
  • useImperativeHandle 可讓你在使用 ref 時,決定暴露什麼給父組件,這裏咱們將 formInstance 暴露出去,這樣父組件就可使用 formInstance 了。


關於 React Hooks 不熟悉的同窗能夠閱讀做者的這篇文章:React Hook 從入門應用到編寫自定義 Hook

點擊查看本小節代碼

初始值 initialValues

以前咱們都是這樣去初始化表單的值:

useEffect(() => {
  form.setFieldsValue({username: 'lion'})
}, [])
複製代碼

顯然這樣初始化是不夠優雅的,官方提供了 initialValues 屬性讓咱們去初始化表單項的,下面讓咱們來支持它吧。

src/useForm.ts 

class FormStore {
  // 定義初始值變量
  private initialValues = {}; 

  setInitialValues = (initialValues:any,init:boolean)=>{
    // 初始值賦給initialValues變量,這樣 formInstance 就一直會保存一份初始值
    this.initialValues = initialValues;
    // 同步給store
    if(init){
      // setValues 是rc-field-form提供的工具類,做者這裏所有copy過來了,不用具體關注工具類的實現
      // 這裏知道 setValues 會遞歸遍歷 initialValues 返回一個新的對象。
      this.store = setValues({}, initialValues, this.store);
    }
  }	
  
  getForm = (): any => ({
    ... 這裏省略了外部使用方法
    
    // 建立一個方法,返回內部使用的一些方法
    getInternalHooks:()=>{
      return {
        setInitialValues: this.setInitialValues,
      }
    }
  });
}
複製代碼

src/Form.tsx 

export default React.forwardRef((props: any, ref) => {
  const [formInstance] = useForm(form) as any;
  const {
    setInitialValues,
  } = formInstance.getInternalHooks();
  
  // 第一次渲染時 setInitialValues 第二個參數是true,表示初始化。之後每次渲染第二個參數都爲false
  const mountRef = useRef(null) as any;
  setInitialValues(initialValues, !mountRef.current);
  if (!mountRef.current) {
    mountRef.current = true;
  }
  
  ...
}
複製代碼

useRef 返回一個可變的 ref 對象,其 current 屬性被初始化爲傳入的參數( initialValue )。返回的 ref 對象在組件的整個生命週期內保持不變。

點擊查看本小節代碼

submit

在此以前,提交 submit 只能打印 store 裏面的值,這並不能知足咱們的需求,咱們須要它能夠回調指定函數。

src/useForm.ts

class FormStore {
  private callbacks = {} as any; //用於存放回調方法

  // 設置callbases
  setCallbacks = (callbacks:any) => {
    this.callbacks = callbacks;
  }

  // 暴露setCallbacks方法到全局
  getForm = (): any => ({
  	...
    getInternalHooks: () => {
      return {
        setInitialValues: this.setInitialValues,
        setCallbacks: this.setCallbacks
      };
    },
  });
  
  // submit 時,去callbacks中取出須要回調方法執行
  submit = () => {
    const { onFinish } = this.callbacks;
    onFinish(this.getFieldsValue())
  };
}
複製代碼

src/Form.tsx

export default React.forwardRef((props: any, ref) => {
  const { ..., onFinish, ...restProps } = props;
  const [formInstance] = useForm(form) as any;
  const {
    setCallbacks,
  } = formInstance.getInternalHooks();
  // 獲取外部傳入的onFinish函數,註冊到callbacks中,這樣submit的時候就會執行它
  setCallbacks({
    onFinish
  })
  
  ...
}
複製代碼

點擊查看本小節代碼

Field

shouldUpdate

經過 shouldUpdate 屬性控制 Field 的更新邏輯。當 shouldUpdate 爲方法時,表單的每次數值更新都會調用該方法,提供原先的值與當前的值以供你比較是否須要更新。

src/Field.tsx 

export default class Field extends Component {
  // 只改造這一個函數,根據傳入的 shouldUpdate 函數的返回值來判斷是否須要更新。
  onStoreChange = (prevStore:any,curStore:any) => {
    const { shouldUpdate } = this.props as any;
    if (typeof shouldUpdate === 'function') {
      if(shouldUpdate(prevStore,curStore)){
        this.forceUpdate();
      }
    }else{
      this.forceUpdate();
    }
  }	
  
}
複製代碼

src/useForm.js 

class FormStore {
  // 以前寫了一個registerField是用來設置Field實例的存儲,再添加一個獲取的方法
  getFieldEntities = ()=>{
    return this.fieldEntities;
  }
  // 新增一個方法,用來通知Field組件更新
  notifyObservers = (prevStore:any) => {
    this.getFieldEntities().forEach((entity: any) => {
      const { onStoreChange } = entity;
      onStoreChange(prevStore,this.getFieldsValue());
    });
  }
  // 如今設置字段值以後直接調用 notifyObservers 方法進行更新組件
  setFieldsValue = (curStore: any) => {
    const prevStore = this.store;
    if (curStore) {
      this.store = setValues(this.store, curStore);
    }
    this.notifyObservers(prevStore);
  };  
}
複製代碼

好了更新的邏輯也差很少寫完了,雖然並不是跟原庫保持一致(原庫考慮了更多的邊界條件),可是足矣幫助咱們理解其思想。

點擊查看本小節代碼

表單驗證

根據用戶設置的校驗規則,在提交表單時或者任何其餘時候對錶單進行校驗並反饋錯誤。
讀源碼的時候發現,底層作校驗使用的是 async-validator 作的。

async-validator

它是一個能夠對數據進行異步校驗的庫, ant.design 與 Element ui 的 Form 組件都使用了它作底層校驗。

安裝

npm i async-validator
複製代碼

基本用法

import AsyncValidator from 'async-validator'
// 校驗規則
const descriptor = {
  username: [
    {
      required: true,
      message: '請填寫用戶名'
    },
    {
      pattern: /^\w{6}$/
      message: '用戶名長度爲6'
    }
  ]
}
// 根據校驗規則構造一個 validator
const validator = new AsyncValidator(descriptor)
const data = {
  username: 'username'
}
validator.validate(data).then(() => {
  // 校驗經過
}).catch(({ errors, fields }) => {
  // 校驗失敗
});
複製代碼

關於 async-validator 詳細使用方式能夠查閱它的 github 文檔

Field 組件設置校驗規則

<Field
	label="Username"
	name="username"
	rules={[
           { required: true, message: 'Please input your username!' },
           { pattern: /^\w{6}$/ }
	]}
    >
	<Input />
    </Form.Item>
複製代碼

若是校驗不經過,則執行 onFinishFailed  回調函數。

[注意] 原庫還支持在 rules 中設置自定義校驗函數,本組件中已省略。

組件改造

src/useForm.ts 

class FormStore {
  // 字段驗證
  validateFields = ()=>{
    // 用來存放字段驗證結果的promise
    const promiseList:any = [];
    // 遍歷字段實例,調用Field組件的驗證方法,獲取返回的promise,同時push到promiseList中
    this.getFieldEntities().forEach((field:any)=>{
      const {name, rules} = field.props
      if (!rules || !rules.length) {
        return;
      }
      const promise = field.validateRules();
      promiseList.push(
        promise
          .then(() => ({ name: name, errors: [] }))
          .catch((errors:any) =>
            Promise.reject({
              name: name,
              errors,
            }),
          ),
      );
    })
    // allPromiseFinish 是一個工具方法,處理 promiseList 列表爲一個 promise
    // 大體邏輯:promiseList 中只要有一個是 rejected 狀態,那麼輸出的promise 就應該是 reject 狀態
    const summaryPromise = allPromiseFinish(promiseList);
    const returnPromise = summaryPromise
      .then(
        () => {
          return Promise.resolve(this.getFieldsValue());
        },
      )
      .catch((results) => {
        // 合併後的promise若是是reject狀態就返回錯誤結果
        const errorList = results.filter((result:any) => result && result.errors.length);
        return Promise.reject({
          values: this.getFieldsValue(),
          errorFields: errorList
        });
      });

    // 捕獲錯誤
    returnPromise.catch(e => e);
	
    return returnPromise;
  }
  // 提交表單的時候進行調用字段驗證方法,驗證經過回調onFinish,驗證失敗回調onFinishFailed
  submit = () => {
    this.validateFields()
      .then(values => {
        const { onFinish } = this.callbacks;
        if (onFinish) {
          try {
            onFinish(values);
          } catch (err) {
            console.error(err);
          }
        }
      })
      .catch(e => {
        const { onFinishFailed } = this.callbacks;
        if (onFinishFailed) {
          onFinishFailed(e);
        }
      });
  };
}
複製代碼

如今的核心問題就是 Field 組件如何根據 value 和 rules 去獲取校驗結果。

src/Field.tsx 

export default class Field extends Component {
  private validatePromise: Promise<string[]> | null = null
  private errors: string[] = [];
  // Field組件根據rules校驗的函數
  validateRules = ()=>{
    const { getFieldValue } = this.context;
    const { name } = this.props as any;
    const currentValue = getFieldValue(name); // 獲取到當前的value值
    // async-validator 庫的校驗結果是 promise
    const rootPromise = Promise.resolve().then(() => {
      // 獲取全部rules規則
      let filteredRules = this.getRules();
      // 獲取執行校驗的結果promise
      const promise = this.executeValidate(name,currentValue,filteredRules);
      promise
        .catch(e => e)
        .then((errors: string[] = []) => {
          if (this.validatePromise === rootPromise) {
            this.validatePromise = null;
            this.errors = errors; // 存儲校驗結果信息
            this.forceUpdate(); // 更新組件
          }
        });
      return promise;
    });
    this.validatePromise = rootPromise;
    return rootPromise;
  }
  // 獲取 rules 校驗結果
  public getRules = () => {
    const { rules = [] } = this.props as any;
    return rules.map(
      (rule:any) => {
        if (typeof rule === 'function') {
          return rule(this.context);
        }
        return rule;
      },
    );
  };  
  // 執行規則校驗
  executeValidate = (namePath:any,value:any,rules:any)=>{
    let summaryPromise: Promise<string[]>;
    summaryPromise = new Promise(async (resolve, reject) => {
      // 多個規則遍歷校驗,只要有其中一條規則校驗失敗,就直接不須要往下進行了。返回錯誤結果便可。
      for (let i = 0; i < rules.length; i += 1) {
        const errors = await this.validateRule(namePath, value, rules[i]);
        if (errors.length) {
          reject(errors);
          return;
        }
      }
      resolve([]);
    });
    return summaryPromise;
  }  
  // 對單挑規則進行校驗的方法
  validateRule = async (name:any,value:any,rule:any)=>{
    const cloneRule = { ...rule };
    // 根據name以及校驗規則生成一個校驗對象
    const validator = new RawAsyncValidator({
      [name]: [cloneRule],
    });
    let result = [];
    try {
      // 把value值傳入校驗對象,進行校驗,返回校驗結果
      await Promise.resolve(validator.validate({ [name]: value }));
    }catch (e) {
      if(e.errors){
        result = e.errors.map((c:any)=>c.message)
      }
    }
    return result;
  }	  
}
複製代碼

到此爲止咱們就完成了一個簡單的 Form 表單邏輯模塊的編寫。本文每小節的代碼均可以在 github 上查看,並且在 dosc 目錄下有相應的使用案例能夠查看。

點擊查看本小節代碼

線上發佈

發佈到 npm

前面介紹過了,這個項目採用的是 dumi + father-builder 工具,所以在發佈到 npm 這塊是特別方便的,在登陸 npm 以後,只須要執行 npm run release 便可。

線上包地址:lion-form

本地項目經過執行命令 npm i lion-form 便可使用。

發佈組件庫文檔

一、配置 .umirc.ts 

import { defineConfig } from 'dumi';

let BaseUrl = '/lion-form'; // 倉庫的路徑

export default defineConfig({
  // 網站描述配置
  mode: 'site',
  title: 'lion form',
  description: '前端組件開發。',
  // 打包路徑配置
  base: BaseUrl,
  publicPath: BaseUrl + '/', // 打包文件時,引入地址生成 BaseUrl/xxx.js
  outputPath: 'docs-dist',
  exportStatic: {}, // 對每隔路由輸出html
  dynamicImport: {}, // 動態導入
  hash: true, //加hash配置,清除緩存
  manifest: {
    // 內部發布系統規定必須配置
    fileName: 'manifest.json',
  },
  // 多國語順序
  locales: [
    ['en-US', 'English'],
    ['zh-CN', '中文'],
  ],
  // 主題
  theme: {
    '@c-primary': '#16c35f',
  },
});
複製代碼

配置完成後,執行 npm run deploy 命令。

二、設置 github pages 
image.png
image.png

設置完成後,再次執行 npm run deploy ,便可訪問線上組件庫文檔地址

總結

本文從工程搭建,源碼編寫以及線上發佈這幾個步驟去描述如何完整的編寫一個 React 通用組件庫。

經過 Form 組件庫的編寫也讓咱們學習到:

  • Form 組件, Field 組件是經過一個全局的 context 做爲紐帶關聯起來的,它們共享 FormStore 中的數據方法,很是相似 redux 工做原理。
  • 經過把每一個 Field 組件實例註冊到全局的 FormStore 中,實現了在任意位置調用 Field 組件實例的屬性和方法,這也是爲何 Field  使用 class 組件編寫的緣由(由於函數組件沒有實例)。
  • 最後也藉助了 async-validator 實現了表單驗證的功能。


學習優秀開源庫的源碼過程是不開心的,可是收穫會是很是大的, Dont Worry Be Happy 。

相關文章
相關標籤/搜索