formily源码学习

1,008 阅读4分钟

formily思维导图

image.png

总入口(which)

// import {createForm} from "@formily/core";
import {
  observable,
  Tracker,
  batch,
  define,
  action,
  toJS,
  autorun,
} from "@formily/reactive";
// import {
//   FormProvider,
//   Field,
//   FormConsumer,
//   FieldContext,
//   useParentForm,
// } from "@formily/react";
// import {observer} from "@formily/reactive-react";
// import {FormItem, Input, Submit} from "@formily/antd";

import {
  FormPath,
  each,
  isFn,
  isValid,
  isUndef,
  isEmpty,
  isPlainObj,
  isNumberLike,
  clone,
  toArr,
} from "@formily/shared";

// todo
import {createForm} from "@/components/my-formily/core";

import {observer} from "@/components/my-formily/reactive-react";

import {FormItem, Input, Submit} from "@/components/my-formily/antd";
import {
  FormProvider,
  Field,
  FormConsumer,
  FieldContext,
  useParentForm,
} from "@/components/my-formily/react";

export {
  // core
  createForm,
  // react
  FormProvider,
  Field,
  FormConsumer,
  FieldContext,
  // 用于读取最近的 Form 或者 ObjectField 实例,主要方便于调用子表单的 submit/validate
  useParentForm,
  // reactive
  observable,
  Tracker,
  batch,
  define,
  action,
  toJS,
  autorun,
  // reactive-react
  observer,
  // antd
  FormItem,
  Input,
  Submit,
  // shared
  FormPath,
  each,
  isFn,
  isValid,
  isUndef,
  isEmpty,
  isPlainObj,
  isNumberLike,
  clone,
  toArr,
};

@formily/antd

antd入口

import FormItem from "./FormItem";
import Input from "./Input";
import Submit from "./Submit";

export {FormItem, Input, Submit};

formItem组件的实现

import { observer, FieldContext } from "@/which";
import { useContext } from "react";

//传的参数跟antd渲染时传的参数相关
const FormItem = observer(({ children }) => {
  const field = useContext(FieldContext); //这里只能用formily里的context,不能自定义context

  return (
    <div>
      <div>{field.title}</div>
      {children}
      <div className="red">{field.selfErrors.join(",")}</div>
    </div>
  );
});

export default FormItem;

Input组件实现

const Input = (props) => {
  return (
    <input
      {...props}
      value={props.value || ""}
      style={{...props.style, border: "solid 1px green"}}
    />
  );
};

export default Input;

Submit组件实现

import {useParentForm} from "@/which";

const Submit = ({
  children,
  onSubmit,
  onSubmitSuccess,
  onSubmitFailed,
  onClick,
}) => {
  // 获取form表单
  const form = useParentForm();
  return (
    <button
      onClick={(e) => {
        if (onClick) {
          if (onClick(e) === false) {
            return;
          }
        }
        if (onSubmit) {
          form
            .submit(onSubmit)
            .then(onSubmitSuccess)
            .catch(onSubmitFailed);
        }
      }}>
      {children}
    </button>
  );
};

export default Submit;

  • 可以看到FormItem组件和Input组件传入的参数有所区别,这主要是由@formily/react里的Field组件在渲染的时候控制的,在下面的实现中可以重点关注它们之间的联系
  • FormItem和Field组件之间存在父子之间的通信,但是具体跨多少层级不确定,所以用到了react里的Context来实现跨层级之间的通信(使用了@formily/react对外暴露的FiledContext)
  • 可以思考一下Submit里的form是如何实现的

@formily/react

入口

import FormProvider from "./FormProvider";
import Field from "./Field";
import FormConsumer from "./FormConsumer";
import {FieldContext} from "./context";
import {useParentForm} from "./hooks";

export {FormProvider, Field, FormConsumer, FieldContext, useParentForm};

FormProvider组件的实现

import { useEffect } from "react";
import { FormContext } from "./context";

export default function FormProvider({ form, children }) {
  //数据存放在core里,需要对form进行初始化
  useEffect(() => {
    form.onMount();
    return () => {
      form.onUnmount();
    };
  }, []);
  return <FormContext.Provider value={form}>{children}</FormContext.Provider>;
}

FormConsumer组件的实现

import {observer} from "@/which";
import {useContext} from "react";
import {FormContext} from "./context";

const FormConsumer = observer((props) => {
  const form = useContext(FormContext);
  const children = props.children(form);
  return children;
});

export default FormConsumer;

hooks(自定义hook)

import {useContext} from "react";
import {FormContext} from "./context";

export function useParentForm() {
  const form = useContext(FormContext);

  return form;
}

context

import React from "react";

export const FormContext = React.createContext();
export const FieldContext = React.createContext();

Field组件的实现

import React, { useContext } from "react";
import { observer, FieldContext } from "@/which";
import { FormContext } from "./context";

