formily2.0探究 (三)

1,595 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

讲formily-react、formily-core、formily-react、formily-antd的核心源码和他们的关联

formily/reactive

接上篇,响应式核心

Tracker

reactive.formilyjs.org/zh-CN/api/t…

主要用于接入 React/Vue 的手动追踪依赖工具,在依赖发生变化时不会重复执行 tracker 函数,需要用户手动重复执行,只会触发 scheduler

使用

import { observable, Tracker } from '@/@formily/reactive'

const values = { username: '111', home: { name: 'beijing' } }

const observableValues = observable(values)
// 包裹一个刷新视图函数 、scheduler
const tracker = new Tracker(() => {

 console.log('forceUpate');

})
// 包裹一个渲染视图函数、view
tracker.track(() => {

 console.log(observableValues.username);

})
// 改动可观察对象时,触发scheduler执行,刷新数据
observableValues.username = 'jiagou';

tracker.jsx


import { ReactionStack } from "./environment";
export class Tracker {
  constructor(scheduler) {
    // 刷新视图函数
    this.track.scheduler = scheduler;
  }
  track = (view) => {
    // 渲染视图函数,同antorun类似,将该函数放入全局变量ReactionStack中,执行该函数,进行响应式绑定
    ReactionStack.push(this.track);
    const result = view();
    ReactionStack.pop();
    return result;
  };
}

toJS

reactive.formilyjs.org/zh-CN/api/t…

深度递归将 observable 对象转换成普通 JS 对象

  
function toJS2(values) {
    const visited = new Set();
    const _toJS = (values) => {
      if (visited.has(values)) {
        return values;
      }
      console.log({ values })
      if (typeof values === 'object') {
        visited.add(values);
        const result = {};
        for (const key in values) {
          console.log({ key })
          result[key] = _toJS(values[key]);
        }
        return result;
      }
      //如果是一个普通的基本类型是直接返回
      return values;
    }

formily/reactive-react

reactive.formilyjs.org/zh-CN/guide

将响应式方法和react组件结合

observer

reactive.formilyjs.org/zh-CN/api/r…

observer接收一个 Function RenderProps ,只要在 Function 内部消费到的任何响应式数据,

都会随数据变化而自动重新渲染,也更容易实现局部精确渲染

React 中, observerFunction Component 变成 Reaction,每次视图重新渲染就会收集依

赖,依赖更新会自动重渲染

使用

输入改动响应对象,对应视图数据更新,写得有mobx那味

import React from 'react'
import { observable } from '@formily/reactive'
import { observer } from '@formily/reactive-react'

const obs = observable({
  value: 'Hello world',
})

export default observer(() => {
  return (
    <div>
      <div>
        <input
          }}
          value={obs.value}
          onChange={(e) => {
            obs.value = e.target.value
          }}
        />
      </div>
      <div>{obs.value}</div>
    </div>
  )
})

observer.jsx

import { useObserver } from "./hooks/useObserver";
// 接收函数组件 observer(()=>{return <></>})
export function observer(component) {
  // 预留props,用于接收参数,看起来简单,这一块其实很难写
  const wrappedComponent = (props) => {
    return useObserver(() => component(props));
  };
  return wrappedComponent;
}

useObserver.jsx

import { useState, useRef } from "react";
import { Tracker } from "@/@formily/reactive";
export const useObserver = (view) => {
  const [, setState] = useState({});
  const forceUpdate = () => setState({});
  const instRef = useRef(null);
  if (!instRef.current) {
    // 创建Tracker实例,放入数据更新函数,作为schedule
    instRef.current = new Tracker(forceUpdate);
  }
  // 将view函数,视图函数,放入Tracker实例中,进行响应式绑定
  return instRef.current.track(view);
};

流程

用observer包裹函数组件,也就是view函数,用view构造Tracker,进行双向绑定

当数据变动时,触发Tracker的数据更新函数schedule执行,也就是响应式更新。

path

处理路径,简单版本,Form用到了,可以先跳过


