携手创作,共同成长!这是我参与「掘金日新计划 · 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 中, observer将 Function 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 });
}
}
总结
formily/reactive 响应式作为底层核心
formily/reactive-react 连接 formily/react组件
formily/core 负责表单和字段,字段为响应式对象
formily/react 负责用 formily/ant组件来渲染 表单