跪拜 Guibai
← All articles
Vue.js · Low-code

JuanForm vs FormCreate: A Vue 3 Form-Engine Linked-Config Shootout

By 全栈小卷 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The PK draws the architectural line for anyone building or choosing a Vue 3 form engine: target-field, scope-and-node config over a plain-object schema versus source-field control over a `ref()`-wrapped reactive rule tree. That line determines how far pure config can stretch before users hit closed components, init-order workarounds, or listener overhead, and since the comparison comes from JuanForm's own developer, its claims are best treated as a strong opinion to verify against real workloads rather than a neutral benchmark.

Summary

The author runs JuanForm, the self-developed engine, against FormCreate on the same six sets of linked-form requirements. Field hiding, expression-based summation, cross-layer writes, province-city cascades, dynamic beneficiary lists with row/column styling, conditional hints, and add limits all get configured in both engines with code and rendered Element Plus effects.

The comparison comes down to where dependencies are declared. JuanForm puts `hidden`, `expression`, and `dependsOn` on the target field, reading values through a scope chain that mirrors Vue's own template semantics — `<template v-if>`, per-row nodes, path-based lookups. FormCreate mostly wires source fields to targets via `control`, `link`, and `update`, and its `computed` variants only work when the entire rule array is wrapped in `ref()`, which the author argues becomes a listener-performance burden as schemas grow.

FormCreate's list handling is closed around a `group` component with built-in add/delete buttons and hooks like `onBeforeAdd`; JuanForm treats add, delete, and limits as ordinary button fields. Cross-layer writes also expose a FormCreate gap: the first write fires before nested subforms initialize, and its `api.nextTick` is actually a `setTimeout(..., 10)` shim rather than Vue's `nextTick`. The verdict favors JuanForm for complex, page-level linking and FormCreate for smaller, standard forms.

Takeaways
— JuanForm declares dependencies on the target field: `hidden`, `expression`, and `dependsOn` read scope variables, so a field controls its own visibility or computed value.
— FormCreate's `control` config works from a source field with `method` selecting `if` or `display`; target-field `computed.hidden` and `computed.value` only fire when the whole rule array is wrapped in `ref()`.
— Hidden fields stay in FormCreate's data model unless `ignoreHiddenFields: true` is set.
— FormCreate's first cross-subform write fails on page load because inner subforms haven't initialized; it requires an `origin === 'init'` check plus `api.nextTick`.
— FormCreate's `api.nextTick` is not Vue's `nextTick`; its `bindNextTick` defers with `setTimeout(..., 10)`.
— JuanForm reaches nested nodes by path with `node.at('crossOuter.crossInner.crossLeaf')`; FormCreate chains `api.getSubForm(...).getSubForm(...).setValue(...)`.
— JuanForm's `List` rows are scope nodes: add, delete, and the 3-row limit live on ordinary button fields using `$parent` and `$index`; FormCreate's `group` owns add/delete through a built-in `button` and the `onBeforeAdd` hook.
— For row/column styling, JuanForm writes `props.style` on `node.current()` and `node.at('share')`; FormCreate requires extra native `row`/`col` nesting with `computed.style`.
— JuanForm can create and delete nodes at runtime with `node.add`/`node.delete` inside `listDependsOn`; FormCreate has no runtime node-creation interface for linked config, so hints must be pre-written with `computed.hidden`.
— The author's stated conclusion: JuanForm targets complex, page-level linking; FormCreate fits standard forms with few fields and source-controlled targets.
Conclusions

The comparison is written by JuanForm's own developer as a home-match "PK": FormCreate's ecosystem, docs, and existing adoption are never weighed, only its config mechanics, so the clean-mechanism verdict is an engineering opinion from the competing engine's team.

The `ref()`-wrapped rule array is the article's strongest concrete charge, but the author concedes it matters little for small forms, which narrows the performance argument to large, config-heavy schemas.

FormCreate's `nextTick` being a `setTimeout` shim points to a real render-ordering gap in its pipeline; whether that stays a corner-case workaround or becomes a maintenance hazard depends on how often nested init-order writes occur in production forms.

The deeper position this stakes out is architectural: a runtime modeled on nodes and scope, close to how a Vue developer writes templates by hand, versus a runtime modeled on a rule tree and instance APIs, which is more imperative and tends toward closed components and per-case configuration interfaces.

Concepts & terms
dependsOn
A JuanForm config entry on a target field that runs a handler whenever referenced scope fields change; tool modules like the node API are injected through an `inject` list.
node.at(path)
A path-based lookup in JuanForm that resolves a node anywhere in the schema, e.g. `node.at('crossOuter.crossInner.crossLeaf')`, used to read or write arbitrary fields from a handler.
ignoreHiddenFields
A FormCreate option that strips hidden fields out of the form data model; without it, hidden fields keep their values in the model.
onBeforeAdd
A FormCreate hook on the `group` list component that can veto an add by returning `false`, used to enforce list-length limits.
group component
FormCreate's built-in list container that renders rows plus add/delete and sort buttons; its internals are closed to configuration beyond options like `button` and `sortBtn`.
From the discussion
Featured comments
全栈小卷

An author spent 8 years developing a Vue low-code form, and then an AI analysis of the open-source code reveals a whole range of design and extensibility problems 🤣

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