// 组件层的Field
const Field = observer((props) => {
  const form = useContext(FormContext);
  const field = form.createField(props);

  const component = React.createElement(field.component[0], {
    ...field.component[1],
    value: field.value || "",
    onChange: field.onInput,
  });

  const decorator = React.createElement(
    field.decorator[0],
    field.decorator[1],
    component
  );

  return (
    <FieldContext.Provider value={field}>{decorator}</FieldContext.Provider>
  );
});

export default Field;

  • 这部分的实现相对来说也较简单,重点是创建了两个context(FormContext和FieldContext),重点关注它们的作用
  • 可以看到Fild组件的field来源于form.createField,那咱们的关注点又来到了@formily/core里,这是在core对外暴露来获取数据的方法

@formily/core

入口

import createForm from "./createForm";

export {createForm};

createForm

import Form from "./Form";

const createForm = (options) => {
  return new Form(options);
};

export default createForm;

Form

import Field from "./Field";
import {define, observable} from "@/which";
import {batchSubmit, batchValidate} from "./internals";

export default class Form {
  constructor(props) {
    this.initialize(props);
    this.makeObservable();
  }

  initialize = (props) => {
    this.props = {...props};
    this.fields = {};
    this.initialValues = props.initialValues;
    // 所有field的value
    this.values = {...props.initialValues};

    this.errors = [];
  };

  makeObservable = () => {
    define(this, {
      fields: observable.shallow,
      values: observable,
    });
  };

  createField = (props) => {
    const {name} = props;
    if (!this.fields[name]) {
      new Field(name, props, this);
    }

    return this.fields[name];
  };

  onMount = () => {};
  onUnmount = () => {};
  validate = () => {
    return batchValidate(this);
  };
  submit = (onSubmit) => {
    return batchSubmit(this, onSubmit);
  };
}

数据层Field

import {define, observable} from "@/which";
import {createReactions, validateSelf} from "./internals";

// 数据层的Field
export default class Field {
  constructor(name, props, form) {
    this.name = name;
    this.props = {...props};
    this.form = form;

    this.form.fields[name] = this;

    this.component = props.component;
    this.decorator = props.decorator;

    this.selfErrors = [];

    this.value = this.form.values[name];

    this.query = {required: props.required};

    this.makeObservable();
    this.makeReactive();
  }

  makeObservable = () => {
    define(this, {
      value: observable,
      selfErrors: observable,
    });
  };

  makeReactive = () => {
    createReactions(this);
  };

  onInput = (e) => {
    const newValue = e.target.value;

    this.value = newValue;
    this.form.values[this.props.name] = newValue;

    // todo 校验
    validateSelf(this);
  };
}

internal

import {autorun, batch, toJS} from "@/which";

export const validateSelf = (field) => {
  let value = field.value;

  if (typeof value == "string") {
    value = value.trim();
  }

  const query = field.query;

  if (query.required && (value == "" || value == undefined)) {
    field.selfErrors = ["请输入必填项"];
  }
};

export const createReactions = (field) => {
  const reactions = field.props.reactions;
  if (typeof reactions === "function") {
    autorun(
      batch.scope.bound(() => {
        reactions(field);
      })
    );
  }
};

export const batchValidate = async (target) => {
  target.errors = [];
  let i = 0;
  for (const key in target.fields) {
    const field = target.fields[key];

    validateSelf(field);

    if (field.selfErrors[0]) {
      target.errors.push({key, msg: field.selfErrors[0]});
    }
  }

  if (target.errors.length > 0) {
    throw target.errors;
  }
};
export const batchSubmit = async (target, onSubmit) => {
  await batchValidate(target);
  const res = onSubmit(toJS(target.values));
  return res;
};

  • 这里重点关注数据层Form和Field之间的联系
  • createField实际上也就是获取数据层Field里的所有数据

响应式reactive-react

入口

import observer from "./observer";

export { observer };

observer

import { memo } from "react";
import { useObserver } from "./hooks";

export default function observer(component) {
  const WrappedComponent = (props) => {
    return useObserver(() => component({ ...props }));
  };
  //属性没变的时候不触发重新渲染
  const memoComponent = memo(WrappedComponent);

  return memoComponent;
}

hooks(自定义hook)

import { useReducer, useEffect, useRef } from "react";
import { Tracker } from "@/which";

export function useObserver(view) {
  const [, forceUpdate] = useReducer((x) => x + 1, 1);

  const trackerRef = useRef(null);
  if (!trackerRef.current) {
    trackerRef.current = new Tracker(() => {
      forceUpdate();
    });
  }

  useEffect(() => {
    //卸载组件的时候调用
    return () => {
      if (trackerRef.current) {
        trackerRef.current.dispose();
        trackerRef.current = null;
      }
    };
  }, []);

  return trackerRef.current.track(view);
}