逐日AI
第 1 周 · D5约 4 小时

生成式界面的三种范式:从组件映射到声明式 UI

让 Agent 决定界面长什么样有三种做法,控制权从前端一路让渡到模型。这一天把三种范式各实现一遍,重点讲清楚它们的安全边界,以及为什么最灵活的那种在生产里往往是最危险的那种。

今日目标 0/3

登录后可以勾选并保存进度。

今日目标

  1. 能说出生成式界面三种范式的差别,并按控制权与自由度把它们排序
  2. 能实现组件映射表与未注册组件的兜底,让模型给出意外类型时界面不崩
  3. 能说清开放式生成的安全风险,并给出一条明确的生产选型建议

昨天的共享状态已经是生成式界面的前身:当数据由模型决定时,下一个自然的问题就是「渲染它的组件能不能也由模型决定」。读完回到页面顶部把三条目标勾掉。

小白版讲解

从选择到生成:模型能在多大程度上决定界面

同传现场的译员通常只管说话。但设想一个更强的版本:他可以按一下按钮,让会场大屏上出现一张表格、一个投票面板、或者一张图。

这时候有个权限问题要回答:他能让大屏显示什么?

  • 版本一:大屏上预设了十个模板,译员只能选一个并填内容
  • 版本二:译员可以描述「左边放标题,右边放三个徽章」,由技术人员按会场规范渲染
  • 版本三:译员可以直接把任意内容投上去,包括他自己写的程序

三个版本的自由度递增,可控性递减。生成式界面的三种范式和这个完全对应。

三种范式与它们的判据

范式模型决定什么前端保留什么典型载荷
静态生成式选哪个组件 + 填什么数据组件实现、样式、交互{ component: 'metric', props: {...} }
声明式一棵界面描述树节点白名单、渲染规则、样式{ type: 'stack', children: [...] }
开放式直接产出界面代码几乎没有一段 JSX 或 HTML 字符串

真正重要的不是这张表,而是区分它们的那一条判据

模型产出的是数据,还是代码?

前两种是数据。数据可以校验、可以过白名单、可以在不认识时降级。第三种是代码,一旦执行就没有中间地带——你没法「部分执行」一段脚本。

这条判据能帮你判断任何新出现的方案属于哪一类,包括那些起了新名字的。

范式一:静态生成式,安全性全部来自注册表

前端定义一份组件注册表,模型只能从里面挑:

TypeScriptTypeScript
export const REGISTRY = {
  metric: {
    description: '展示一个关键指标,props: label(字符串) value(数字) unit(可选字符串)',
    validate: (p) => {
      const label = str(p.label)
      const value = num(p.value)
      if (label === null || value === null) return null  // 数据不合法
      return { label, value, unit: str(p.unit) ?? '' }
    },
  },
  // choices、table……
}

两个细节值得说。

description 会进 system prompt。 模型选错组件,多半不是它笨,而是这段说明写得不清楚。这是个容易被忽略的调试入口:生成式界面出问题时,先去看注册表说明写得对不对。

每个组件自己校验 props。 模型给的数据不可信——不是因为它恶意,而是因为它会犯错:少个字段、数字给成字符串、数组给成逗号分隔的文本,都很常见。校验失败就降级,而不是把坏数据传给组件让它自己崩。

两种失败要分开报

解析一份组件规格会有两种失败,它们对排查的指向完全不同:

TypeScriptTypeScript
// 组件名不在注册表里
{ ok: false, reason: 'unknown-component', component: 'timeline_3d' }
 
// 组件名认识,但数据不合法
{ ok: false, reason: 'invalid-props', component: 'metric' }

合并成一个「渲染失败」会让排查无从下手。 前者说明模型选了个不存在的东西——要么 prompt 里的说明不够清楚,要么上游加了新组件而前端没跟上;后者说明组件名对但字段错了,方向完全不同。

界面上的文案也要分开。对用户来说,「这个版本还不支持」和「数据不完整」是两条不同的信息,前者暗示升级可能解决,后者不会。

范式二:声明式,自由但仍然是数据

静态范式的限制是布局写死在组件里。模型想说「左边一个标题,右边并排两个徽章」,它做不到。

声明式范式让模型返回一棵节点树:

TypeScriptTypeScript
{
  type: 'stack',
  children: [
    { type: 'heading', text: '2026 Q3 摘要' },
    { type: 'row', children: [
      { type: 'badge', text: '已复核' },
      { type: 'text', text: '数据来自财务系统导出' },
    ]},
  ],
}

