[{"data":1,"prerenderedAt":370},["ShallowReactive",2],{"page-\u002Farchitecture\u002F服务器驱动ui的sdui范式讲解之flet":3},{"id":4,"title":5,"body":6,"description":12,"extension":358,"meta":359,"navigation":365,"path":366,"seo":367,"stem":368,"__hash__":369},"content\u002Farchitecture\u002F服务器驱动UI的SDUI范式讲解之Flet.md","服务器驱动UI的SDUI范式讲解之Flet",{"type":7,"value":8,"toc":354},"minimark",[9,13,16,19,22,25,28,31,147,170,185,188,215,218,221,237,249,267,283,286,290,299,302,305,309,316,319,322,329,336,339,350],[10,11,12],"p",{},"到9月份，Flet的正式版1.0终于发布，也表示着Flet可以正式投入到生产中使用了额。这对于Python开发者来说是个很好的消息！按捺不住这个心情，我决定写一个相关的笔记来讲解Flet的具体渲染逻辑是怎么实现的，为什么python可以编写flutter应用。",[10,14,15],{},"Flet 将 Python 代码作为描述 UI 的“语言”，并通过一个中间层（JSON 协议）来驱动 Flutter 渲染，这非常契合 SDL（场景描述语言）的核心理念。但它比传统 SDL 多了一个关键环节：服务器驱动的实时双向同步。",[10,17,18],{},"Flet使用了一个客户端-服务器架构，核心是一个被同步维护的控制树，当Python代码在声明组件的时候，Flet在底层：",[10,20,21],{},"构建控制树，构建出包含组件结构和属性的树状对象；序列化为json，框架会将这个python控制树序列化为json格式；websocket传输，通过websocket连接，将json更新实时推送到flutter客户端；flutter客户端反序列化json，并构建出对应原生的Widget进行渲染。",[10,23,24],{},"我之前说python在这里充当了类似sdl的功能，但是sdl一般是静态的，而flet的python端则是活的后端逻辑，持续运行并维护状态，用户点击按钮事件、事件会回传到python服务器，python代码处理后会更新状态，flet再自动生成新的json差异推送给前端，这个本质上是一种服务器驱动UI的架构，所以叫做SDUI。",[10,26,27],{},"这个和我们之前使用的Nuxt Content的内容驱动不一样，我们内容驱动是View通过服务器接收Model，这个在传统架构就叫做数据驱动\u002F内容驱动。SDUI不仅是传数据，还要传结构。",[10,29,30],{},"传统MVC\u002FMVVM或者前后端分离架构中，服务器只是返回纯粹的业务数据，前端写死UI结构，拿到数据之后，把数据填进UI里，这个时候决定UI的就是前端代码了，服务器只是提供内容。SDUI架构里，服务器不只是给数据，他给结构，服务器直接返回一段描述UI的json，叫做UI Schema：",[32,33,38],"pre",{"className":34,"code":35,"language":36,"meta":37,"style":37},"language-json shiki shiki-themes github-light github-dark","\u002F\u002F 服务器返回的 UI 描述 (包含结构 + 数据)\n{\n  \"type\": \"Card\",\n  \"children\": [\n    { \"type\": \"Heading\", \"text\": \"Kiriyama\" },\n    { \"type\": \"Text\", \"text\": \"Age: 20\" }\n  ]\n}\n","json","",[39,40,41,50,57,74,83,111,135,141],"code",{"__ignoreMap":37},[42,43,46],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"sJ8bj","\u002F\u002F 服务器返回的 UI 描述 (包含结构 + 数据)\n",[42,51,53],{"class":44,"line":52},2,[42,54,56],{"class":55},"sVt8B","{\n",[42,58,60,64,67,71],{"class":44,"line":59},3,[42,61,63],{"class":62},"sj4cs","  \"type\"",[42,65,66],{"class":55},": ",[42,68,70],{"class":69},"sZZnC","\"Card\"",[42,72,73],{"class":55},",\n",[42,75,77,80],{"class":44,"line":76},4,[42,78,79],{"class":62},"  \"children\"",[42,81,82],{"class":55},": [\n",[42,84,86,89,92,94,97,100,103,105,108],{"class":44,"line":85},5,[42,87,88],{"class":55},"    { ",[42,90,91],{"class":62},"\"type\"",[42,93,66],{"class":55},[42,95,96],{"class":69},"\"Heading\"",[42,98,99],{"class":55},", ",[42,101,102],{"class":62},"\"text\"",[42,104,66],{"class":55},[42,106,107],{"class":69},"\"Kiriyama\"",[42,109,110],{"class":55}," },\n",[42,112,114,116,118,120,123,125,127,129,132],{"class":44,"line":113},6,[42,115,88],{"class":55},[42,117,91],{"class":62},[42,119,66],{"class":55},[42,121,122],{"class":69},"\"Text\"",[42,124,99],{"class":55},[42,126,102],{"class":62},[42,128,66],{"class":55},[42,130,131],{"class":69},"\"Age: 20\"",[42,133,134],{"class":55}," }\n",[42,136,138],{"class":44,"line":137},7,[42,139,140],{"class":55},"  ]\n",[42,142,144],{"class":44,"line":143},8,[42,145,146],{"class":55},"}\n",[10,148,149,150,161,162,165,166,169],{},"前端拿到这个 JSON 后，",[151,152,153,154,157,158],"strong",{},"不再是自己写死 ",[39,155,156],{},"\u003Ch1>"," 和 ",[39,159,160],{},"\u003Cp>","，而是用一个“渲染引擎”去解析它。前端看到 ",[39,163,164],{},"type: \"Card\""," 就调出卡片组件，看到 ",[39,167,168],{},"type: \"Heading\""," 就渲染标题。",[10,171,172,173,176,177,180,181,184],{},"这个时候，决定 UI 长什么样的是",[151,174,175],{},"服务器","。如果服务器明天把 ",[39,178,179],{},"Heading"," 换成 ",[39,182,183],{},"Image","，前端根本不需要改代码，甚至不需要重新发版，界面就变了。注意，在这个环节里面控制器发生了转移，UI架构里，谁决定页面的层级结构和组件类型，谁就是driver，如果服务器一起把排版结构都发布过来了，前端就是渲染器了，这个就是服务器驱动，有点像SSR。",[10,186,187],{},"Flet就是一个极端SDUI+远程状态同步机制，你在Python代理里写：",[32,189,193],{"className":190,"code":191,"language":192,"meta":37,"style":37},"language-python shiki shiki-themes github-light github-dark","ft.Column([\n    ft.Text(\"Hello\"),\n    ft.Button(\"Click me\")\n])\n","python",[39,194,195,200,205,210],{"__ignoreMap":37},[42,196,197],{"class":44,"line":45},[42,198,199],{},"ft.Column([\n",[42,201,202],{"class":44,"line":52},[42,203,204],{},"    ft.Text(\"Hello\"),\n",[42,206,207],{"class":44,"line":59},[42,208,209],{},"    ft.Button(\"Click me\")\n",[42,211,212],{"class":44,"line":76},[42,213,214],{},"])\n",[10,216,217],{},"Flet底层会把这段python对象序列化成上面类似的ui schema，通过ws发给flutter客户端，然后flutter客户端里的引擎去解析这个json，然后把页面画出来。这个过程里，python不仅控制了下发的model，还控制了view的层级。",[10,219,220],{},"你像vue\u002Fnuxt的生态中，已经有了SDUI的成熟方案，它们可以通过schema\u002Fjson驱动或者服务端组件等不同的路径将ui的控制权交给服务端，这里我举几个：",[10,222,223,228,229,232,233,236],{},[151,224,225],{},[39,226,227],{},"nuxt-sdui-layer","：专为 ",[151,230,231],{},"Nuxt 3+"," 设计的开箱即用模块。它通过 Firestore 文档驱动 UI，内置了 ",[39,234,235],{},"SDUIRenderer"," 等组件，将后端数据直接映射为 Vue 组件进行渲染，非常适合 Nuxt 技术栈。",[10,238,239,244,245,248],{},[151,240,241],{},[39,242,243],{},"json-render-vue","：一款强大的 ",[151,246,247],{},"Vue 3"," JSON 驱动渲染器。它提供了完整的 UI Spec 规范，支持双向数据绑定、插件化组件库、条件可见性以及动作处理，灵活性很高。",[10,250,251,256,257,260,261,157,264,266],{},[151,252,253],{},[39,254,255],{},"@sdui-kit\u002Fvue","：",[151,258,259],{},"框架无关","的 SDUI Kit 的 Vue 适配器。它允许你注册自有组件，并通过 ",[39,262,263],{},"SDUIProvider",[39,265,235],{}," 来渲染后端下发的、与框架无关的 JSON 节点。",[10,268,269,274,275,278,279,282],{},[151,270,271],{},[39,272,273],{},"@burgantech\u002Fpseudo-ui","：一个功能强大的 ",[151,276,277],{},"TypeScript"," SDUI 渲染引擎。它内置了 30+ Material Design 3 组件，并提供了表达式引擎、条件引擎和验证引擎，特别适合动态",[151,280,281],{},"表单","和复杂工作流。",[10,284,285],{},"这些项目我们后期可以自己去了解，这里就先不去谈论这个了，现在的主题是Flet。",[287,288,289],"h2",{"id":289},"关于移动端的实现",[291,292,293],"blockquote",{},[10,294,295,298],{},[151,296,297],{},"嵌入Python运行时 + Dart FFI"," 的混合策略",[10,300,301],{},"现在Flet已经实现了安卓端，所以我其实怀疑Flet是已经解决了aot并消除了sdui的，但是实际上不是这样。flet的sdui核心架构没有去掉，而是通过python运行时嵌入的技术，实现了从远程服务器的驱动到本地驱动。这套技术还是没有变化。",[10,303,304],{},"因此，现在还是使用的客户端-服务器的通信模式，python依然在负责业务逻辑和ui控制树。flutter客户端负责将控制器渲染成原生ui，这个模型在android上依然存在。当然aot部分是存在的，但是应用在了flutter部分，flutter的dart代码会被aot编译为arm\u002Fx64原生机器码，保证ui渲染和交互的性能。",[287,306,308],{"id":307},"分离部署集中管理","分离部署、集中管理",[291,310,311],{},[10,312,313],{},[151,314,315],{},"WebSocket服务器完全可以独立于客户端运行，并且一个服务端进程能够同时维护成百上千个独立的Python会话，为每个连接的客户端提供服务。",[10,317,318],{},"Flet的远程模式中，python应用逻辑运行在性能强大的云服务器上，而ws网关比如nginx、fastapi可以单独部署，甚至可以用cdn加速。",[10,320,321],{},"所有客户端的连接请求都由ws服务器接收，然后转发给后端的flet应用进程进行处理。flet是由严格的会话隔离机制来确保多个客户端互不干扰的，每一个客户端通过ws来连接到flet应用的时候，服务端都会给他创建一个session对象。",[10,323,324,325,328],{},"当需要管理大量客户端时，Flet推荐将应用托管在",[151,326,327],{},"生产级的ASGI服务器","（如Uvicorn、Hypercorn）上，而不是使用其内置的开发服务器。这能带来更好的并发性能和稳定性。",[10,330,331,332,335],{},"这个使用场景其实比较好想，在工厂车间或者实验室里，往往有多态工控机、手持PDA、大屏看板。这样可以让几个终端连接同一个服务端， 权限不同，工人连上来的 UI 树里可能只有“扫码”按钮；工程师连上来的 UI 树里包含“修改参数”按钮。 ",[151,333,334],{},"全局状态同步","，当设备发生紧急故障时，服务端可以瞬间修改所有在线终端的 UI 树——在所有人的屏幕上强制弹出一个红色的报警模态框。如果某个老旧平板性能极差，服务端可以为它下发一个“精简版 UI 树”（去掉复杂图表），只保留关键数据。",[10,337,338],{},"那么甚至连请求都可以在SDUI的服务器里执行了，这就是一个完美的BFF聚合层。假设你的上位机需要显示“设备温度”和“订单列表”，传统做法是前端发两个请求。",[10,340,341,342,345,346,349],{},"而在 SDUI 里，服务端收到页面刷新事件后，在 Python 里用 ",[39,343,344],{},"asyncio"," 并发请求这两个微服务，然后组合成",[151,347,348],{},"一棵完整的 UI 树","（包含数据和结构），一次性推给上位机。客户端减少了网络握手，加载速度极快。",[351,352,353],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":37,"searchDepth":52,"depth":52,"links":355},[356,357],{"id":289,"depth":52,"text":289},{"id":307,"depth":52,"text":308},"md",{"date":360,"tags":361},"2026-10-02",[362,192,363,364],"architecture","flet","flutter",true,"\u002Farchitecture\u002F服务器驱动ui的sdui范式讲解之flet",{"description":12},"architecture\u002F服务器驱动UI的SDUI范式讲解之Flet","NmXVFIHJB9G4cwObj04OPx87ogVzsU5DseFYdnlR0V0",1793064658106]