组件和页面拆分
生产环境中的 Applet 不应该变成一个巨大的 app.js。拆分方式可以参考 Flutter、
SwiftUI 和 Compose:简洁的入口、可复用组件、状态模型模块和主题辅助函数。
src/ app.js state.js theme.js components.js screens/ home.js settings.jssrc/app.js 只负责装配大的组件:
import "@app/material";import { AppState } from "./state.js";import { appTheme } from "./theme.js";import HomeScreen from "./screens/home.js";
export default function App() { const model = AppState();
return MaterialApp({ theme: appTheme(model.dark), home: HomeScreen(model), });}页面模块描述一个路由或一个主标签页:
import { SectionHeader } from "../components.js";
export default function HomeScreen(model) { return Scaffold({ appBar: AppBar({ title: Text("Home") }), body: ListView([ SectionHeader("Pinned"), ...model.items.map((item) => ItemTile(item, model)), ]), });}组件模块保持单一职责:
export function SectionHeader(title) { return Padding( Text(title).style({ theme: "titleMedium" }), { padding: { left: 16, right: 16, top: 20, bottom: 8 } } );}
export function ItemTile(item, model) { return ListTile({ leading: Icon(item.icon), title: Text(item.title), subtitle: Text(item.subtitle), selected: model.selectedId === item.id, onTap: () => model.select(item.id), });}数据先于 UI
Section titled “数据先于 UI”目录、菜单、配置一类内容保留为普通 JavaScript 对象。通过组件渲染它们,不要把大数组 混在页面主体里:
export const destinations = [ { id: "components", label: "Components", icon: Icons.widgets }, { id: "color", label: "Color", icon: Icons.palette }, { id: "typography", label: "Typography", icon: Icons.text_fields },];NavigationBar({ selectedIndex: model.index, onDestinationSelected: (index) => model.selectIndex(index), destinations: destinations.map((item) => NavigationDestination({ icon: Icon(item.icon), label: item.label, }) ),});依赖数据的页面都应该明确处理加载、空数据、错误和就绪状态:
export function ResultsScreen(model) { if (model.loading) return LoadingView(); if (model.error) return ErrorView(model.error, () => model.retry()); if (model.results.length === 0) return EmptyView();
return ResultsList(model.results);}这样声明式模型会更清楚:每次渲染都返回当前状态对应的 UI。
入口里导入 @app/material,即可获得全局 Flutter 风格组件名。功能模块中需要编辑器
提示或更清晰依赖时,可以使用命名导入:
import { Card, Icon, Icons, ListTile, Text } from "@app/material";两种方式都可用。建议使用 @app/* 模块名;@applet/* 作为兼容别名保留。