fix(react): evaluate expressions under a node's properties, not just props (#4799) - #5122
Merged
Merged
Conversation
…t `props`
`properties` is the spec spelling of a node's config bag and `props` is the
legacy alias, but SchemaRenderer's evaluation memo only ran the expression
evaluator over `props`. Renderers in the `element:*` namespace read
`schema.properties` first (readProps merges `{ ...props, ...properties }`), so a
node written the canonical way handed the renderer the raw `${...}` source and
rendered it verbatim, while the same node written with the alias evaluated
correctly -- writing the spec-compliant form was the way to lose expressions.
Measured through a real render with dataSource `{ total: 99 }`:
`props: { content: '${data.total}' }` rendered `99`, while
`properties: { content: '${data.total}' }` rendered `${data.total}`.
The memo already hoisted `properties.*` onto the node's top level, but left
`newSchema.properties` pointing at the untouched original -- which is the object
the renderer actually reads -- so that hoist did nothing for `element:*`. The
evaluated bag now REPLACES `newSchema.properties`, and the evaluation runs
before the hoist so a key means the same thing read as `schema.properties.x`, as
`schema.x`, or as the spread `x` React prop.
Evaluation stays per-value and shallow on both spellings (nested objects and
arrays are passed through, not walked -- measured identical before and after on
both keys); deepening it would be a separate decision owed to both spellings at
once. `properties` keeps its precedence over `props`, and
`packages/components/**` is untouched.
Fixes #4799
Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收:ACCEPT(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片) 实物核验:merge-base 验收要点:
三件套照常:本评论 → undraft → auto-merge(SQUASH)。 Generated by Claude Code |
yinlianghui
marked this pull request as ready for review
August 18, 2026 02:42
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4799
问题
properties是节点配置袋的规范拼写,props是readProps()注释里明写的 legacy alias。但SchemaRenderer的 evaluation memo 只对newSchema.props逐值求值,properties没有对应分支;而element:*命名空间的渲染器(packages/components/src/renderers/basic/elements.tsx的readProps())读的是{ ...schema.props, ...schema.properties }—— 先读properties,且它优先。于是按规范拼写写节点的作者,表达式静默失效、上屏字面量;写 "legacy alias" 的反而正常。
真实 SchemaRenderer 渲染探针实测(dataSource 为
total: 99):改动前
element:text+props: content表达式99element:text+properties: content表达式${data.total}← 规范拼写丢表达式element:text+props: content静态HELLOelement:text+properties: content静态HELLO改动后
element:text+props: content表达式99element:text+properties: content表达式99element:text+props: content静态HELLOelement:text+properties: content静态HELLOmemo 里原有一段把
properties.*提升到节点顶层,顶层content随后会被求值 —— 但newSchema.properties仍指向原始未求值对象,而渲染器读的正是schema.properties,所以提升那一步对element:*没有作用。这就是"求值结果必须替换newSchema.properties"的由来。改法
packages/react/src/SchemaRenderer.tsx的 evaluation memo 里,对newSchema.properties做与props同样的逐值求值,并把结果替换回newSchema.properties。三点刻意的形状:
properties.*拷到节点顶层,这些拷贝还会在渲染时被 spread 成 React prop。先求值,同一个键无论从schema.properties.x、schema.x还是xprop 读才是同一个值(原先只有content一个键碰巧被顶层那条腿求值,其余顶层拷贝是生的)。顺带让后面的content腿幂等:这里求过值的字符串已不含${...}。evaluator.evaluate对非字符串原样返回,所以嵌套对象/数组是穿过而不是走进去 —— 实测aria里嵌一层表达式,在任一拼写下、改动前后都渲染生字符串。加深是另一个决定,要加必须两边一起加,不在这里单边夹带;两条 pin 把这个 parity 钉住了。props分支的裸真值判断更窄(多了typeof === 'object'与!Array.isArray)。因为这个值要喂给提升段:退化形态的properties(字符串、数组)若被对象 spread 重塑会顺着传下去。非对象直接跳过求值,原样进提升段。readProps的properties优先级保持不变,packages/components/**零改动。与 #4795 方向 3 的序列关系
本单是先行的机械 alias parity,不实施 #4795 的方向 3(未求值表达式的响亮诊断,维护者已在 #4795 评论 5312061331 裁定"方向 3 现在做、方向 2 永不")。两者落在同一个 memo:本单先行,D3 后续同文件。因此改动形状刻意保持局部 —— 只补一个求值分支,未重构 memo —— 给 D3 留干净落点。
测试
新增
packages/react/src/__tests__/SchemaRenderer.propertiesExpressions.test.tsx(18 项),覆盖派发的五条钉:properties+ 表达式求值上屏、props不回归、两键同现时properties优先且两边都已求值、静态值两边不回归、非 element 命名空间既有 props 求值不变;另加提升段一致性(顶层 / 配置袋 / React prop 三个读法同值)、properties.type与properties.id仍不遮蔽节点描述符、浅求值两边同形、退化形态守卫。跑过:react 全量 47 files / 636 tests;components 全量 155 / 1417;layout + plugin-detail + plugin-list + plugin-dashboard 203 / 2170;app-shell 全量 424 / 4079(1 skipped);仓根 turbo type-check 81/81;改动文件 eslint 0 errors;
check:control-bytes+ 自扫。反向验证(先书面预判,再跑)
git checkout HEAD~1 -- SchemaRenderer.tsx)。预判:6 项转红(4 条
properties求值钉 + 两键同现钉 + 提升一致性钉),其余 12 项保持绿。这是普通红向,不是 feat(components,plugin-detail): 五个 GA 键发布为 inputs,page:tabs 补上被发布的那个拼写的读点 (#4668) #5009 那种倒置 —— 这里没有??链,新钉断言的是一个此前根本不存在的能力。实测:
Tests 6 failed | 12 passed (18),转红的正是预判点名的那 6 项。预判命中。newSchema.properties—— 即复刻 issue 成因 (c):提升段拿到已求值的值,配置袋仍指向旧对象。预判:同样 6 项红;判别信息在提升一致性那一项内部 —— 顶层与 React prop 两半会过,只有读配置袋的那半失败。
实测:同样
6 failed | 12 passed,且提升一致性那项在第 200 行(schema.properties.variant)失败、第 199 行(schema.variant)通过;而摘掉整个分支时它是在第 199 行失败。两个变异的失败行不同,正是"evaluated"与"evaluated 且 replaced"的分界。**预判命中。**这也是那几条schema.properties.*断言存在的理由:去掉它们,套件会在 bug 仍在时转绿。properties内嵌套对象/数组的求值语义是否与props分支同形。实测(改动前后各一遍,真实渲染探针):
aria里嵌一层表达式,在任一拼写下、改动前后都渲染生字符串 —— 既有props分支本就浅求值。所以本改动是浅同形,未顺手加深,并用两条 pin 固定该 parity。这条与"红向"模板无关,如实记为「两边都不深、且改动前后不变」。Generated by Claude Code