前端有一份节点类型白名单,按自己的样式渲染。模型获得了布局自由度,但它给的仍然是数据——树里没有任何东西能被执行。

这是 Google 的 A2UI 等项目在做的方向。它的吸引力在于同一份描述能在不同框架上渲染,因为描述的是意图而不是实现。

遇到不认识的东西:降级,而不是整棵树拒绝

这是声明式范式最重要的实现细节。

模型产出的树里混进一个白名单外的节点类型,怎么办?

不要整棵树拒绝渲染。 一个节点不认识就让整个回答变成一片空白,是对用户最没道理的处理——其余部分通常仍然有用。

正确做法是降级这一个节点:把它的文字内容保住,把结构丢掉,渲染成一段带视觉标记的纯文本,其余节点照常。

TypeScriptTypeScript
if (!ALLOWED.has(node.type)) {
  return {
    type: 'text',
    text: node.text ?? `(不支持的组件:${node.type})`,
    children: [],
    issue: 'unknown-type',   // 界面据此加一个降级样式
  }
}

还有一个容易漏的防护:嵌套深度上限。模型可能产出深到爆栈的树,而这不需要任何恶意——递归生成跑偏就够了。设一个上限(本课是 6 层),超了就截断并留一句可见的说明。

范式三:开放式,以及为什么本课不实现它

开放式范式让模型直接产出界面代码并在用户浏览器里执行。最灵活,也是三者里唯一真正危险的。

风险的核心不是「模型可能写出有 bug 的代码」,而是:

模型产出的代码会以你的页面权限运行。

它能读 cookie、读 localStorage、发任意请求、改任意 DOM。而模型是可以被它读到的内容影响的——一封邮件、一个网页、一份用户上传的文档,里面都可能藏着让它产出恶意代码的指令。

这条攻击路径最值得强调的一点是:它不需要模型「变坏」,只需要它听话。 模型忠实地执行了它在文档里读到的指令,而那份文档是攻击者写的。

沙箱(iframe 加严格的 CSP)能降低风险,但有两个代价:不能完全消除(沙箱逃逸的历史很长),以及会让生成的界面失去与主应用交互的能力——而那恰恰是当初想要开放式的理由。

所以生产建议很明确:从静态范式开始,需要布局自由度时升到声明式,除非有非常强的理由并且准备好完整的沙箱方案,否则不要用开放式。

注册表说明怎么写,模型才选得对

上面提过一句「模型选错组件多半是说明写得不清楚」,这一节展开说,因为它是生成式界面最高频的调试入口。

写得差的说明长这样:展示数据。模型看到三个组件都能「展示数据」,只能猜。

写得好的说明包含三件事:这个组件适合什么场景、每个字段是什么类型、以及什么时候不该用它

TypeScriptTypeScript
metric: {
  description:
    '展示单个关键指标,适合强调一个数字。props: label(字符串) value(数字) unit(可选字符串)。' +
    '有多个数字要对比时用 table,不要连续放多个 metric。',
}

最后那半句尤其有用——告诉模型什么时候不该选它,比只说什么时候该选它更能减少误选。这和写提示词的经验是一致的:边界比定义更有信息量。

还有一个实践建议:把注册表说明和组件实现放在同一个文件里。分开放的结果是改了组件忘了改说明,模型继续按过时的描述选组件,而这种不一致没有任何机制能自动发现。

什么时候根本不该用生成式界面

最后一节反过来问:这套东西什么时候不该用?

界面形态稳定时不要用。 如果你的场景就是「查天气返回天气卡片」,那直接写死这个映射,不需要让模型决定。生成式界面的成本(注册表维护、校验、兜底、测试)只有在形态真的多变时才划算。

需要精确对齐品牌规范时慎用。 模型的组合会突破你的设计系统的预期,尤其是声明式范式。

表单类交互要特别小心。 生成式界面产出的表单,它的校验规则、提交逻辑、错误处理都要你自己接住。一个由模型临时组合出来的表单很容易在这些地方留下漏洞。

一条可操作的判断:如果你能列出全部可能的界面形态,并且这个列表不长,那就不要用生成式。

源码导读

动手实验

🧪 D5 实验:三个生成式组件加一套映射与兜底机制

代码位置:labs/frontend-agent-ux-7days/day-05-generative-ui

