最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入学习并手写 React Ant Design4 表单核心库 rc-field-form

    正文概述 掘金(Lion)   2021-01-28   1828

    前言

    最近有一个非常复杂的表单需求,可能需要对表单做“任何事情”,现有的 UI 组件库选用的是 Ant Design 简称 antd 。它的 Form 表单已经帮我们把“表单项校验”、“表单项错误信息”等常见操作全部封装好了。使用起来非常便捷。翻看了 antd Form  源码发现其核心能力都是通过 rc-field-form 库,提供出来的。因此阅读它的源码将是作者项目开始前必须要做的。

    本文将模拟 rc-field-form 库,手写一个“学习版” ,深入学习其思想。

    如果本文对你有所帮助,请点个? 吧!

    工程搭建

    rc-field-form 使用的是 Dumi  和 father-build 对组件库进行打包,为了保持一致,作者也将使用这两个工具来完成项目。

    Dumi

    dumi 中文发音嘟米,是一款为组件开发场景而生的文档工具,与 father-builder 一起为开发者提供一站式的组件开发体验, father-builder 负责构建,而 dumi 负责组件开发及组件文档生成。

    father-build

    father-build 属于 father (集文档与组件打包一体的库)的一部分,专注于组件打包。

    脚手架创建项目

    使用 @umijs/create-dumi-lib 来初始化项目。这个脚手架整合了上面提及的两个工具。

    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 
    

    深入学习并手写 React Ant Design4 表单核心库 rc-field-form
    集文档,打包为一体的组件库就这样快速的搭建完成了。下面就让我们来手写一个 rc-field-form  吧。

    完整代码地址

    源码编写

    rc-field-form

    对于经常使用 react 开发的同学来说, antd 应该都不会陌生。开发中经常遇到的表单大多会使用 antd 中的 Form 系列组件完成,而 rc-field-form 又是 antd Form 的重要组成部分,或者说 antd Form 是对 rc-field-form 的进一步的封装。

    想要学习它的源码,首先还是得知道如何使用它,不然难以理解源码的一些深层次的含义。

    简单的示例

    首先来实现如下图所示的表单,类似于我们写过的登录注册页面。
    深入学习并手写 React Ant Design4 表单核心库 rc-field-form

    代码示例:

    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 实例也是整个库的核心。

    基础框架搭建

    通过对 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. 组件更新,初始值就展示到界面上了。


    深入学习并手写 React Ant Design4 表单核心库 rc-field-form

    点击查看本小节代码

    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 即可使用。

    发布组件库文档

    1、配置 .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 命令。

    2、设置 github pages 
    深入学习并手写 React Ant Design4 表单核心库 rc-field-form
    深入学习并手写 React Ant Design4 表单核心库 rc-field-form

    设置完成后,再次执行 npm run deploy ,即可访问线上组件库文档地址。

    总结

    本文从工程搭建,源码编写以及线上发布这几个步骤去描述如何完整的编写一个 React 通用组件库。

    通过 Form 组件库的编写也让我们学习到:

    • Form 组件, Field 组件是通过一个全局的 context 作为纽带关联起来的,它们共享 FormStore 中的数据方法,非常类似 redux 工作原理。
    • 通过把每个 Field 组件实例注册到全局的 FormStore 中,实现了在任意位置调用 Field 组件实例的属性和方法,这也是为什么 Field  使用 class 组件编写的原因(因为函数组件没有实例)。
    • 最后也借助了 async-validator 实现了表单验证的功能。


    学习优秀开源库的源码过程是不开心的,但是收获会是非常大的, Dont Worry Be Happy 。


    起源地下载网 » 深入学习并手写 React Ant Design4 表单核心库 rc-field-form

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元