手写实现 rc-field-form (五)

510 阅读3分钟

「这是我参与2022首次更文挑战的第39天,活动详情查看:2022首次更文挑战」。

写在前面

  • 前面一篇文章,我们已经实现,在 Form 组件中获取数据仓库的操作权限对象,然后通过 Context 将其传递给子孙组件,最后在 Field 组件中接收到数据仓库的操作权限,并接管 Field 组件的 children 的 onChange 事件,完成 Form 组件中所有数据统一管理的需求
  • 但是前面只是实现了,Field 组件 children 与数据仓库的交互,数据仓库数据更新后,相应的组件该如何进行更新渲染呢?
  • 下面我们就来解决这个问题

Field 组件响应数据更新

  • 为了解决组件响应数据更新的问题,我的大体思路就是:
    • 在每个 Field 组件上,定义一个组件更新渲染的方法
    • 在数据仓库中注册活跃的 Field 组件实例
    • 然后数据仓库的数据发生更新后,立即调用 Field 组件实例的更新方法,更新组件渲染
  • 接下来我们来实现它
export class Field extends Component {
  static contextType = FormContext;

  // 将接收的组件变为受控组件
  getControlled = () => {
    const { name } = this.props;
    const { setFieldValue, getFieldValue } = this.context;

    return {
      value: getFieldValue(name),
      onChange(e) {
        const newVal = e.target.value;
        setFieldValue(name, newVal);
      },
    };
  };

  // 数据改变时,更新组件的方法
  onStoreChange = () => {
    this.forceUpdate();
  };

  componentDidMount() {
    // 在 store 中注册 field 组件的 实例
    this.unRegister = this.context.registerField(this);
  }

  // 注销 store 中的组件实例
  componentWillUnmount() {
    this.unRegister();
  }

  render() {
    const controlledChild = React.cloneElement(
      this.props.children,
      this.getControlled()
    );
    return controlledChild;
  }
}
  • 可以看到,上面增加了几个方法
    • onStoreChange,就是我们的 Field 组件更新渲染方法
    • context.registerField,就是我们注册 Field 组件实例的方法,下面会实现它
    • unRegister,就是我们注销不活跃 Field 组件实例的方法,它是 context.registerField 方法返回的结果函数

完善数据仓库的操作权限

  • 回到数据仓库的代码中,实现上面提到的 registerField 方法
import { useRef } from "react";

// 1、实现一个专属 Form 的状态管理库
class FormStore {
  constructor() {
    this.store = {}; // 用于存放组件数据

    this.fieldEntities = []; // 用于存放 field 组件实例
  }

  // 2、实现操作状态的方法 get set
  // 2.1、多个字段的 set 操作
  setFieldsValue = (newStore) => {
    // 先更新 store 中的数据
    this.store = {
      ...this.store,
      ...newStore,
    };

    // 然后更新组件
    this.fieldEntities.forEach((instance) => {
      Object.keys(newStore).forEach((name) => {
        // 只有当 newStore 中的相关 name 的组件的状态发生改变时,才更新对应的组件
        if (name === instance.props.name) {
          instance.onStoreChange();
        }
      });
    });
  };

  // 2.2、多个字段的 get 操作
  getFieldsValue = () => ({ ...this.store });

  // 2.3、单个字段的 set 操作
  setFieldValue = (field, val) => {
    this.setFieldsValue({ [field]: val });
  };

  // 2.4、单个字段的 get 操作
  getFieldValue = (fieldName) => this.store[fieldName];

  // 3、注册需要更新的组件实例
  registerField = (fieldInstance) => {
    this.fieldEntities.push(fieldInstance);

    // 返回一个函数,用于注销已注册的组件实例,并清空 store 中的相关数据
    return () => {
      this.fieldEntities = this.fieldEntities.filter(
        (instance) => instance !== fieldInstance
      );

      delete this.store[fieldInstance.props.name];
    };
  };

  // 暴露操作状态的方法
  getForm = () => ({
    getFieldsValue: this.getFieldsValue,
    setFieldsValue: this.setFieldsValue,
    setFieldValue: this.setFieldValue,
    getFieldValue: this.getFieldValue,
    registerField: this.registerField,
  });
}
  • 既然要注册 Field 组件实例,那么我们就需要初始化一个 fieldEntities 用于存放实例
  • 在上面的代码中 registerField 方法
    • 接收一个 Field 组件实例,然后直接 push 到 fieldEntities 中完成注册
    • 然后返回注销 Field 组件实例的方法
  • 有了 Field 组件实例,那么响应数据更新就好办了
  • 在 setFieldsValue 中,循环组件实例,只要是组件实例的 name 属性与传入数据的 name 相同的,该组件都需要进行更新,直接调用实例的 onStoreChange 方法即可

小结

  • 至此,我们呢就实现了,数据更新,Field 组件随即响应更新的需求了
  • 后面的文章,我们将继续晚上,Form 注册回掉事件的功能,如:数据校验成功,数据校验失败回掉等

最后

  • 今天的分享就到这里了,欢迎大家在评论区里面进行讨论 👏。
  • 如果觉得文章写的不错的话,希望大家不要吝惜点赞,大家的鼓励是我分享的最大动力 🥰