验收标准:

  1. 正常剧本下三个静态组件与一棵声明式树都正确渲染
  2. 未注册组件显示明确说明,而不是白屏或崩溃
  3. 数据不合法显示的是另一套文案,与未注册组件区分开
  4. 声明式树里混入非法节点时,其余节点照常渲染
  5. 任何一个块渲染失败都不影响后续内容
  6. pnpm typecheck && pnpm selftest 退出码为 0

做的时候注意:兜底文案是给用户看的,不是把技术错误抛给用户。「这个版本还不支持某某」比「Unknown component type」有用得多。

  1. 定义组件注册表,实现静态生成式的三个组件
  2. 实现一个声明式渲染器,按界面描述渲染嵌套结构
  3. 用未注册组件剧本验证兜底不崩且提示清晰
  4. 处理流式中途的不完整结构化输出
  5. 写一份三种范式的选型对照,说明各自的适用场景

面试题

今天 4 道题在下方题库区,侧重生成式界面的范式选型、未知输入的兜底设计、模型产出代码的安全边界。展开后先看"分析过程"再看要点——照着推导练,比背要点管用。标注"国内高频 / 海外高频"方便按目标市场取舍。

检查清单与明日预告

  • 能说出生成式界面三种范式的差别,并按控制权与自由度把它们排序
  • 能实现组件映射表与未注册组件的兜底,让模型给出意外类型时界面不崩
  • 能说清开放式生成的安全风险,并给出一条明确的生产选型建议
  • 能说出什么情况下根本不该用生成式界面
  • 实验的 6 条验收标准全部通过
  • 4 道面试题不看要点也能答出至少 3 道

明天(D6)我们把前五天做出来的这些零件组装成一个真正能用的工作台:中途打断、失败重试、编辑上一条重发、从某一轮分叉出新分支,再加上长任务的进度呈现与子 Agent 的可视化。到今天为止我们处理的都是「一次运行之内」的事,明天开始处理多次运行之间的关系——这是会话与单次问答的分水岭。

