资讯专栏INFORMATION COLUMN

rc-form之最单纯情况

ziwenxie / 1323人阅读

摘要:数据信息包括等元数据信息包括,校验规则等。第一次元数据一般得不到,内部会返回个空对象这里的简化后结果为,第一次为空。

前言

第一次探索这个框架,对于里面很多逻辑是不懂的,所以只能一点一点去揣摩,其中做了什么。
而学习过程中,总是禁不住好奇这里的逻辑是干什么的,那里的逻辑是什么的,在不理解这段逻辑是做什么的情况下,死磕很容易事倍功半。所以本次先从一个比较简单的场景入手,看看它的源码中做了什么手脚,至于有些逻辑没有涉及到的,先不去管它就好了。

探究内容 效果

首先上图,看看这次案例的效果。

其实是上一篇案例的精简版,去掉了非空验证。但是分析的更细致些。

业务代码

</>复制代码

  1. import React from "react";
  2. import { createForm, formShape } from "rc-form";
  3. class Form extends React.Component {
  4. static propTypes = {
  5. form: formShape,
  6. };
  7. componentWillMount() {
  8. this.nameDecorator = this.props.form.getFieldDecorator("name");
  9. }
  10. onSubmit = (e) => {
  11. e.preventDefault();
  12. this.props.form.validateFields((error, values) => {
  13. if (!error) {
  14. console.log("ok", values);
  15. } else {
  16. console.log("error", error, values);
  17. }
  18. });
  19. };
  20. onChange = (e) => {
  21. console.log(e.target.value);
  22. }
  23. render() {
  24. const { getFieldError } = this.props.form;
  25. return (
  26. {this.nameDecorator(
  27. )}
  28. {(getFieldError("name") || []).join(", ")}
  29. );
  30. }
  31. }
  32. const WrappedForm = createForm()(Form);
  33. export default WrappedForm;
源码分析

PS: 源码分析以代码+备注的形式展示

WrappedForm 概述

这个页面直接渲染了WrappedForm,所以我们不妨直接从WrappedForm看起。
其中WrappedForm是由rc-form提供的createForm创建的,第一个配置对象未传递,第二个参数是要修饰的组件。这里传递给了我们的业务组件

源码 createForm.js

</>复制代码

  1. import createBaseForm from "./createBaseForm";
  2. // 一系列给其他组件用的自定义混入
  3. export const mixin = {
  4. getForm() {
  5. // 这里需要注意的是this是动态的,所以这里的this.xxx会根据环境改变而改变
  6. return {
  7. getFieldsValue: this.fieldsStore.getFieldsValue,
  8. getFieldValue: this.fieldsStore.getFieldValue,
  9. getFieldInstance: this.getFieldInstance,
  10. setFieldsValue: this.setFieldsValue,
  11. setFields: this.setFields,
  12. setFieldsInitialValue: this.fieldsStore.setFieldsInitialValue,
  13. getFieldDecorator: this.getFieldDecorator,
  14. getFieldProps: this.getFieldProps,
  15. getFieldsError: this.fieldsStore.getFieldsError,
  16. getFieldError: this.fieldsStore.getFieldError,
  17. isFieldValidating: this.fieldsStore.isFieldValidating,
  18. isFieldsValidating: this.fieldsStore.isFieldsValidating,
  19. isFieldsTouched: this.fieldsStore.isFieldsTouched,
  20. isFieldTouched: this.fieldsStore.isFieldTouched,
  21. isSubmitting: this.isSubmitting,
  22. submit: this.submit,
  23. validateFields: this.validateFields,
  24. resetFields: this.resetFields,
  25. };
  26. },
  27. };
  28. function createForm(options) {
  29. // 这里调用了createBaseForm并将混入传入到该函数
  30. return createBaseForm(options, [mixin]);
  31. }
  32. export default createForm;

这里我们看到,其实createForm本身没做什么事情,只是在该文件内定义了混入的格式。
接下来我们的重点是createBaseForm中做了什么。这里只列出跟我们案例相关的内容

createBaseForm.js