const parse = (pattern) => {
  if (!pattern) {
    return {
      entire: '',
      segments: []
    }
  }
  return {
    entire: pattern,
    segments: pattern.split('.')
  }
}
export class Path {
  constructor(input = '') {
    const { entire, segments } = parse(input);
    this.entire = entire;
    this.segments = segments;
  }
  static parse() {
    return new Path();
  }
  concat(...args) {
    const path = new Path('');
    path.segments = this.segments.concat(...args);
    path.entire = path.segments.join('.');
    return path;
  }
}

formily/core

core.formilyjs.org/zh-CN/guide

core.formilyjs.org/zh-CN/api/e…

使用

创建一个表单有,内有username字段,在表单创建字段,name为字段,其他为属性名。表单值原为username=11后面改成jiagou

  const form = createForm({
    values: {
      username: "11",
    },
  });
  console.log(form);
  const field = form.createField({
    name: "username",
    title: "用户名",
    value: "jiagou",
  });
  console.log(field);

Form

core.formilyjs.org/zh-CN/api/m…

调用createForm所返回的核心表单模型 API,以下会列出所有模型属性,如果该属性是可写的,那么我们可以直接引用是修改该属性,@formily/reactive 便会响应从而触发 UI 更新。

form.js

import { observable, define } from "@/@formily/reactive";
import { FormPath } from "@/@formily/shared"; // FormPath 引入的是 path库
import { Field } from "./Field";
import { batchSubmit } from "../shared/internals";
export class Form {
  values = {}; //表单的值
  fields = {}; //表单的字段
  constructor(props = {}) {
    // 参数初始化
    this.initialize(props);
    // 将表单值、字段 响应式化
    this.makeObservable();
    // 初始化表单值
    this.makeValues();
  }
  initialize(props) {
    this.props = props;
  }
  // 将表单值、字段 响应式化
  makeObservable() {
    define(this, {
      values: observable,
      fields: observable.shallow,
    });
  }
  makeValues() {
    this.values = Object.assign({}, this.props.values || {});
  }
  // 创建字段
  createField(props) {
    // 传入 props.name : "username" ,处理一下路径
    const address = FormPath.parse().concat(props.name);
    // 传出 address:{entire: "username",segments: ['username']}
    // 创建字段
    new Field(address, props, this);
    // 返回字段
    return this.fields[address.entire];
  }
  // 处理字段值 修改
  setValuesIn(pattern, value) {
    this.values[pattern.entire] = value;
  }
  getValuesIn(pattern) {
    return this.values[pattern.entire];
  }
  submit = (onSubmit) => {
    return batchSubmit(this, onSubmit);
  };
}

Field

字段模型;core.formilyjs.org/zh-CN/api/m…

调用createField所返回的 Field 模型。

以下会列出所有模型属性,如果该属性是可写的,那么我们可以直接引用是修改该属性,@formily/reactive 便会响应从而触发 UI 更新。

const field = form.createField({
  name: 'username',
  title: '用户名',
  value: 'jiagou'
});

field

import { define, observable } from "@/@formily/reactive";
export class Field {
  constructor(address, props, form) {
    this.props = props;
    this.form = form;
    // 添加到表单
    this.locate(address);
    // 初始化
    this.initialize();
    this.makeObservable();
  }
  initialize() {
    this.value = this.props.value;
    this.decorator = this.props.decorator;
    this.component = this.props.component;
  }
  // 将字段值设值为 可观察对象,字段值变动时,重新渲染该字段渲染函数
  makeObservable() {
    define(this, {
      value: observable,
    });
  }
  locate(address) {
    // 这里将当前字段依附到表单上
    this.form.fields[address.entire] = this;
    // this.path = {entire: "username",segments: ['username']}
    this.path = address;
  }
  get decorator() {
    return [this.decoratorType];
  }
  set decorator(value) {
    this.decoratorType = value?.[0] || "";
  }
  get component() {
    return [this.componentType];
  }
  set component(value) {
    this.componentType = value?.[0];
  }
  // 设值
  // this.path = {entire: "username",segments: ['username']}
  get value() {
    return this.form.getValuesIn(this.path);
  }
  set value(value) {
    this.form.setValuesIn(this.path, value);
  }
  // this.path = {entire: "username",segments: ['username']} 赋值username
  onInput = (event) => {
    const newValue = event.target.value;
    this.value = newValue;
    //this.form.values.username = '新的输入框的值'
    this.form.values[this.path.entire] = newValue;
  };
}

