尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Streamlit Custom Components v2(CCv2)开发实战:从内联脚本到可打包的交互组件

Streamlit Custom Components v2(CCv2)开发实战:从内联脚本到可打包的交互组件 Streamlit Custom Components v2CCv2开发实战从内联脚本到可打包的交互组件【免费下载链接】streamlitStreamlit — A faster way to build and share data apps.项目地址: https://gitcode.com/gh_mirrors/st/streamlit导读当 Streamlit 内置组件无法满足你的 UI 需求从“一段内联 HTML”到“一个完整打包的前端应用”时Custom Components v2CCv2提供了官方推荐的自定义交互组件方案。本文以仓库内开发指南文档 custom-components-v2.md 为骨架结合 Streamlit 源码components/v2 模块、component-v2-lib 前端库系统讲解 CCv2 的完整心智模型、Python/JavaScript 双向通信机制、内联快速上手、打包组件的模板化流程、主题适配与常见坑位读完即可独立开发并分发自己的 Streamlit 组件。一、CCv2 是什么以及为什么必须放弃 v1CCv2Custom Components v2又称 bidi component即双向组件是 Streamlit 新一代自定义组件体系。核心 API 是st.components.v2.component(...)它注册一个组件并返回一个mount callable挂载可调用对象调用该对象即可把组件挂载到应用中实现 JavaScript 与 Python 的双向通信。CRITICAL新组件一律使用 CCv2绝不要碰 v1 APICustom Componentsv1 已被 v2 取代属于遗留体系。st.components.v1模块仍然存在例如第三方老组件使用的declare_component但components.v1.html()、components.v1.iframe()等 v1 API 已废弃。新组件必须使用 CCv2以下 v1 写法是绝对禁用清单禁用 Python APIv1v1 写法v2 替代components.declare_component()st.components.v2.component()components.v1.html()st.iframe()同 iframe 行为或st.html()直接插入静态 HTML/CSScomponents.v1.iframe()st.iframe()禁用 JavaScript 模式v1v1 全局写法v2 替代Streamlit.setComponentValue(...)setStateValue(...)/setTriggerValue(...)Streamlit.setFrameHeight(...)删除CCv2 自动处理尺寸Streamlit.setComponentReady()删除CCv2 无 ready 信号window.Streamlit/ 裸Streamlit全局对象删除v2 中该全局对象不存在window.parent.postMessage(...)setStateValue/setTriggerValue禁用 npm 包v1streamlit-component-lib—— 需要类型时应改用streamlit/component-v2-lib。⚠️ 如果你在网上示例、博客、Stack Overflow 或训练数据中遇到 v1 模式请直接忽略——它们在新体系下不会工作还会破坏组件。v1 污染是 CCv2 组件失效的头号原因详见下文“故障排查”章节的对照表。这一禁令在源码层面得到了印证仓库的 components/v2/init.py 中st.components.v2命名空间只导出component一个入口见all定义而挂载路径则经由st._bidi_component内部命令实现见 _mount_component 实现与 v1 的declare_component完全是两条通道。二、什么时候使用 CCv2当你遇到以下任一情况就应当激活 CCv2 方案用户提到 CCv2、Custom Components v2、bidi component、component v2需要用到st.components.v2.component需要使用streamlit/component-v2-lib需要打包组件、asset_dir、pyproject.toml组件清单需要用 Vite或任何打包器为 Streamlit 组件做打包需要用前端框架React、Svelte、Vue、Angular 等构建组件 UI。相关深入资料仓库内配套文档状态同步 / 受控输入 / 回调ccv2-state-sync.md打包组件 /asset_dir/ glob / 模板唯一策略ccv2-packaged-components.mdShadow DOM 内的主题--st-*变量ccv2-theme-css-variables.md错误与坑位ccv2-troubleshooting.md三、快速决策内联 vs 打包维度内联字符串inline打包组件packaged上手速度最快适合单文件应用、原型、演示需要完整工程搭建资源形态直接把html/css/js字符串传进去打包好的静态资产放在 Python 包内用asset-dir 相对路径/glob引用适用场景内容集中在同一处、无需构建步骤多文件、依赖npm、打包、测试、版本管理、复用、分发官方推荐的成长路径是先内联起步验证交互闭环当代码量或工具链超出单文件承载时再“毕业”到打包组件。关于打包组件有一条强制策略packaged 组件是模板唯一的template-only必须从 Streamlit 官方component-templatev2 起步禁止手写脚手架或从网络示例复制打包骨架详见第五节。四、CCv2 运行模型挂载、渲染与结果对象从源码看CCv2 的完整运行链路可以分为四步对应 components/v2/init.py 与 bidi_component/state.py 的实现Python 注册调用st.components.v2.component(...)注册组件返回mount callable。注册时会做参数类型守卫css/js只接受字符串或None并通过build_definition_with_validation与pyproject.toml中预注册的组件定义做校验合并见 _register_component。挂载调用 mount callable传入data...、布局参数width、height以及可选的on_key_change回调。挂载最终落在st._bidi_component(...)见 _mount_component。前端渲染前端默认导出default export函数以({ data, key, name, parentElement, setStateValue, setTriggerValue })为参数执行。前端参数类型由 component-v2-lib 的 FrontendRendererArgs 定义支持通过泛型为data和状态键提供类型安全。返回结果对象组件函数返回的结果对象其属性对应state keys状态键和trigger keys触发键。该对象基于AttributeDictionary实现见 bidi_component/state.py 的 ComponentResult因此既支持result.value属性访问也支持result[value]键访问state 值持久、trigger 值在一次脚本运行后重置为None。data参数的序列化范围来自 types.py 的 ComponentRenderer 文档支持 JSON 可序列化对象如Dict[str, str | int]、List[str]、Arrow 可序列化对象如pandas.DataFrame、原始字节仅限顶层以及字典键必须是 Python 原生类型Arrow 序列化仅支持顶层或字典一层深度。五、最佳实践用自己的 Python API 包装 mount callable官方强烈建议对外暴露你自己的 Python 函数在内部包装st.components.v2.component(...)返回的 callable。这样做的好处是给最终用户一个干净、稳定的 API 面类型化参数、校验、友好的默认值把data...、default...、回调接线等细节作为内部实现隐藏起来。两条重要守则组件只声明一次通常在模块导入时。避免在多次调用的函数内部定义并注册组件——你可能会意外重复注册组件名导致令人困惑的行为。源码中component()的文档也明确警告同名组件重复注册会记录警告并使用最后注册者见 components/v2/init.py。回调是可选的但如果你希望结果属性总是存在请提供哪怕是空的回调。官方推荐的包装范式import streamlit as st from collections.abc import Callable _MY_COMPONENT st.components.v2.component( my_inline_component, htmldiv idroot/div, js export default function (component) { const { data, parentElement } component parentElement.querySelector(#root).textContent data?.label ?? } , ) def my_component( label: str, *, key: str | None None, on_value_change: Callable[[], None] | None None, on_submitted_change: Callable[[], None] | None None, ): # Callbacks are optional, but if you want result attributes to always exist, # provide (even empty) callbacks. if on_value_change is None: on_value_change lambda: None if on_submitted_change is None: on_submitted_change lambda: None return _MY_COMPONENT( data{label: label}, keykey, on_value_changeon_value_change, on_submitted_changeon_submitted_change, )六、内联快速上手打通最小的“双向循环”再次强调只用 v2 API。你的 JS 必须export default function(component)并解构{ setStateValue, setTriggerValue, parentElement, data }绝不使用Streamlit.setComponentValue()、window.Streamlit或任何 v1 模式。最小“bidi 循环”由两个方向组成JS → Python通过setStateValue(...)发出持久状态、setTriggerValue(...)发出一次性事件Python → JS每次运行时通过data...重新水合re-hydrateUI。import streamlit as st HTML input idtxt /button idbtn typebuttonSubmit/button JS \ export default function (component) { const { data, parentElement, setStateValue, setTriggerValue } component const input parentElement.querySelector(#txt) const btn parentElement.querySelector(#btn) if (!input || !btn) return const nextValue (data data.value) ?? if (input.value ! nextValue) input.value nextValue input.oninput (e) { setStateValue(value, e.target.value) } btn.onclick () { setTriggerValue(submitted, input.value) } } my_text_input st.components.v2.component( my_inline_text_input, htmlHTML, jsJS, ) KEY txt-1 component_state st.session_state.get(KEY, {}) value component_state.get(value, ) result my_text_input( keyKEY, data{value: value}, on_value_changelambda: None, # optional; include to always get result.value on_submitted_changelambda: ( None ), # optional; include to always get result.submitted ) st.write(value (state):, result.value) st.write(submitted (trigger):, result.submitted)两个内联细节值得注意内联 JS/CSS 应使用多行字符串。CCv2 会把“看起来像路径”的字符串当作文件引用处理多行字符串可以被无歧义地识别为内联内容这也是 ccv2-troubleshooting.md 中“路径启发式”的要点。优先在parentElement下查询元素而不是document避免多实例之间互相泄漏。七、State 与 Trigger如何理解键的语义StatesetStateValue(value, ...)跨应用重跑持久化对已挂载实例存储在st.session_state[key]下。对应结果对象上的 state 属性长期存在直到被显式修改。TriggersetTriggerValue(submitted, ...)一次性事件载荷仅用于一次重跑重跑后重置为None。读取 Trigger 的两种姿势挂载之后直接用result.submitted在on_submitted_change回调内部用st.session_state[key].submitted回调运行于脚本主体之前此时你还没有result。Defaults如果你为某个 state 键传了default{...}则必须同时传对应的on_key_change回调参数否则 Streamlit 会报错。受控输入的完整模式与坑位参见 ccv2-state-sync.md。该文档给出了受控文本输入的规范范式核心要点包括不存在内建的双向绑定前端显式调用setStateValue/setTriggerValue发出状态JS 读取component.data并更新 DOM 来完成水合两边都需要你亲手实现。只有值不同时才赋值给input.value否则会和用户的输入光标“打架”。default只对 state 键生效trigger 不支持默认值transient 且默认None。Python → JS 水合有两种模式initial-only仅首挂载读取data.initialX不会反映后续 Python 变更与 true sync受控每次渲染都按data.value对账、仅在变化时写入。需要 Python 能更新 UI 时务必用 true sync。Session State 时序在组件挂载之后同一运行内修改st.session_state.key.field可能报错。安全的做法是在挂载之前更新状态例如按钮处理器放在挂载调用之前或在另一轮运行中更新先设置状态再触发重跑。八、打包组件模板唯一策略与完整工作流当出现以下需求时就从内联“毕业”到打包组件需要多个前端文件组件/模块而不是一个大字符串需要引入前端库npm 依赖并运行打包器需要测试、CI、版本管理或分发PyPI / 私有索引。模板唯一策略mandatory必须以 Streamlit 官方component-templatev2 为起点绝不手写打包/清单/构建接线绝不从网络示例、博客、gist 或文档复制打包脚手架如果拿到的是非模板脚手架先基于模板重新生成再迁移组件逻辑自定义模板时只用 v2 APIst.components.v2.component、setStateValue、setTriggerValue、parentElement、data不得引入st.components.v1、declare_component()、Streamlit.setComponentValue()等 v1 写法必须保证js/css的 glob 在清单的asset_dir下恰好匹配一个文件必须用streamlit run ...验证单纯的python -c import ...对打包组件可能是假阴性。前置条件Python 构建工具uv推荐cookiecutter前端构建工具Node.js npm。前端框架React 是可选而非必须官方component-templatev2 同时支持React TypeScriptVite与Pure TypeScriptVite两种骨架CCv2 也兼容任何能编译成 JavaScript 的前端框架Svelte、Vue、Angular、vanilla TS/JS 等。唯一硬性要求是把 JS/CSS 资产产出到组件的asset_dir然后在 Python 侧用html...、js...、css...以asset-dir 相对路径/glob注册。推荐安装streamlit/component-v2-lib以获得端到端类型安全它提供FrontendRenderer/FrontendRendererArgs等 TypeScript 类型让你的默认导出渲染器拿到带类型的data载荷以及带泛型的 state/trigger 键见 component-v2-lib 的 types.ts 中的类型化组件示例。生成新的 CCv2 组件项目这是每个打包 CCv2 组件的规定起点命令uvx --from cookiecutter cookiecutter gh:streamlit/component-template --directory cookiecutter/v2非交互式生成时必须显式传入 cookiecutter 键不要依赖默认值。模板键如下键说明示例值author_name作者名Your Nameauthor_email作者邮箱youexample.comproject_name项目名Streamlit Breadcrumbspackage_name发行包名streamlit-breadcrumbsimport_name导入包名streamlit_breadcrumbsdescription描述Packaged Streamlit CCv2 breadcrumb componentopen_source_license开源协议Apache-2.0framework前端骨架React Typescript或Pure Typescript推荐的完整非交互调用以假设的面包屑组件为例uvx --from cookiecutter cookiecutter gh:streamlit/component-template \ --directory cookiecutter/v2 \ --no-input \ author_nameYour Name \ author_emailyouexample.com \ project_nameStreamlit Breadcrumbs \ package_namestreamlit-breadcrumbs \ import_namestreamlit_breadcrumbs \ descriptionPackaged Streamlit CCv2 breadcrumb component \ open_source_licenseApache-2.0 \ frameworkReact Typescript注意选择值必须与模板选项完全一致framework只能是React Typescript或Pure Typescript。传齐所有键可以避免模板占位名和后期的重命名折腾。离线 / 气隙环境把gh:streamlit/component-template换成本地路径即可uvx --from cookiecutter cookiecutter /path/to/component-template --directory cookiecutter/v2开发循环模板默认流程先激活目标项目环境source /path/to/project/.venv/bin/activate构建前端资产在import_name/frontend下npm i npm run build可编辑安装在包含pyproject.toml的项目根目录uv pip install -e . --force-reinstall用 Streamlit 运行示例应用streamlit run example.py为什么是这个顺序先构建确保asset_dir里已存在预期文件重命名键之后重新做可编辑安装让元数据与导入路径保持同步。验证构建产物预防绝大多数加载失败确认清单的asset_dir存在且包含构建产物确认你在 Python 侧注册的每个 glob 在asset_dir下恰好匹配一个文件典型是jsindex-*.js与cssindex-*.css若出现多个匹配先清理构建输出模板命令npm run clean再重建。React 模板的三文件数据流千万别跳过index.tsxReact cookiecutter 模板的数据流横跨三个文件Python 传出的data{...}必须依次流经它们__init__.py—— Python 包装器把data{name: name, testId: test_id, ...}发给前端index.tsx—— 桥接文件解构data并把 props 传给 React 组件const { name, testId } data;→MyComponent name{name} testId{testId} /MyComponent.tsx—— 接收 props 并渲染 UIbutton>.card { background: var(--st-secondary-background-color); color: var(--st-text-color); border: 1px solid var(--st-border-color); border-radius: var(--st-base-radius); } .primaryButton { background: var(--st-primary-color); color: var(--st-background-color); border-radius: var(--st-button-radius); }序列化规则与安全兜底变量源自 Streamlit 主题对象并被序列化为字符串字符串直接透传如--st-primary-color: #ff4b4b数字转为字符串如--st-base-font-weight: 400布尔值变成1或0如--st-link-underline数组变成逗号拼接的字符串如--st-heading-font-sizes: 2.75rem,2.25rem,...若需在 JS 中取单个值按,分割缺失值null/undefined变成unset让消费者回退到 CSS 初始/继承行为。90% 常用变量速查表用途变量页面背景--st-background-color面板/卡片背景--st-secondary-background-color正文文字--st-text-color标题--st-heading-color、--st-heading-font主色/强调--st-primary-color链接--st-link-color、--st-link-underline边框/分隔线--st-border-color、--st-border-color-light控件描边--st-widget-border-color圆角--st-base-radius、--st-button-radius代码块--st-code-background-color、--st-code-text-color、--st-code-font其他分类基础 token 还包括字体族--st-font、--st-heading-font、--st-code-font、正文字号/字重--st-base-font-size、--st-base-font-weight、H1–H6 标题字号与字重的数组变量--st-heading-font-sizes、--st-heading-font-weights及其逐级便捷变量--st-heading-font-size-1~--st-heading-font-size-6、--st-heading-font-weight-1~--st-heading-font-weight-6数据展示 token 有--st-dataframe-border-color、--st-dataframe-header-background-color图表调色板是逗号拼接数组--st-chart-categorical-colors离散系列、--st-chart-sequential-colors低→高、--st-chart-diverging-colors围绕中点的负↔正。语义/状态色板适合徽标、告警、校验提示不适合主布局面每个家族通常有三种变体基础--st-name-color、背景--st-name-background-color、文字--st-name-text-color覆盖 red、orange、yellow、blue、green、violet、gray 七族如--st-red-color、--st-green-background-color等。字母序完整索引见 ccv2-theme-css-variables.md。十一、故障排查与高频坑位遇到“应该能用却不行”的情况从 ccv2-troubleshooting.md 的以下清单入手1. v1 污染最常见的失效原因症状组件渲染为空白/空 iframe、控制台报Streamlit is not defined、setComponentValue is not a function、组件永远无法与 Python 通信。禁用写法v1正确写法v2st.components.v1st.components.v2.component(...)components.declare_component()st.components.v2.component(...)components.html()st.components.v2.component(...)配合htmlStreamlit.setComponentValue(val)setStateValue(key, val)或setTriggerValue(...)Streamlit.setFrameHeight()直接删除v2 自动处理尺寸Streamlit.setComponentReady()直接删除v2 无 ready 信号window.Streamlit使用export default function解构出的参数window.parent.postMessage(...)使用setStateValue/setTriggerValuestreamlit-component-libnpmstreamlit/component-v2-lib需要类型时function sendMessageToStreamlitClient直接删除改用 v2 回调参数修复在全部.py和.js/.ts/.tsx文件中搜索这些模式并替换为 v2 等价写法。cookiecutter 模板生成的是正确的 v2 代码——如果你从模板起步污染一定来自你的自定义部分。2. 打包资产与清单asset_dir、组件键报错 “Component must be declared in pyproject.toml with asset_dir to use file-backed js/css” 时你传了路径风格的js/css字符串如index-*.js但 Streamlit 找不到该组件键对应的asset_dir。修复想要内联 JS/CSS传多行的实际代码字符串不是路径想要打包资产确保 wheel 内含带[[tool.streamlit.component.components]] ... asset_dir ...的pyproject.toml并以匹配的全限定键st.components.v2.component(project.component, js..., css...)调用。注意此错误在部分环境下用纯 Python import 测试打包包装器时是预期现象请用streamlit run ...验证因为清单发现是 Streamlit 运行时初始化的一部分。3. 内联字符串 vs 文件资产路径启发式CCv2 使用启发式“看起来像路径”的字符串会被当作文件引用多行字符串一律视为内联内容。修复内联html/css/js优先用三引号多行字符串js/css中避免单行压缩过的 JS/CSS必要时加一个换行。4. Glob 零匹配或多个匹配glob 在asset_dir下必须恰好匹配一个文件。修复重建前先清理构建输出目录让打包器输出可预测的index-hash.js/index-hash.css或assets/子目录下的assets/index-hash...从模板起步的话在frontend/下运行npm run clean清掉build/输出保证index-*.js只匹配一个文件。5. 重命名后仍显示旧模板名症状路径或元数据里残留streamlit-component-x/streamlit_component_x导入、清单组件键、注册键不再对齐。修复按第八节“重命名清单”把所有相关面一起更新并在重命名后重建前端、重装可编辑包。6.default、回调与缺失的结果属性default{...}只作用于state 键且挂载时 Streamlit 要求这些键通过on_key_change回调参数声明。修复传default{value: ...}时同时传on_value_changelambda: Nonetrigger 没有默认值transient 且默认None。7. Pythonkey与前端key的区别Python 侧的key是用户可见的 Streamlit 元素键前端还会收到一个由Streamlit 生成的key字符串它与 Python 的key不是同一个除非你显式通过data传用户键。修复前端需要稳定标识符时在data{user_key: key, ...}中传递。8. Shadow DOM /isolate_styles的意外isolate_stylesTrue默认时组件挂在shadow root中parentElement是ShadowRoot注入到 document 的全局 CSS如 Tailwind不会自动作用于组件。修复保持isolate_stylesTrue并用 CSS 变量与组件局部样式仅在确实需要全局样式行为时才用isolate_stylesFalse。9. 前端构建Vite坑位缺少base: ./从 Streamlit 的组件 URL 路径提供资源时相对资源 URL 会失效陈旧的构建产物Vite 输出哈希文件名若保留旧构建index-*.js可能匹配多个文件——构建前清理构建目录。10. DOM 覆盖覆盖注入的 HTML/CSS如果你直接设置parentElement.innerHTML ...会覆盖 Streamlit 从html/css注入的内容。修复优先用querySelector并修改子元素需要动态 HTML 时创建新的子元素并只设置该元素的innerHTML。十二、验证建议与源码印证用streamlit run ...验证打包组件而不是python -c import ...Streamlit 在运行时初始化阶段发现组件清单纯 import 检查可能对正确打包的组件误报asset_dir注册错误。仓库内提供了完整的 v2 组件端到端测试样例e2e_playwright/bidi_components/目录下有 basics.py、session_state_interactions.py、error_handling.py 等应用及配套的*_test.py测试可作为理解 state/trigger 语义与受控输入模式的参考实现。前端类型库 component-v2-lib 提供FrontendRenderer、FrontendRendererArgs、FrontendState、ArrowData等类型FrontendRendererArgsTState, TData通过两个泛型分别约束状态键形状与 Python 传入的data形状FrontendState的每个键对应 Python 侧的on_key_change回调参数。Python 侧的类型与状态结构分别定义在 components/v2/types.pyComponentRenderer协议key、data、default、width、height、**on_callbacks与 components/v2/bidi_component/state.pyComponentResultstate 持久、trigger 单次运行后重置。结语CCv2 是 Streamlit 当前唯一推荐的自定义组件方案用st.components.v2.component注册、用setStateValue/setTriggerValue建立 JS→Python 通道、用data完成 Python→JS 水合即可在“一行内联 JS”与“完整打包并分发到 PyPI 的组件”之间自由伸缩。牢记三条主线绝不混入 v1 API、打包组件从官方模板起步并保持 glob 唯一匹配、用--st-*变量与isolate_styles处理好样式与主题你就能把任何缺失的 UI 变成可复用、可共享的 Streamlit 组件。【免费下载链接】streamlitStreamlit — A faster way to build and share data apps.项目地址: https://gitcode.com/gh_mirrors/st/streamlit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表