</>复制代码

  1. //整体结构
  2. function createBaseForm(options={}, mixins={} ) {
  3. const {
  4. mapPropsToFields,
  5. onFieldsChange,
  6. // ****其他的optinos里面的值,下文可能会用到
  7. } = option;
  8. //此处的WrappedComponent就是我们示例中,被包裹的Form组件
  9. return funciton decorate(WrappedComponent) {
  10. const Form = createReactClass({
  11. // 该混入包含一个getForm方法用来得到一些通用方法
  12. mixins,
  13. getInitialState() {/*mark-init,初始化组件state*/}
  14. componentWillReceiveProps(nextProps) {/*mark-recProps,初始化部分数据*/}
  15. onCollect(){/*mark-collect收集表单数据*/}
  16. onCollectCommon() {}
  17. getCacheBind() {/*mark-bind组件事件绑定等收集*/}
  18. getFieldDecorator() {/*mark-deco装饰组件,促进双向绑定的修饰器*/}
  19. getFieldProps() {/*mark-props设置字段元数据,计算被修饰组件的属性*/}
  20. // 一些其他函数
  21. render() {
  22. const { wrappedComponentRef, ...restProps } = this.props;
  23. const formProps = {
  24. [formPropName]: this.getForm(),
  25. };
  26. // ** 精简本次分析无关的代码
  27. // 其中mapProps函数就是一个function(obj) {return obj};
  28. // 这里用了一个小技巧,就是call(this,xxx),直接将该组件上的核心方法,全都放到了子组件的属性上,而且由于该组件是createReactClass创建的,所以子组件(本例中的Form)调用这些从父组件获取的方法时,方法内部的this,指向当前组件。
  29. const props = mapProps.call(this, {
  30. ...formProps,
  31. ...restProps,
  32. });
  33. return ;
  34. },
  35. },
  36. })
  37. //简化静态方法转移部分
  38. return Form;
  39. }
  40. }

当createBaseForm函数在渲染函数中返回了我们的Form组件后,就可以看到Form组件中做的事情。

Form.js

</>复制代码

  1. class Form extends React.Component {
  2. static propTypes = {
  3. form: formShape,
  4. };
  5. componentWillMount() {
  6. // 上个文件,createBaseForm的装饰器函数decorate,生成的组件中渲染了该组件,
  7. // 并将getFieldDecorator方法通过属性传递给了它。
  8. this.nameDecorator = this.props.form.getFieldDecorator("name");
  9. }
  10. onSubmit = (e) => {
  11. e.preventDefault();
  12. this.props.form.validateFields((error, values) => {
  13. if (!error) {
  14. console.log("ok", values);
  15. } else {
  16. console.log("error", error, values);
  17. }
  18. });
  19. };
  20. onChange = (e) => {
  21. console.log(e.target.value);
  22. }
  23. render() {
  24. const { getFieldError } = this.props.form;
  25. return (
  26. {this.nameDecorator(
  27. )}
  28. {/*这里只是展示错误信息,不是我们关注点*/}
  29. {(getFieldError("name") || []).join(", ")}
  30. );
  31. }
  32. }

重点了。关键是看看getFieldDecorator中做了什么。

</>复制代码

  1. getFieldDecorator(name, fieldOption) {
  2. // 获取需要传递给被修饰元素的属性。包括onChange,value等
  3. // 同时在该props中设定用于收集元素值得监听事件(onChange),以便后续做双向数据。
  4. const props = this.getFieldProps(name, fieldOption);
  5. // 通过该函数传入(input/被修饰)元素。
  6. return (fieldElem) => {
  7. // 此处fieldStore存储字段数据信息以及元数据信息。
  8. // 数据信息包括value,errors,dirty等
  9. // 元数据信息包括initValue,defaultValue,校验规则等。
  10. const fieldMeta = this.fieldsStore.getFieldMeta(name);
  11. // 获取input上本身绑定的属性,例如该例子中的onChange打印内容函数
  12. const originalProps = fieldElem.props;
  13. fieldMeta.originalProps = originalProps;
  14. fieldMeta.ref = fieldElem.ref;
  15. return React.cloneElement(fieldElem, {
  16. ...props,
  17. ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
  18. });
  19. };
  20. },

其中getFieldProps值得我们关注

</>复制代码

  1. // 简化后的内容如下
  2. getFieldProps(name, usersFieldOption = {}) {
  3. // name为我们为该文本框起的name
  4. // 这里的数据fieldOption用来初始后面的FieldMeta。
  5. const fieldOption = {
  6. name,
  7. trigger: "onChange",
  8. valuePropName: "value", // checkBox取值时通过checked属性。
  9. ...usersFieldOption,
  10. };
  11. const {
  12. trigger,
  13. validateTrigger = trigger,
  14. } = fieldOption;
  15. // 第一次get元数据一般得不到,内部会返回个空对象
  16. const fieldMeta = this.fieldsStore.getFieldMeta(name);
  17. if ("initialValue" in fieldOption) {
  18. fieldMeta.initialValue = fieldOption.initialValue;
  19. }
  20. // 这里的inputProps简化后结果为{value: xxx},第一次为空。
  21. const inputProps = {
  22. ...this.fieldsStore.getFieldValuePropValue(fieldOption),
  23. };
  24. // make sure that the value will be collect
  25. // 这里用来在getFieldDecorator第二个参数为空时,确保给input绑定一个基本的onChange事件来收集input的修改值,最终放入fieldsStore中
  26. if (trigger && validateTriggers.indexOf(trigger) === -1) {
  27. inputProps[trigger] = this.getCacheBind(name, trigger, this.onCollect);
  28. }
  29. // 这里就是我们fieldsStore中设置的元数据
  30. const meta = {
  31. ...fieldMeta,
  32. ...fieldOption,
  33. };
  34. this.fieldsStore.setFieldMeta(name, meta);
  35. // 这里返回的{value: "xxx", onChange: fn};
  36. return inputProps;
  37. },