面试题库

  • 生成式界面的三种范式分别把多少控制权交给模型?你在生产里会选哪种?How much control do the three generative UI paradigms hand to the model, and which would you choose in production?
    国内高频海外高频进阶#generative-ui#architecture#decision-making

    分析过程 · 先想清楚再作答

    1. 这题要能把三种范式说清楚,但真正的区分度在于你有没有一条能判断新方案的判据,而不是背下三个名字。
    2. 先给分类:静态生成式,前端定组件、模型只选哪个并填数据;声明式,模型返回一棵界面描述树、前端按白名单和自己的样式渲染;开放式,模型直接产出界面代码。自由度递增,可控性递减。
    3. 然后给判据,这是答案的核心:**模型产出的是数据还是代码**。前两种是数据,可以校验、可以过白名单、可以在不认识时降级;第三种是代码,一旦执行就没有中间地带,你没法部分执行一段脚本。这条判据能用来归类任何新出现的方案,包括那些起了新名字的。
    4. 生产选型给一条明确的递进路径:**从静态开始,需要布局自由度时升到声明式,除非有非常强的理由并准备好完整沙箱,否则不用开放式。** 敢给出明确建议比罗列三种各有优劣更能体现判断力。
    5. 补一句成本视角:生成式界面不是免费的,注册表维护、props 校验、兜底文案、测试都是持续成本。只有界面形态真的多变时才划算。
    6. 可预期的追问是「什么时候根本不该用」——如果你能列出全部可能的界面形态而且这个列表不长,那就写死映射,不要引入生成式。另外表单类交互要特别小心,模型临时组合出来的表单,它的校验、提交、错误处理都要你自己接住。

    How to reason about it · think before answering

    1. You need to describe all three, but the real signal is whether you have a criterion for classifying new approaches rather than three memorized names.
    2. The taxonomy: static generative, where the frontend owns components and the model only picks one and fills data; declarative, where the model returns a UI description tree that the frontend renders against a whitelist and its own styling; and open-ended, where the model emits UI code directly. Freedom increases, control decreases.
    3. Then the criterion, which is the heart of the answer: **does the model produce data or code?** The first two produce data, which can be validated, whitelisted, and degraded when unrecognized. The third produces code, and executing code has no middle ground — you cannot partially run a script. This criterion classifies any new approach, whatever it is branded.
    4. For production, give a clear escalation path: **start static, move to declarative when you need layout freedom, and avoid open-ended unless you have a strong reason and a complete sandbox.** Committing to a recommendation shows more judgment than listing pros and cons three times.
    5. Add the cost view: generative UI is not free. Registry upkeep, prop validation, fallback copy, and tests are ongoing costs, justified only when the range of interfaces genuinely varies.
    6. Expect the follow-up on when not to use it at all: if you can enumerate every possible interface and the list is short, hardcode the mapping. Also be careful with forms, since a model-assembled form leaves validation, submission, and error handling for you to catch.

    答题要点

    • 三种范式是静态生成式、声明式、开放式,自由度递增而可控性递减。
    • 判据是模型产出的是数据还是代码:数据能校验能降级,代码一旦执行没有中间地带。
    • 生产路径:从静态开始,需要布局自由度升到声明式,没有完整沙箱不用开放式。
    • 生成式有持续成本(注册表、校验、兜底、测试),只有形态真多变时才划算。
    • 能列全所有界面形态且列表不长时,直接写死映射,不要用生成式。

    Key points

    • The three are static generative, declarative, and open-ended, with rising freedom and falling control.
    • The criterion is whether the model emits data or code: data can be validated and degraded, code cannot be partially executed.
    • Production path: start static, escalate to declarative for layout freedom, avoid open-ended without a full sandbox.
    • Generative UI carries ongoing costs and only pays off when interface shapes genuinely vary.
    • If you can enumerate every interface and the list is short, hardcode the mapping instead.
  • 模型返回了一个你没注册过的组件类型,界面应该怎么处理?The model returns a component type you never registered. How should the interface handle it?
    国内高频海外高频进阶#generative-ui#error-handling#resilience

    分析过程 · 先想清楚再作答

    1. 这题看着是个小细节,实际考的是你对「部分失败」的处理习惯。答「报错」或者「不渲染」都太粗糙,接不住追问。
    2. 第一条原则:**降级这一块,而不是让整个回答失败**。模型给了一个不认识的组件,不该让整条回复变成空白——其余内容通常仍然有用。这一条在声明式范式里同样成立:树里一个节点不认识,就把那个节点降级成纯文本,其余节点照常渲染。
    3. 第二条:**两种失败要分开**。组件名不在注册表里,和组件名认识但数据不合法,对排查的指向完全不同。前者说明 prompt 里的说明不清楚或者上游加了新组件而前端没跟上,后者说明字段错了。合并成一个「渲染失败」会让排查无从下手。
    4. 第三条是文案:兜底界面是给**用户**看的,不是把技术错误抛给用户。「这个版本还不支持某某,其余内容不受影响」比「Unknown component type」有用得多,而且前者暗示升级可能解决,后者什么都没说。
    5. 还有一个容易漏的防护值得主动提:**嵌套深度上限**。声明式的树可能深到爆栈,这不需要恶意,递归生成跑偏就够了。设个上限,超了截断并留一句可见说明。
    6. 可预期的追问是「怎么减少这种情况」——把注册表说明写进 system prompt 并写清楚,模型选错组件多半是那段说明写得不好。这是个容易被忽略的调试入口。

    How to reason about it · think before answering

    1. It looks like a detail but tests your habits around partial failure. 'Throw' or 'render nothing' are too coarse and do not survive follow-ups.
    2. Principle one: **degrade that block, do not fail the whole response**. An unrecognized component should not blank out the entire reply, since the rest is usually still useful. The same holds in the declarative paradigm: degrade the unknown node to plain text and render its siblings normally.
    3. Principle two: **distinguish the two failure modes**. A component missing from the registry and a registered component with invalid data point in completely different directions — the first suggests unclear prompt documentation or an upstream addition the frontend has not caught up with, the second means the fields are wrong. Collapsing both into 'render failed' leaves you nowhere to start.
    4. Principle three is copy: the fallback is for **users**, not a place to dump technical errors. 'This version does not support X yet; the rest is unaffected' beats 'Unknown component type', and it hints that upgrading may help.
    5. One more guard worth raising unprompted: **a nesting depth limit**. Declarative trees can be deep enough to blow the stack, and that needs no malice, only a recursive generation that wanders. Cap it, truncate, and leave a visible note.
    6. Expect the follow-up on reducing occurrences: put the registry descriptions into the system prompt and write them well. Models usually pick the wrong component because that documentation was vague — an easily overlooked debugging entry point.

    答题要点

    • 降级这一块而不是让整个回答失败,其余内容通常仍然有用。
    • 未注册组件与数据不合法要分开报,两者对排查的指向完全不同。
    • 兜底文案写给用户看,说明现象与影响范围,不要抛技术错误。
    • 声明式树里还要设嵌套深度上限,递归生成跑偏就能产出爆栈的树。
    • 减少这类情况的入口是把注册表说明写清楚并放进 system prompt。

    Key points

    • Degrade that block rather than failing the whole response; the rest is usually still useful.
    • Report unknown-component and invalid-props separately, since they point to different causes.
    • Write fallback copy for users, describing the effect and its scope, not a raw technical error.
    • Declarative trees also need a depth cap, since runaway recursive generation can blow the stack.
    • Reduce occurrences by writing clear registry descriptions into the system prompt.
  • 让模型直接生成前端代码并在用户浏览器里执行,有哪些风险?你会怎么设边界?What are the risks of having a model generate frontend code that runs in the user's browser, and how would you bound them?
    国内高频海外高频深入#security#generative-ui#prompt-injection

    分析过程 · 先想清楚再作答

    1. 这是道安全题,区分度在于你能不能说出攻击路径,而不是泛泛地说「不安全」。
    2. 先说清楚风险的核心:**模型产出的代码会以你的页面权限运行**。它能读 cookie、读 localStorage、发任意请求、改任意 DOM。这不是理论风险,页面里的脚本本来就有这些能力。
    3. 然后是关键的一步,也是多数人答不出来的:**模型是可以被它读到的内容影响的**。一封邮件、一个网页、一份用户上传的文档,里面都可能藏着让它产出恶意代码的指令。所以这条攻击路径**不需要模型变坏,只需要它听话**——它忠实执行了它在文档里读到的指令,而那份文档是攻击者写的。能说出这一层,就说明你理解了提示注入与生成式界面叠加起来的后果。
    4. 边界怎么设:首选是根本不用这种范式,改用声明式——模型给的是数据不是代码,能校验能白名单能降级。这是唯一能真正消除风险的做法。
    5. 如果非用不可,沙箱是最低要求:独立 origin 的 iframe 加严格 CSP、禁用同源访问、不传任何凭据进去。但要诚实说出它的两个代价:沙箱逃逸的历史很长,不能算完全消除;而且沙箱会让生成的界面失去与主应用交互的能力,**而那恰恰是当初想用开放式的理由**。
    6. 可预期的追问是「那业界为什么还有人做」——做的多是内部工具或者演示场景,那里输入可控、受众可信。判断依据是模型读到的内容是不是可能来自不可信来源。

    How to reason about it · think before answering

    1. A security question where the signal is naming the attack path, not saying 'it is unsafe'.
    2. Start with the core: **the generated code runs with your page's privileges**. It can read cookies and localStorage, issue arbitrary requests, and modify any DOM. That is not theoretical; any script on the page has those powers.
    3. Then the step most candidates miss: **the model is influenced by whatever it reads**. An email, a web page, or a user-uploaded document can carry instructions to emit malicious code. So the attack path **does not require the model to turn malicious, only to be obedient** — it faithfully follows instructions it found in a document an attacker wrote. Naming this shows you understand prompt injection compounded by generative UI.
    4. On bounding it: the preferred answer is not to use the paradigm at all and use declarative instead, where the model emits data rather than code and you can validate, whitelist, and degrade. That is the only approach that truly removes the risk.
    5. If you must, a sandbox is the floor: a separate-origin iframe with a strict CSP, no same-origin access, and no credentials passed in. Be honest about the two costs: sandbox escapes have a long history so the risk is reduced rather than eliminated, and the sandbox strips the generated UI of the ability to interact with the host app — **which was the reason to want open-ended in the first place**.
    6. Expect the follow-up on why anyone ships it: mostly internal tools and demos, where inputs are controlled and the audience is trusted. The deciding question is whether the model's inputs can come from untrusted sources.

    答题要点

    • 核心风险是生成的代码以页面权限运行,能读 cookie 与本地存储、发任意请求、改任意 DOM。
    • 攻击路径不需要模型变坏只需要它听话:它读到的邮件、网页、文档里可能藏着指令。
    • 首选边界是改用声明式范式,让模型产出数据而非代码,可校验可白名单可降级。
    • 必须用时的最低要求是独立 origin 的 iframe 加严格 CSP 且不传凭据。
    • 要诚实承认沙箱的代价:不能完全消除风险,且会让生成界面失去与主应用交互的能力。

    Key points

    • The core risk is that generated code runs with page privileges: cookies, storage, arbitrary requests, full DOM access.
    • The attack needs no malicious model, only an obedient one, since instructions can hide in emails, pages, or uploaded documents.
    • The preferred boundary is switching to declarative so the model emits data that can be validated, whitelisted, and degraded.
    • If unavoidable, the floor is a separate-origin iframe with a strict CSP and no credentials.
    • Be honest that sandboxing reduces rather than removes risk, and costs the interaction that motivated open-ended in the first place.
  • 结构化输出也是流式生成的,界面在 JSON 只到一半时应该怎么渲染?Structured output also streams. How should the interface render a JSON payload that is only half complete?
    国内高频海外高频进阶#streaming#generative-ui#ui-state

    分析过程 · 先想清楚再作答

    1. 这题本身不难,但它是个很好的检验:你有没有意识到「不完整的数据」在流式系统里是常态而不是异常。
    2. 直接答案:能 parse 就渲染,不能就显示占位,**绝不把半截 JSON 原文显示给用户**。半截 JSON 出现在界面上,用户会以为程序崩了。
    3. 值得往上抽一层的是这个模式的重复性。本课里它出现了三次:消息增量拼到一半、工具参数拼到一半、生成式载荷拼到一半。**每一处接收流式数据的地方都要回答同一个问题:只到一半时显示什么。**
    4. 占位内容的选择有讲究:如果知道要渲染的是什么组件(比如 component 字段已经先到了),可以显示那个组件的骨架屏,用户看到的是「表格正在生成」而不是一个通用转圈。载荷字段的顺序因此是可以设计的——把决定形态的字段放前面。
    5. 另一个实现细节:不要每来一个字符就试着 parse 一次,那是纯粹的浪费。可以按帧节流,或者等到明显的边界(比如收到结束事件)再解析。这一条和 D2 的按帧合并是同一个思路。
    6. 可预期的追问是「有没有办法让部分 JSON 也能用」——有,流式 JSON 解析器可以产出部分对象,让已经到齐的字段先渲染。代价是实现复杂度,而且要处理「字段后来又变了」的情况。多数场景下先占位再整体渲染就够了。

    How to reason about it · think before answering

    1. Not a hard question, but a good check on whether you see incomplete data as the norm in streaming systems rather than an exception.
    2. The direct answer: render if it parses, show a placeholder if it does not, and **never display the partial JSON**. Half-formed JSON on screen reads as a crash.
    3. Worth abstracting one level: this pattern recurs. In this course it appears three times — partial message deltas, partial tool arguments, partial generative payloads. **Every place that receives streaming data must answer the same question: what do you show when it is half there?**
    4. Placeholder choice matters: if you already know which component is coming (because the component field arrived first), show that component's skeleton so the user sees 'a table is being generated' rather than a generic spinner. Field order in the payload is therefore designable — put the shape-determining fields first.
    5. An implementation detail: do not attempt a parse on every character, which is pure waste. Throttle per frame or wait for a clear boundary such as the end event. Same thinking as the per-frame batching from day two.
    6. Expect the follow-up on making partial JSON usable: streaming JSON parsers can emit partial objects so completed fields render early. The costs are complexity and having to handle fields that later change. For most cases, placeholder then full render is enough.

    答题要点

    • 能 parse 就渲染,不能就显示占位,绝不显示半截 JSON 原文。
    • 这是流式系统的通用模式:消息增量、工具参数、生成式载荷都要回答同一个问题。
    • 占位可以按已到达的组件字段显示对应骨架屏,所以载荷字段顺序是可设计的。
    • 不要每个字符都试着 parse,按帧节流或等结束事件,思路同按帧合并。
    • 流式 JSON 解析器能让已到齐的字段先渲染,代价是复杂度与字段可能回改。

    Key points

    • Render if it parses, otherwise show a placeholder, and never display the raw partial JSON.
    • This is the general streaming pattern: message deltas, tool arguments, and generative payloads all pose the same question.
    • Placeholders can be component-specific skeletons once the component field arrives, so payload field order is worth designing.
    • Do not parse on every character; throttle per frame or wait for the end event, mirroring per-frame batching.
    • Streaming JSON parsers can render completed fields early, at the cost of complexity and fields that may later change.

评论