总结

模型层,表单和字段模型

formily/react

@formily/react 的核心定位是将 ViewModel(@formily/core)与组件实现一个状态绑定关系,它不负责管理表单数据,表单校验,它仅仅是一个渲染胶水层,但是这样一层胶水,并不脏,它会把很多脏逻辑优雅的解耦,变得可维护。

react.formilyjs.org/zh-CN/guide

react.formilyjs.org/zh-CN/api/c…

使用

使用 core 创建 表单,使用antd渲染表单

import { createForm } from '@/@formily/core';
import { FormProvider, Field } from '@/@formily/react';
import { FormItem, Input } from '@/@formily/antd';
import 'antd/dist/antd.css'
const form = createForm();
const App = () => {
  return (
    <FormProvider form={form}>
      <Field
        name="username"
        title="用户名"
        value="jiagou"
        decorator={[FormItem]}
        component={[Input]}
      />
      <button onClick={() => {
        form.submit(console.log);
      }}>提交</button>
    </FormProvider>
  )
}
export default App;

shared

export const FormContext = createContext(null);
export const FieldContext = createContext(null);

hooks

import { FieldContext } from '../shared'
import { FormContext } from "../shared";
export const useField = () => {
  return useContext(FieldContext);
}
// 拿到父级的form
export const useForm = () => {
  return useContext(FormContext);
};

component

# FormProvider.js
import React from 'react';
import { FormContext } from '../shared';
export const FormProvider = (props) => {
  const form = props.form;
  return (
      // 拿到form,传递form
    <FormContext.Provider value={form}>
      {props.children}
    </FormContext.Provider>
  )
}
# Field.jsx
import React from 'react';
import { FieldContext } from '../shared'
import { useForm } from '../hooks';
import { ReactiveField } from './ReactiveField';
export const Field = (props) => {
  const form = useForm(); //获取表单的领域模型
  const field = form.createField(props);//创建字段的领域模型 
  return (
    <FieldContext.Provider value={field}>
      <ReactiveField field={field}>{props.children}</ReactiveField>
    </FieldContext.Provider>
  )
}
# ReactiveField
import React from "react";
import { observer } from "@/@formily/reactive-react";
const ReactiveInternal = (props) => {
  const field = props.field;
  const renderDecorator = (children) => {
    return React.createElement(field.decoratorType, {}, children);
  };
  const renderComponent = () => {
    const value = field.value;
    const onChange = (event) => {
      // 输入值赋值给 field.value
      field.onInput(event);
    };
    // componentType : input ,用antd的input来渲染
    return React.createElement(field.componentType, { value, onChange });
  };
  return renderDecorator(renderComponent());
};
// 用observer包裹监听 渲染函数
export const ReactiveField = observer(ReactiveInternal);

总结

胶水层,将core创建的表单,用ant的组件来渲染

formily/antd

基于 Ant Design 封装的针对表单场景专业级(Professional)组件库

input


import { connect, mapProps } from '@/@formily/react';
import { Input as AntdInput } from 'antd';
export const Input = connect(AntdInput, mapProps(props => {
  return { ...props };
}));
export default Input;

formily/react/shared

import React from 'react';
import { observer } from '@/@formily/reactive-react';
import { useField } from '../hooks';

export function mapProps(...propMappers) {
  return (Target) => {
    return observer((props) => {
      const field = useField();
      const result = propMappers.reduce((props, propMapper) => {
        return Object.assign(props, propMapper(props, field));
      }, { ...props })
      //return <Target {...result} />
      return React.createElement(Target, result)
    });
  }
}

export function connect(target, ...enhanceTargets) {
  const Target = enhanceTargets.reduce((target, enhanceTarget) => {
    return enhanceTarget(target);
  }, target);
  return (props) => {
    //return <Target {...props} />
    return React.createElement(Target, { ...props });
  }
}

总结

img

formily/reactive 响应式作为底层核心

formily/reactive-react 连接 formily/react组件

formily/core 负责表单和字段,字段为响应式对象

formily/react 负责用 formily/ant组件来渲染 表单