上述代码中onChange绑定了一个onCollect,其中getCacheBind函数主要是修正函数使用时候this指针。此处忽略直接分析onCollect

</>复制代码

  1. onCollect(name_, action, ...args) {
  2. // 通过onCollectCommon在input的onChange中触发,收集到该元素相关东西
  3. const { name, field, fieldMeta } = this.onCollectCommon(name_, action, args);
  4. const { validate } = fieldMeta;
  5. const newField = {
  6. ...field,
  7. dirty: hasRules(validate),//根据规则验证,此处可忽略
  8. };
  9. // 更新fieldStore中的值,主要触发了一个forceUpdate方法,重新渲染该组件
  10. this.setFields({
  11. [name]: newField,
  12. });
  13. },

最后关键的一步就是看看onCollectCommon做了什么

</>复制代码

  1. onCollectCommon(name, action, args) {
  2. const fieldMeta = this.fieldsStore.getFieldMeta(name);
  3. if (fieldMeta[action]) {
  4. fieldMeta[action](...args);
  5. } else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
  6. // 此处调用input原来的onChange事件,即打印输入值,这个originalProps是在getFieldDecorator函数中存下的,这里只不过拿来用了。
  7. fieldMeta.originalProps[action](...args);
  8. }
  9. // 此处的getValueFromEvent其实就是取e.target.value.
  10. const value = fieldMeta.getValueFromEvent ?
  11. fieldMeta.getValueFromEvent(...args) :
  12. getValueFromEvent(...args);
  13. const field = this.fieldsStore.getField(name);
  14. return ({ name, field: { ...field, value, touched: true }, fieldMeta });
  15. },

至此整个数据流程基本跑通,onChange触发onCollect去改变fieldStore中的值并forceUpdate更新界面,onCollectCommon则展示了onCollect取值的细节。forceUpdate更新组件后,触发Formrender方法,又开始了之前getFieldDecorator 中读取fieldStore中值,返回被修改后的组件的流程。

题外话

跑通了最简单的场景,就可以向下一步更复杂的场景探索了。

结语

至此一个最简单的流程已经分析完毕。接下来,需要考虑的就是表单验证,数据反显(从后端拿到数据渲染编辑页面)等等,一步一个脚印,慢慢来吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95979.html

相关文章

  • python编程分析了一下高考那些事,发现了两个之最,原来是这样

    摘要:它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。代码这是自年恢复高考以来到年的高考报考及录取数据。为了直观展示,对录取率做了尺度上的变换。 Python(发音:英[?pa?θ?n],美[?pa?θɑ:n]),是一种面向对象、直译式电脑编程语言,也是一种功能强大的通用型语言,已经具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常...

    Jingbin_ 评论0 收藏0
  • js算法之最常用的排序

    摘要:算法之最常用的排序参加百度前端的课程真的是好多知识点不知道。快速排序也是在实际中最常用的一种排序算法,速度快,效率高。插入排序的思路很简单,很清晰,是一种最常见最简单的排序方法。 js算法之最常用的排序 参加百度前端的课程真的是好多知识点不知道。边学边做题,在问题中学习,知识点从点到面,但是要善于总结记录才行。加油吧,骚年! 可视化排序网站 时间复杂度是衡量一个算法效率的基本方法我们把...

    宠来也 评论0 收藏0
  • Ant design pro 开发笔记 - 表单和数据绑定

    摘要:支持表单双向绑定,开发过程中无需通过回调函数去获取组件的值,通过可以自动完成数据绑定的功能。如果我们通过获取了数据之后,表单数据不会发生变化。注意使用这个函数必须用封装需要绑定的字段。 antd支持表单双向绑定,开发过程中无需通过onChange()回调函数去获取组件的值,通过 getFieldDecorator() 可以自动完成数据绑定的功能。 { getFieldDecor...

    zsy888 评论0 收藏0
  • Ant-Design-组件-——-Form表单(一)

    摘要:擅长网站建设微信公众号开发微信小程序开发小游戏制作企业微信制作建设,专注于前端框架服务端渲染技术交互设计图像绘制数据分析等研究。 Ant Design of React @3.10.9 拉取项目 luwei.web.study-ant-design-pro, 切换至 query 分支,可看到 Form 表单实现效果 实现一个查询表单 showImg(https://segmentfau...

    trilever 评论0 收藏0
  • 较常用的Math方法及ES6中的扩展

    摘要:记录下与有关的常用方法,如求最大值最小值等,或者是保留几位数啥的数据求最大值最小值求最小值使用来重新绑定使用展开运算符求最大值使用来重新绑定使用展开运算符取整四舍五入取整取与参数最接近的整数向上取整取大于或等于函数参数,并且与之最接近的 记录下与Math有关的常用方法,如:求最大值、最小值等,或者是保留几位数啥的 1.数据 let floatA = 2.325232; let floa...

    MudOnTire 评论0 收藏0

发表评论

0条评论

ziwenxie

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<