Mastering Formily: Data Management, Field Dependencies, and Precise Updates
This article explains how Formily structures form data, manages field dependencies through a publish‑subscribe model, and achieves precise component updates using a custom reactive system, providing practical code examples for frontend developers.
Background
In the middle‑back office at GuMing, Formily is heavily used for form handling. The initial impression was that Formily was difficult to use, but the team identified three recurring challenges: form data management, field dependencies, and precise updates.
Form Data Management
Creating a form with
createFormproduces a form model consisting of FormGraph and FormHeart :
FormGraph manages the form object and its fields as nodes.
FormHeart handles the form lifecycle, including mounting and field state changes.
When an input component changes, the corresponding field value updates, which propagates to the form and back, forming a bidirectional communication loop.
The communication involves three parts:
formState: maintains all field values.
fieldState: maintains the current field value.
component: the UI component (e.g., Input, Select) that renders the field.
Form and Field Data Communication
Form and Field communicate via a publish‑subscribe pattern. When
field.valuechanges, the form is notified, and vice versa.
<code>// form.value 变化通知 field
const makeReactive = {
const triggerFormValuesChange = (form, change) => {
// 比较 form.value 是否存在变动
if (contains(form.values, change.object)) {
// 通知 field 组件,form.value 改变
form.notify(LifeCycleTypes.ON_FORM_VALUES_CHANGE)
}
}
observe(form, (change) => {
xxxx
triggerFormValuesChange(form, change)
}, true)
}
// field.value 变动通知表单
const onInput = (...args) => {
const values = getValues(args)
const value = values[0]
this.inputValue = value
this.value = value
// 通知表单 field.value 改变
this.notify(LifeCycleTypes.ON_FIELD_INPUT_VALUE_CHANGE)
}
</code>Field and Component Data Communication
Each Field is linked to a Component. User input triggers an
onChangeevent, which updates the Field value; the Field then passes its value back to the Component via props, achieving two‑way binding.
<code>const renderComponent = () => {
const events = {} as Record<string, any>
// 设置 field 中的 onChange 事件
events.change = (...args: any[]) => {
if (!isVoidField(field)) field.onInput(...args)
originChange?.(...args)
}
const componentData = {
attrs: {
// 获取 field 中的value
value: !isVoidField(field) ? field.value : undefined,
},
on: events,
}
// 渲染 field 的 component 组件
return h(component, componentData, mergedSlots)
}
class Field {
construct(props) {
this.value = props.value;
this.makeObservable()
}
makeObservable() {
define(this, {
// 将 this.value 变成响应式
value: observable.computed,
})
}
}
</code>Form Field Dependency (联动)
Field dependency means a field reacts to changes in other fields. Formily implements this using a reactive model built on
formily/coreand
formily/reactive.
Dependency Collection: When a Field instance is created,
createReactionsregisters lifecycle effects and collects dependencies.
<code>const createReactions = (field: GeneralField) => {
const reactions = toArr(field.props.reactions)
// 在表单中注册该字段值变动的生命周期
field.form.addEffects(field, () => {
reactions.forEach((reaction) => {
if (isFn(reaction)) {
field.disposers.push(
// 收集依赖
autorun(reaction(field))
)
}
})
})
}
</code>The
autorunfunction tracks accessed properties, enabling dependency collection.
<code>let ReactionStack;
const RawReactionsMap = new WeakMap();
export function observable(value) {
return new Proxy(value, baseHandler);
}
const baseHandler: any = {
get(target, key) {
const result = target[key];
const current = ReactionStack
if (current) {
// 当前存在响应器
addRawReactionsMap(target, key, current);
}
return result;
},
set(target, key, value) {
target[key] = value;
RawReactionsMap.get(target)?.get(key)?.forEach(reaction => reaction());
return true;
},
};
export function autorun(tracker) {
const reaction = () => {
ReactionStack = reaction;
tracker();
ReactionStack = null;
};
reaction();
}
</code>Dependency Listening: The
settrap in the proxy notifies collected reactions, causing only the dependent components to re‑render.
Precise Form Updates
Precise updates rely on the same reactive principle. By turning the global
ReactionStackinto a stack, Formily can pinpoint which component actually depends on a changed field and re‑render only that component.
<code>// 使用栈记录依赖函数,实现精准刷新
let ReactionStack = [];
export function observable(value) {
return new Proxy(value, baseHandler);
}
const baseHandler: any = {
get(target, key) {
const result = target[key];
// current 表示当前依赖所在的执行函数
const current = ReactionStack[ReactionStack.length - 1]
if (current) {
// 当前存在响应器
addRawReactionsMap(target, key, current);
}
return result;
},
// ...
};
export function autorun(tracker) {
const reaction = () => {
ReactionStack.push(reaction);
tracker();
ReactionStack.pop();
};
reaction();
}
</code>Conclusion
Formily provides a high‑performance solution for complex form scenarios in middle‑back office development by leveraging a reactive architecture that cleanly separates data management, field dependencies, and precise component updates.
Goodme Frontend Team
Regularly sharing the team's insights and expertise in the frontend field
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.