Frontend Development 10 min read

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.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
Mastering Formily: Data Management, Field Dependencies, and Precise Updates

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

createForm

produces 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.value

changes, 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

onChange

event, 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/core

and

formily/reactive

.

Dependency Collection: When a Field instance is created,

createReactions

registers 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

autorun

function 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

set

trap 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

ReactionStack

into 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.

JavaScriptfrontend developmentReactive ProgrammingFormilyForm Management
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.