Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Silex 核心库文档

silex 是框架的核心库,它整合了 DOM 操作、响应式系统、路由和标准组件,为您提供一站式的 Web 开发体验。

快速开始

在您的 Cargo.toml 中引用 silex

[dependencies]
silex = { path = "../../silex" } # 或者使用 git/crates.io 版本

功能特性 (Feature Flags)

silex 提供以下功能开关,以优化编译时间和依赖体积:

FeatureDescriptionDefault
macros启用 css!, #[component], #[derive(Store)] 等宏支持。Yes
persistence启用统一持久化系统 (silex::persist)。No
json启用基于 Serde 的 JSON 编解码支持 (JsonCodec)。No
net启用网络通信支持 (HttpClient, WebSocket, SSE)。No

推荐在代码中导入 prelude:

#![allow(unused)]
fn main() {
use silex::prelude::*;
}

1. 路由系统 (Router)

Silex 提供了一个类型安全且易于使用的客户端路由。

基本用法

使用 <Router> 组件包裹您的应用,并定义路由规则:

#![allow(unused)]
fn main() {
fn App() -> impl View {
    Router()
        .base("/app") // 可选:设置基础路径
        .render(|| {
            // 这里通常放置布局组件(Layout)
            // 根据路由匹配显示不同内容
            let path = use_location_path();
            
            div((
                nav((
                    Link("/", "首页"),
                    Link("/about", "关于"),
                )),
                main((
                    Dynamic(move || {
                        match path.get().as_str() {
                            "/" => Home().into_any(),
                            "/about" => About().into_any(),
                            _ => NotFound().into_any(),
                        }
                    })
                ))
            ))
        })
}
}

使用枚举管理路由 (推荐)

对于大型应用,建议使用 Enum + #[derive(Route)] (需启用宏) 来管理路由:

#![allow(unused)]
fn main() {
#[derive(Route, Clone, PartialEq)]
enum MyRoutes {
    #[route("/")]
    Home,
    #[route("/users/:id")]
    User(i32),
    #[route("/*")]
    NotFound,
}

impl RouteView for MyRoutes {
    fn render(&self) -> AnyView {
        match self {
            MyRoutes::Home => Home().into_any(),
            MyRoutes::User(id) => UserPage(*id).into_any(),
            MyRoutes::NotFound => NotFound().into_any(),
        }
    }
}

// 在 Router 中使用
Router().match_route::<MyRoutes>()
}

导航

  • HTML: 使用 <Link> 组件代替 <a> 标签。
    #![allow(unused)]
    fn main() {
    Link(MyRoutes::Home, "Go Home")
    }
  • Code: 使用 use_navigate hook。
    #![allow(unused)]
    fn main() {
    let nav = use_navigate();
    nav.push("/new-path");
    }

查询参数与外部状态 (Query Parameters & Persistence)

Silex 提供了统一的持久化入口来处理 URL 查询参数、浏览器存储和双向绑定:

  • use_query_map():

    • 返回 Memo<HashMap<String, String>>
    • 使用 web_sys::UrlSearchParams 标准解析,自动处理 URI 编码。
    • 响应式:当 URL 变化时自动更新。
  • Persistent::builder(key):

    • 统一后端:.local().session().query()
    • 统一 codec:.string().parse::<T>().json::<T>()
    • 返回 Persistent<T>,可直接 get/set/update,也可直接用于常见 View / bind_value 场景。
    #![allow(unused)]
    fn main() {
    let search = Persistent::builder("q")
        .query()
        .string()
        .default(String::new())
        .build();
    
    input()
        .bind_value(search);
    }

2. 流程控制 (Flow Control)

Silex 提供了一组组件来处理常见的逻辑控制,这比手动编写 move || 闭包更具可读性且性能更好。

Show (条件渲染)

#![allow(unused)]
fn main() {
let (is_logged_in, set_log) = Signal::pair(false);

Show(is_logged_in)
    .children(UserDashboard())
    .fallback(LoginButton())
}

或者使用语法糖:

#![allow(unused)]
fn main() {
is_logged_in.when(UserDashboard())
}

Showchildrenfallback 都是渲染型参数,传入普通 View 即可。

Switch (多路分支)

类似于 match 语句,根据值选择渲染的内容。

#![allow(unused)]
fn main() {
let (tab, set_tab) = Signal::pair(0);

Switch(tab)
    .fallback(div("Fallback"))
    .case(0, TabA())
    .case(1, TabB())
}

Switch 会在构建阶段检查重复 case 值,并在分支未变化时避免重复重建。

Portal (传送门)

将组件渲染到当前 DOM 树之外的节点(默认是 document.body)。适用于模态框(Modals)、全局通知、浮动菜单等。

核心优势

  • Context 连通:即便 DOM 位于 body 下,依然能无缝访问定义处的响应式上下文(Signals, Context)。
  • 自动清理:当 Portal 组件销毁时,它会自动从目标节点中移除渲染的内容。
#![allow(unused)]
fn main() {
Portal(div!(
    h2("我是模态框"),
    button("关闭")
))
.mount_to(custom_node) // 可选,默认为 body
}

For (列表渲染)

高效渲染列表数据,支持 Keyed Diff 算法。

#![allow(unused)]
fn main() {
let (users, set_users) = Signal::pair(vec![
    User { id: 1, name: "Alice" },
    User { id: 2, name: "Bob" },
]);

For(
    users,           // 数据源 (Signal)
    |u| u.id,        // Key 提取函数 (必须唯一且稳定)
)
.children(|u, idx| div((idx.get(), ": ", u.name)))
.error(|err| div(format!("For 出错: {}", err)))
}

如果不传 .error(...),默认会调用 handle_error

Index (索引列表渲染)

当列表项没有唯一 ID,或者列表项是基础类型(如 Vec<String>),或者列表长度固定仅内容变化时,使用 IndexFor 更高效。它复用 DOM 节点,仅更新 Signal。

#![allow(unused)]
fn main() {
let (logs, set_logs) = Signal::pair(vec!["Log 1", "Log 2"]);

Index(logs).children(|item, index| {
    // item 是 Signal<T>,内容变化时直接更新文本节点
    div((index, ": ", item))
})
}

3. 错误处理 (Error Handling)

使用 <ErrorBoundary> 可以捕获子组件树中的 PanicSilexError。它能有效防止由于局部组件故障导致整个应用崩溃,并展示友好的备用 UI。

基本用法

#![allow(unused)]
fn main() {
ErrorBoundary(move || DangerousComponent())
    .fallback(|err| {
        div!(
            h3("糟糕,出错了"),
            p(format!("错误详情: {}", err)),
            button("重试").on_click(|_| {
                // 逻辑处理,例如刷新页面
                let _ = web_sys::window().unwrap().location().reload();
            })
        )
        .style("background: #fff1f0; border: 1px solid #ffa39e; padding: 16px; border-radius: 8px;")
    })
}

核心特性

  • 捕获同步 Panic: 自动使用 std::panic::catch_unwind 包装子组件的渲染过程。
  • 捕获逻辑错误: 捕获子组件通过 ErrorContext 向上冒泡的 SilexError(例如在事件处理器或异步 Resource 中抛出的错误)。
  • 状态隔离: 错误界限会隔离故障,父级组件和其他不相关的组件树分支将保持正常工作。
  • 异步兼容: 错误状态的更新是异步调度的,避免了在渲染阶段直接修改状态导致的潜在问题。

4. 异步加载 (Suspense)

配合 Resource 使用,优雅处理异步数据加载状态。

#![allow(unused)]
fn main() {
// 组件化 API
Suspense(move || {
    let data = Resource::new(source_signal, fetcher);
    div(rx!(data.get().unwrap_or_default()))
        .style("color: green")
})
.fallback(div("Loading..."))

// 卸载模式 (Unmount)
Suspense(move || {
    let data = Resource::new(source_signal, fetcher);
    div(rx!(data.get().unwrap_or_default()))
})
.mode(SuspenseMode::Unmount) // <--- 启用卸载模式
.fallback(div("Loading..."))
}

5. UI 与布局 (UI & Layout)

Silex 提供了一些基础的原子组件来迅速搭建响应式应用布局结构以及实现主题隔离机制:

布局组件 (Stack, Center, Grid)

内置实现了高度复用的布局原语,均已自动提供类型及属性信号响应绑定功能:

#![allow(unused)]
fn main() {
use silex::components::layout::*;

// 纵向 Flex,子元素以 10px 间隔
Stack(view_chain!(
    div("Child 1"),
    div("Child 2")
))
.gap(px(10))
.direction(FlexDirectionKeyword::Row) // 切换为横排

// 居中包围
Center(div("I am in the center"))

// 3 列网格网格
Grid(view_chain!(
    div("Cell 1"),
    div("Cell 2"),
    div("Cell 3"),
))
.columns(3)
.gap(px(8))
}

主题系统 (Theme System)

Silex 提供了一个能够与 CSS 变量无缝集成的强类型主题系统:

  • 强类型校验:定义主题后自动生成的常量(如 AppTheme::PRIMARY)自带属性类型,防止将颜色误传给尺寸。
  • 全局模式:使用 set_global_theme(signal) 为整个应用设置基础视觉方案。
  • 局部补丁:使用 theme_patch(patch_signal) 进行增量微调,利用 CSS 变量继承实现精准局部覆盖。
  • 零损耗:主题变量直接注入现有元素的属性中,不会引入额外的 DOM 包裹层。
#![allow(unused)]
fn main() {
// 1. 设置全局主题
set_global_theme(theme_signal);

// 2. 局部增量覆盖
// 仅修改 primary 变量,其余变量自动从环境继承
let patch = rx!(|| AppThemePatch::default().primary(hex("#ff69b4")));
div("局部变色卡片").apply(theme_patch(patch))

// 3. 在样式中使用主题变量 (具备 IDE 补全与类型检查)
sty().color(AppTheme::PRIMARY)
     .border_radius(AppTheme::RADIUS)
}

7. 网络请求 (Networking)

silex 提供了简洁且功能强大的 API 来处理 HTTP 请求、WebSocket 消息和 SSE 流。

HTTP 请求 (HttpClient)

使用流式接口构建请求,并集成响应式系统:

#![allow(unused)]
fn main() {
// 1. 获取 JSON 数据并转化为 Resource (自动触发加载态)
let user_id = Signal::pair(1);
let user_data = HttpClient::get("https://api.example.com/users/{id}")
    .path_param("id", user_id)
    .json::<User>()
    .as_resource(user_id);

// 2. 提交数据 (Mutation)
let login = HttpClient::post("/api/login")
    .json_body(login_info)
    .json::<Token>()
    .as_mutation();

// 3. 配置重试与缓存
let api = HttpClient::get("/api/config")
    .retry_policy(3, Duration::from_secs(1))
    .cache(CachePolicy::StaleWhileRevalidate)
    .json::<Config>();
}

WebSocket

提供状态和消息的完整响应式绑定:

#![allow(unused)]
fn main() {
let ws = WebSocket::connect("ws://localhost:8080/chat")
    .on_open(|| println!("Connected!"))
    .build();

// 获取实时消息信号 (自动 JSON 解码)
let messages = ws.message::<ChatMessage>();

// 发送消息
ws.send_json(&msg)?;
}

Server-Sent Events (SSE)

#![allow(unused)]
fn main() {
let stream = EventStream::builder("/api/notifications")
    .event("update") // 监听特定事件
    .build();

// 获取最后一条消息
let last_msg = stream.last_message::<Notify>();
}

8. 常用宏与工具 (Macros & Utilities)

Silex 提供了一系列宏来简化开发,这些宏都已包含在 prelude 中。

组件定义 (#[component])

#![allow(unused)]
fn main() {
#[component]
fn MyComp(name: String, #[prop(default)] age: i32) -> impl View {
    div(format!("Name: {}, Age: {}", name, age))
}
}

属性助手 (classes!)

  • classes!: div(()).class(classes!["btn", "active" => is_active])

详细文档请参阅 silex_macros 文档

Silex CSS:极致性能的类型安全样式库

silex_css 是 Silex 框架的核心组件之一,它为 Rust Web 开发提供了原生的类型安全 CSS 体系

在 Silex 中,CSS 不再是脆弱的字符串拼接,而是具有强类型保障、自动性能优化和零运行时损耗(Zero-runtime overhead)的现代化基础设施。

为什么选择 Silex CSS?

  • 编译期类型校验:通过 px(10), rem(1.2), hex("#fff") 等包装类型,彻底杜绝了单位遗漏或属性写错等低级错误。
  • 极致性能更新:动态样式优先转化为 CSS 变量,通过响应式信号实现原子化更新,避开复杂的 DOM 重新解析。
  • 零 DOM 损耗:采用最先进的 Adopted StyleSheets 技术,样式完全驻留在内存中,不再向 head 注入成堆的 <style> 标签。
  • 智能自动回收:内置 LRU 缓存与弱引用机制,自动清理不再使用的样式规则,保障长效运行下的内存安全。
  • 显式空值处理:所有属性类型内部均使用 Option 包装,支持 Default 生成“未设置”状态,避免了强制赋予默认数值导致的冲突。

1. 快速上手

在 Silex 中,你可以使用多种方式编写样式。最简单的方法是使用 css! 宏或纯 Rust API sty()

类型安全的属性值

Silex 要求显式指定单位,这不仅能获得 IDE 的自动补全,还能在编译阶段拦截错误。

#![allow(unused)]
fn main() {
use silex::css::prelude::*;

// 声明响应式变量
let width = Signal::pair(px(200));
let color = Signal::pair(hex("#4f46e5"));

// 使用 css! 宏 (现在支持代码块语法)
let base_cls = css! {
    width: $(width);
    background-color: $(color);
    padding: 1rem;
    &:hover { 
        filter: brightness(1.1);
        transform: scale(1.02);
    }
};

div("Hello Silex").class(base_cls)
}

原理说明:当 width 信号变化时,Silex 不会修改 .class 里的规则,而是仅调用一次 style.setProperty("--v-width", "200px")。这种变量级的更新几乎是浏览器能达到的最高效率。


2. 纯 Rust 样式构建器 (Style Builder)

如果你更喜欢纯粹的 Rust 语法,或者希望获得更极致的类型提示,可以使用 sty()Style::new() 的简写)。

#![allow(unused)]
fn main() {
use silex::css::prelude::*;

div("用 Builder 构建的样式")
    .style(
        sty().display(DisplayKeyword::Flex)
            .justify_content(JustifyContentKeyword::Center)
            .background_color(hex("#f3f4f6"))
            .on_hover(|s| s.background_color(hex("#e5e7eb")))
    )
}

全面对齐宏的功能:

  • IDE 友好:每一个方法都有明确的参数类型要求。
  • 复杂嵌套:使用 .nest("& > div", |s| ...) 支持任意选择器嵌套。
  • 响应式设计:使用 .media("@media (max-width: 600px)", |s| ...) 直接定义断点样式。
  • 零损耗更新:即使是深层嵌套中的信号,依然通过原子级的 CSS 变量进行更新。
#![allow(unused)]
fn main() {
sty().width(px(200))
    .on_hover(|s| s
        .background_color(hex("#f3f4f6"))
        .nest("& > .icon", |s| s.opacity(0.8)) // 复杂嵌套
    )
    .media("@media (max-width: 768px)", |s| s // 媒体查询
        .width(pct(100))
    )
}

3. 复合属性工厂

为了简化繁琐的组合属性(如 margin, border),silex_css 提供了工厂函数:

#![allow(unused)]
fn main() {
use silex::css::prelude::*;

let border_val = border(px(2), BorderStyleKeyword::Solid, hex("#3b82f6"));
let pad_val = padding::x_y(px(16), px(32)); // 水平 16px, 垂直 32px

styled! {
    pub MyBox<div> {
        border: $(border_val);
        padding: $(pad_val);
    }
}
}

4. 复杂属性 DSL (Complex Properties)

对于 transformgrid-template-areas 等语法极其复杂的属性,Silex 提供了专门的 DSL(领域专用语言)来确保输入的正确性。

变换 (Transform)

支持链式调用,无需手动拼接字符串,且会自动验证单位。

#![allow(unused)]
fn main() {
sty().transform(
    transform()
        .translate(px(10), px(20))
        .rotate(deg(45))
        .scale(1.2)
)
}

网格区域 (Grid Template Areas)

通过 Rust 数组/向量声明布局,自动处理引号包裹。

#![allow(unused)]
fn main() {
sty().grid_template_areas(
    grid_template_areas(["header header", "main sidebar"])
)
// 生成: grid-template-areas: "header header" "main sidebar";
}

字体变体 (Font Variation Settings)

为变体字体(Variable Fonts)提供结构化输入。

#![allow(unused)]
fn main() {
sty().font_variation_settings(
    font_variation_settings([("wght", 700.0), ("ital", 0.5)])
)
}

5. 计算属性与运算符重载

Silex CSS 允许你像编写原生 CSS 一样进行数值计算。通过重载算术运算符,你可以直接组合不同的单位。

算术运算

#![allow(unused)]
fn main() {
use silex::css::prelude::*;

let width = px(100) + rem(2); // 自动生成 (100px + 2rem)
let half = width / 2.0;       // 自动生成 ((100px + 2rem) / 2)
}

现代 CSS 函数

完全支持 calc(), min(), max()clamp(),且具有编译时类型检查。

#![allow(unused)]
fn main() {
use silex::css::prelude::*;

sty().width(clamp(px(200), pct(50), px(800)))
     .font_size(min(vec![rem(2), vw(5)]))
     .margin_top(calc(px(100) - rem(1)));
}

6. 主题系统 (Theme System)

传统的样式框架在实现主题切换时,通常依赖外层类名切换或 JS 环境注入。Silex 提供了一个高度原生的、基于 CSS 变量注入 的强类型主题系统,它不仅性能极高,而且支持极致的代码补全和类型校验。

6.1 定义主题

使用 theme! 宏定义主题结构。通过 #[theme(main)] 标记主主题,宏会自动生成 Theme 类型别名,供其他样式宏自动识别。

#![allow(unused)]
fn main() {
theme! {
    #[theme(main, prefix = "slx")]
    pub struct AppTheme {
        pub primary: Hex,     // 颜色类型
        pub radius: Px,       // 尺寸类型
        pub surface: Hex,
    }
}
}

提示:Silex 现已全面转向基于路径的变量引用语法。你可以通过 $AppTheme::PRIMARY 等语法安全地引用由 theme! 生成的常量。

6.2 强类型变量引用 (推荐)

宏生成的常量(如 AppTheme::PRIMARY)具有 CssVar<Hex> 类型,并在编译期继承 Hex 的校验规则。

#![allow(unused)]
fn main() {
// ✅ 合法:primary 是颜色,可以传给 color()
sty().color(AppTheme::PRIMARY)

// ❌ 编译报错:无法将颜色传给 width()
// 错误信息:类型 `CssVar<Hex>` 无法作为有效的 CSS `Width` 属性值使用
sty().width(AppTheme::PRIMARY) 

// ✅ 合法:radius 是尺寸,支持算术运算
sty().border_radius(AppTheme::RADIUS + px(4))
}

6.3 应用主题

Silex 支持全局挂载和局部补丁,所有操作均通过 .apply() 注入,不产生额外的 DOM 包装层。

#![allow(unused)]
fn main() {
// 1. 全局主题 (应用于 :root)
// 支持信号、常量或 rx! 闭包
set_global_theme(rx!(move || {
    if is_dark.get() { default_dark_theme() } else { default_light_theme() }
}));

// 2. 局部补丁 (增量覆盖)
// 仅修改 primary 变量,其余变量自动从环境继承 (CSS Inheritance)
let patch = rx!(|| AppThemePatch::default().primary(hex("#ff69b4")));
div("粉色主题区域").apply(theme_patch(patch))
}

6.4 获取主题状态

如果你需要在 Rust 逻辑中直接访问当前的变量数值(而非仅仅引用变量名):

#![allow(unused)]
fn main() {
let theme = use_theme::<AppTheme>();
let is_dark = theme.map(|t| t.surface == "#111827");
}

7. 核心引擎与架构

silex_css 的高性能离不开其底层的中心化注册机制

  1. 静态提升:所有纯静态的 CSS 规则会被自动提取,合并到一个全局唯一的 CSSStyleSheet 中,避免重复解析。
  2. 异步同步 (Async Sync):样式注入操作通过微任务队列进行批处理,确保即使在一帧内创建大量组件,也只触发一次浏览器的样式重计算。
  3. 内存在管理:不使用 <style> 标签,意味着样式表对 DOM 树不可见且无法直接通过字符串检索,减少了大型应用中 DOM 树的压力。

小结

silex_css 的设计哲学是将 CSS 的灵活性与 Rust 的安全性深度融合。无论你是追求极致开发体验(使用 css!),还是极致性能提示(使用 sty()),它都能在保障类型安全的同时,为你提供行业一流的渲染性能。

建议下一步阅读:silex_macros 宏指南深入组件样式化

Silex Reactivity 引擎

silex_reactivity 是 Silex 框架的底层响应式引擎。它实现了一个类型擦除 (Type-Erased)细粒度 (Fine-Grained) 的响应式图谱。

设计理念

该 crate 采用了引擎与接口分离的设计模式:

  • Runtime (运行时):负责管理节点图谱、数据存储 (Arena / SparseSecondaryMap)、副作用调度和内存管理。
  • Algorithm (算法):核心图算法(如 BFS 状态传播、迭代式 DFS 求值)被解耦到 algorithm.rs 模块,并通过 ReactiveGraph trait 与运行时交互。
  • Type Erasure (类型擦除):所有的信号值都以 AnyValue 的形式存储。这是一种支持小对象优化 (SOO) 的异构容器,使得运行时可以统一管理不同类型的信号,且避免了小数据的堆分配。
  • Zero-Allocation (零分配):利用 WorkSpace 对象池复用 VecVecDeque,在图遍历和更新传播过程中实现摊销零分配。
  • Arena 存储:使用定制的 ArenaSparseSecondaryMap 存储节点数据,提供稳定的 NodeId 引用和高效的内存访问(通过分块和代际索引)。

核心架构

1. Runtime (运行时)

Runtime 是一个线程局部 (Thread-Local) 的单例,包含了整个响应式系统的状态:

#![allow(unused)]
fn main() {
pub struct Runtime {
    pub(crate) graph: Arena<Node>,
    pub(crate) node_aux: SparseSecondaryMap<NodeAux, 32>, // 冷数据存储
    pub(crate) signals: SparseSecondaryMap<SignalData, 64>, // 信号数据
    pub(crate) effects: SparseSecondaryMap<EffectData, 64>, // 副作用数据
    pub(crate) states: SparseSecondaryMap<NodeState, 64>,   // 节点状态 (Clean/Check/Dirty)
    
    // 任务队列与工作区
    pub(crate) observer_queue: RefCell<VecDeque<NodeId>>,
    pub(crate) workspace: RefCell<WorkSpace>,
    // ...
}
}

2. Arena & SparseSecondaryMap (内存管理)

silex_reactivity 不再依赖外部的 ECS 或 Arena 库,而是实现了定制的内存分配策略:

  • Arena: 采用分块 (Chunk) 存储和代际索引 (Generational Index)。它使用 UnsafeCell 提供了内部可变性,允许在不违反 Rust 借用规则的前提下高效地构建自引用的响应式图谱。
  • SparseSecondaryMap: 配合 Arena 使用的辅助存储,用于映射 NodeId 到特定的组件数据(如 SignalData, EffectData, NodeAux)。支持可配置的块大小 (const N: usize) 以平衡内存占用和缓存局部性。

3. NodeId 与 Node (拓扑结构)

  • NodeId: arena::Index 的别名。包含 index (u32) 和 generation (u32)。
  • Node: 仅存储最核心的图谱信息(如 parent),以保持轻量级,利于 CPU 缓存。
  • NodeAux: 存储辅助性或“冷”数据,如子节点列表 (children)、清理回调 (cleanups) 和上下文 (context)。这些数据不常被访问,因此从 Node 中分离出来。
  • NodeState: 节点的响应式状态,用于惰性求值优化。
    • Clean: 节点是最新的。
    • Check: 依赖可能已变动,需要检查。
    • Dirty: 节点已过时,必须重新计算。

4. SignalData (信号数据)

信号是响应式图谱中的数据源。

  • Value: 使用 AnyValue 存储。如果数据较小(如 i32, bool, f64),直接内联存储;否则才使用堆分配 (Box)。
  • Subscribers: 订阅了该信号变更的副作用节点列表。内部使用了优化过的枚举结构 NodeList (Empty/Single/Many) 来减少常见单订阅场景的内存占用。
  • Version & Tracking: 维护 versionlast_tracked_by,用于优化依赖收集,防止重复注册。

5. EffectData (副作用数据)

副作用是响应式图谱中的观察者。

  • Computation: 实际执行的闭包逻辑。为了性能,以 Option<Box<dyn Fn()>> 形式存储(执行时取出所有权,避免引用计数开销)。
  • Dependencies: 该副作用依赖的信号列表。使用 DependencyList (List<(NodeId, u32)>) 存储依赖 ID 及其版本号,用于变更检测。

6. Memo (派生数据)

派生数据(Memo)是信号和副作用的混合体,用于缓存计算结果。

  • Composition (组合式): 这里没有独立的 DerivedData 结构体。一个 Memo 节点实际上是同时拥有 SignalData(作为数据源被下游消费)和 EffectData(作为观察者依赖上游)的 Node。
  • Lazy Evaluation (惰性求值): 利用 algorithm::evaluate 实现迭代式 DFS 求值。当 Memo 被访问时,根据 NodeState 决定是否需要重新计算。如果状态为 Check,会先检查所有依赖的版本号 (dependency_versions) 是否变更,若无变更则直接转为 Clean,避免不必要的计算。

关键机制

自动依赖追踪

当一个副作用执行时,Runtime 会将其设为 current_owner。在此期间读取的任何信号都会自动将该副作用注册为订阅者。

#![allow(unused)]
fn main() {
// 伪代码流程
effect(|| {
    // try_get_signal 内部调用 track_dependency
    let value = try_get_signal(id).unwrap(); 
    println!("Value: {}", value);
});
}

状态传播与批量更新

  • Propagation (BFS): 当信号更新时,algorithm::propagate 使用广度优先搜索 (BFS) 遍历所有下游节点。
    • 将直接订阅者标记为 Dirty
    • 将更下游的节点标记为 Check
    • 将纯副作用节点 (EffectData only) 加入 observer_queue
  • Queue Execution: 批量更新阶段(run_queue),运行时从队列中取出节点并执行。对于 Memo 节点,此时仅标记状态;对于 Effect 节点,则执行其计算闭包。
  • Zero-Allocation: 这一过程使用的 VecVecDeque 均从 WorkSpace 对象池中借用,用完即还。

内存管理与清理

  • Dispose: 调用 dispose(id) 会递归清理该节点及其所有子节点。
  • Cleanup: 副作用重新执行前,会自动清理旧的依赖关系(反注册订阅)和注册的清理回调 (on_cleanup)。

Silex Core:响应式编程指南

silex_core 是 Silex 框架的心脏。它提供了一套高效、简洁且类型安全的“响应式原语”,让你能够以声明式的方式管理应用状态。Silex 的核心理念是极致性能零成本抽象,确保你的应用在保持代码整洁的同时,拥有媲美原生 Rust 的执行效率。

本指南将带你从最基础的信号开始,逐步掌握 Silex 响应式系统的精髓。


1. 响应式的基础:信号 (Signals)

在 Silex 中,信号 (Signal) 是存储状态的基本单元。你可以把它想象成一个“活”的变量:当你修改它的值时,所有依赖于该值的地方都会自动收到通知并更新。

创建与基础操作

最常用的信号是 RwSignal(可读写信号)。

#![allow(unused)]
fn main() {
use silex::prelude::*; // 推荐使用 prelude 导入核心工具

// 1. 创建一个操作对:只读信号与写入器
let (count, set_count) = Signal::pair(0); 

// 或者通过 RwSignal 直接创建(包含读写能力,最为常用)
let count = RwSignal::new(0);

// 2. 读取值 (响应式)
println!("当前值: {}", count.get());

// 3. 修改值
count.set(10);

// 4. 就地更新 (推荐用于结构体,避免频繁克隆)
count.update(|n| *n += 1);
}

Tip

信号是 Copy:在 Silex 中,所有的信号句柄(如 RwSignal, ReadSignal, Signal)都实现了 Copy 特征。这意味着你可以像传递整数一样在组件间自由传递它们,无需使用 .clone()


2. 自动化的力量:派生计算

响应式最强大的地方在于,你可以基于已有信号创建“公式”。当源数据变化时,计算结果会自动更新。

使用运算符 (Operator Overloading)

Silex 为信号重载了算术和逻辑运算符,让代码读起来就像普通的 Rust 代码。

#![allow(unused)]
fn main() {
let (a, _) = Signal::pair(10);
let (b, _) = Signal::pair(20);

// sum 是一个派生信号,当 a 或 b 变化时,它会自动重算
let sum = a + b; 
let is_positive = sum.greater_than(0); // 也可以使用流畅化 API
}

使用 rx! 宏:智能与性能的平衡

对于更复杂的逻辑,推荐使用 rx! 宏。它能自动追踪闭包内使用的所有信号,并提供极致性能。

$变量 语法:零拷贝访问

rx! 中访问信号时,使用 $ 前缀可以直接获取内部引用的视图,完全避免数据克隆。

#![allow(unused)]
fn main() {
let first_name = RwSignal::new("Alice".to_string());
let last_name = RwSignal::new("Smith".to_string());

// $first_name 实际上是 &String 类型。
// 宏会自动展开为嵌套引用访问,实现真正的零拷贝。
let full_name = rx!(format!("{} {}", $first_name, $last_name));
}

极致优化:@fn 静态分发

如果你确信表达式中不捕获局部外部变量(仅使用 $信号 和全局/常量),可以使用 @fn 前缀,这能显著减少编译后的代码体积。

#![allow(unused)]
fn main() {
// 零堆内存分配模式:将计算转化为极其轻量级的静态函数调用
let display = rx!(@fn if *$count > 0 { "Visible" } else { "Hidden" });
}

3. 极致性能:读取路径的选择

为了性能最大化,Silex 区分了不同的读取方式。

get() vs read() vs with()

  • .get(): 【强力克隆】 获取值的一个完整备份。仅当类型实现了 Clone 时可用。
  • .read(): 【引用守卫】 返回一个守卫对象,通过它可以直接读取内部数据而无需克隆,适用于大型结构体。
  • .with(|v| ...): 【闭包借用】 将数据引用传递给闭包。这是最推荐的零拷贝读取方式。

读取方式对比

方法性能Clone 要求返回类型适用场景
get()一般 (涉及拷贝)必须实现T简单数值 (如 i32, bool)
read()优秀 (零拷贝)无要求RxGuard需要在作用域内手动处理引用
with()极致 (零拷贝)无要求闭包返回值 U只需要读取结构体的某个部分

4. 细粒度更新:Memo 与 Slice

在大规模应用中,避免不必要的 UI 刷新是性能的关键。

Memo (记忆化计算)

只有当计算结果真正发生变化(基于 PartialEq)时,Memo 才会通知下游。

#![allow(unused)]
fn main() {
let count = RwSignal::new(0);
// 即使 count 从 1 变到 2,is_even 依然是 false,
// 依赖 is_even 的组件不会发生无效重绘。
let is_even = count.map(|n| n % 2 == 0).memo();
}

SignalSlice (响应式投影)

当你有一个庞大的全局状态,但某个组件只关心其中的一个子字段时,请使用 .slice()

#![allow(unused)]
fn main() {
struct AppState { user_name: String, theme: String }
let state = RwSignal::new(AppState { ... });

// 创建一个只关注“用户名”的切片。
// 修改 theme 时,依赖 name_slice 的组件不会刷新!
let name_slice = state.slice(|s| &s.user_name);
}

5. 异步管理:Resource 与 Mutation

Silex 将异步操作(网络请求)深度集成到了响应式系统中。

Resource:拉取型异步

适用于加载数据(如:拉取用户信息)。它自带 Loading/Error/Ready 等状态。

#![allow(unused)]
fn main() {
let user_id = RwSignal::new(1);
// 当 user_id 变化时,fetch 会自动重新执行
let user_data = Resource::new(user_id, |id| async move {
    api::fetch_user(id).await
});

Show(move || user_data.loading())
    .children(div("Loading..."))
    .fallback(div(format!("User: {:?}", user_data.get())))
}

Mutation:触发型异步

适用于提交表单、点击按钮等主动动作。

#![allow(unused)]
fn main() {
let login_action = Mutation::new(|(user, pass)| async move {
    api::login(user, pass).await
});

// 触发异步动作
login_action.mutate(("admin".into(), "password".into()));
}

6. 副作用:Effect

当你需要在信号变化时执行一些非 UI 的操作(如记录日志或手动操作原生 DOM)时,使用 Effect

#![allow(unused)]
fn main() {
let count = RwSignal::new(0);

// 创建一个副作用,它会自动追踪闭包内使用的信号
Effect::new(move |_| {
    println!("计数器变了: {}", count.get());
});
}

总结:Silex Core 的优势

  1. 极简 API:通过宏和运算符重载,让响应式代码读起来像原生 Rust。
  2. 极致小巧:内部采用“响应式归一化”技术,极大减少了泛型单态化导致的编译体积膨胀。
  3. 极致流畅:基于 RxGuard 的自适应读取系统,确保了应用在处理大数据时依然保持零拷贝的高性能。

掌握了 silex_core 的这些原语,你就已经拥有了构建高性能复杂前端应用的核心武器。下一步,可以查阅 Silex DOM 了解如何将逻辑渲染到浏览器中。

Silex DOM

silex_dom 是框架的渲染层核心。它不仅仅是一个简单的 DOM 包装器,而是深度集成了 silex_core 的响应式系统,实现了细粒度更新 (Fine-Grained DOM Updates)

核心概念

1. Element (元素)

所有的 DOM 节点在 Rust 中被表示为 Element 或强类型的 TypedElement<T>。它们是对 web_sys::Element 的轻量级包装。

#![allow(unused)]
fn main() {
let div = Element::new("div");
}

2. View (视图)

View 是一个 Trait,定义了如何将内容挂载渲染到屏幕上。 Silex 的视图系统非常灵活,支持多种类型直接作为视图:

  • 基本类型: 数字、字符串、布尔值直接渲染为文本节点。

  • 信号 (Signals): ReadSignal<T>, Signal<T>, Memo<T> 等响应式类型。它们会创建一个响应式的文本节点,当信号更新时仅更新该文本内容。

  • 计算闭包:

    • rx!(...) (推荐): 统一的响应式入口,将表达式转化为响应式视图。
    • move || { ... }: 传统的闭包形式,同样被视为动态视图。 Silex 会自动建立副作用 (Effect),并在数据变化时通过双锚点 (Double-Anchor) 机制智能清理并更新 DOM。
  • 集合: Vec<V>, Slice [V], 元组 (A, B) 都会按顺序渲染其内容。

3. Attributes (属性)

Silex 提供了一套统一且强大的属性设置 API。所有设置属性的方法(.attr(), .prop(), .class(), .style(), .apply() 等)都支持泛型 V: IntoStorable

重要规则

  • 静态值:可以直接传入 &str, String, bool 等。
  • 动态值必须使用 rx!(...) 宏包裹闭包。直接传入 move || ... 无法通过编译。

静态设置

#![allow(unused)]
fn main() {
div.id("app")
   .class("container text-center")
   .style("color: red;")
}

响应式设置

任何接受属性值的地方,都可以传入一个 rx! 包装的计算单元。

#![allow(unused)]
fn main() {
let (count, set_count) = Signal::pair(0);

// class 会随着 count 的奇偶性自动切换
div.class(rx!(if count.get() % 2 == 0 { "even" } else { "odd" }))

// 禁用状态根据信号自动切换
button().disabled(rx!(count.get() > 10))
}

通用应用 (Apply)

如果需要应用一段通用的逻辑、主题变量或 Mixins,可以使用 .apply() 方法:

#![allow(unused)]
fn main() {
// rx!(|el| ...) 创建一个 RxEffect,允许直接操作元素
element.apply(rx!(|el: &web_sys::Element| {
    el.set_attribute("data-custom", "value").unwrap();
}))
}

4. Attribute Forwarding (属性透传)

Silex 支持多根节点组件(view chain),通常通过 view_chain! 宏实现。

当你在一个返回 view chain 的组件上设置属性(如 .class("foo"))时,Silex 采用首个匹配 (First-Match) 策略:

  • 属性会被向下传递给容器的所有子节点。
  • 第一个能够实际消费属性的真实 DOM 节点(Element)会应用这些属性并将该应用操作“取走”。
  • 后续的节点将不再接收到该属性。

这确保了在组件外部设置的 classid 能够符合直觉地应用到组件的“主”元素上。

事件处理

1. 强类型事件 (推荐)

使用 .on() 方法配合 silex_dom::event 模块。这能提供完美的类型推断。

#![allow(unused)]
fn main() {
use silex_dom::event;

button.on(event::click, |e| {
    // e 自动推断为 web_sys::MouseEvent
    log!("Clicked at: {}, {}", e.client_x(), e.client_y());
})
}

2. 快捷方法

对于常用事件,可以直接使用快捷方法:

#![allow(unused)]
fn main() {
button.on_click(|e| { ... })
      .on_input(|value| { ... }) // input 事件会自动提取 value 字符串
}

3. 双向绑定

#![allow(unused)]
fn main() {
let text = rw_signal("".to_string());
input().bind_value(text)
}

直接 DOM 访问 (NodeRef)

有时你必须访问底层的 HTML 元素(例如调用 .focus())。

#![allow(unused)]
fn main() {
use web_sys::HtmlInputElement;

// 1. 创建引用 (NodeRef 实现了 Copy)
let input_ref = NodeRef::<HtmlInputElement>::new();

div![
    input()
        .node_ref(input_ref) // 2. 绑定引用
        .placeholder("Wait for focus..."),
        
    button("Focus").on_click(move |_| {
        // 3. 安全获取
        if let Some(el) = input_ref.get() {
            let _ = el.focus();
        }
    })
]
}

实用工具 (Helpers)

silex_dom::helpers 模块提供了一系列常用的辅助函数:

  • Window/Document: window(), document() (线程局部缓存)。
  • 定时器 (Hooks): use_interval(duration, cb), use_timeout(duration, cb)。它们会自动集成 on_cleanup,在组件卸载时自动销毁,避免内存泄漏。
  • 事件辅助: event_target_value(&event), window_event_listener(event::resize, |e| ...)
  • 调度: debounce (防抖), request_animation_frame
#![allow(unused)]
fn main() {
// 每秒执行一次,组件卸载时自动停止
use_interval(Duration::from_secs(1), || {
    log!("Tick!");
});
}

Silex HTML

silex_html 提供了构建用户界面的 DSL (Domain Specific Language)。它是一组简单的工厂函数,用于创建强类型的 HTML 元素。

基础用法

不再需要手写 Element::new("div"),而是直接使用对应的函数:

#![allow(unused)]
fn main() {
use silex_html::{div, p, button};

let view = div((
    h1("Hello Silex"),
    p("This is a fine-grained reactive framework."),
    button("Click me")
));
}

组合与嵌套

由于 Rust 不支持变长参数函数,多个子元素需要包裹在元组中:

#![allow(unused)]
fn main() {
ul((
    li("Item 1"),
    li("Item 2"),
    li("Item 3"),
))
}

为了减少括号噪声,我们提供了一组同名的宏(推荐):

#![allow(unused)]
fn main() {
use silex_html::{ul, li};

ul!(
    li!("Item 1"),
    li!("Item 2")
)
}

类型安全属性

silex_html 产生的元素是强类型的(如 TypedElement<Input>)。这让 IDE 可以提供更好的补全,并且在编译时检查属性的合法性。

例如,只有 input 才有 type 属性,只有 a 才有 href 属性:

#![allow(unused)]
fn main() {
// ✅ 合法:Input 实现了 FormAttributes
input().type_("text").value("Silex");
// ✅ 合法:Anchor 实现了 AnchorAttributes
a("Link").href("https://github.com");

// ❌ 编译错误:div 没有实现 AnchorAttributes,因此没有 href 方法
// div("Content").href(...) 
}

这些特定属性被组织在不同的 Trait 中(如 FormAttributes, MediaAttributes, TableAttributes 等),只有对应的标签才实现了这些 Trait。全局属性(如 id, class, style)则对所有元素可用。

支持的标签

目前覆盖了所有标准的 HTML5 和 SVG 标签(基于 MDN 数据自动生成):

  • 结构: div, section, header, footer
  • 文本: p, span, h1-h6, strong, em
  • 列表: ul, ol, li.
  • 表单: form, input, button, select, option
  • 媒体: img, video, audio
  • SVG: 完整的 SVG 标签支持 (svg, path, circle, filter, feGaussian_blur…)。

所有标签都提供强类型的 TypedElement<T> 返回值,确保类型安全。

Silex 宏工具箱

silex_macros 包含了一系列过程宏,旨在减少样板代码,提升开发效率。

1. 定义组件 (#[component])

使用 #[component] 宏可以将普通函数转换为功能强大的组件构造器。

#![allow(unused)]
fn main() {
#[component]
fn Button(
    // 必填参数
    label: String,
    // 可选参数,默认值为类型的 Default
    #[prop(default)] color: String, // 默认为 ""
    // 可选参数,指定默认值
    #[prop(default = 1.0)] opacity: f64,
    // 自动调用 .into(),接受 &str 等
    #[prop(into)] on_click: Option<Callback<()>>,
) -> impl View {
    button(())
        .style(format!("opacity: {}", opacity))
        .text(label)
}

```rust
// 使用
// 由于 label 不是 children 参数,所以依然使用无参构造 + 链式调用
Button()
    .label("Click me") // 必须
    .opacity(0.8)      // 可选

// 如果第一个参数是 children: Children,则必须使用
// Parent(div("child"))
// 代替
// Parent().children(div("child"))
}

属性透传 (Attribute Forwarding)

生成的组件结构体实现了 AttributeBuilder Trait,这意味着你可以像操作普通 DOM 元素一样操作组件!

所有标准的 DOM 方法(如 .class(), .id(), .style(), .on_click())都可以直接链式调用:

#![allow(unused)]
fn main() {
Button()
    .label("Submit")
    .class("my-btn")       // 透传给 Button 内部的根元素
    .on_click(|_| { ... }) // 透传点击事件
}

多根节点 (Fragments) 支持: 如果组件返回多个根节点,属性会采用首个匹配策略:属性会被转发给第一个能消费属性的子节点(通常是第一个 DOM 元素),后续节点不受影响。

泛型与生命周期支持

#[component] 宏原生支持复杂的泛型和生命周期参数。这意味着你可以定义接受多态类型或带有特定生命周期的引用的组件:

#![allow(unused)]
fn main() {
#[component]
pub fn GenericMessage<'a, T: std::fmt::Display + Clone + 'static>(
    value: T,
    title: &'a str,
) -> impl View {
    div![
        h4(title.to_string()),
        p(format!("Value: {}", value)),
    ]
}

// 使用方式:
GenericMessage()
    .value(42)  // 推导为 i32
    .title("Number") // &'static str
}

在底层生成组件的 Builder 时,宏会自动处理相关的生命周期和泛型类型,并通过注入 PhantomData 来确保编译器正确追踪未使用(unused)但在宏块签名前声明了的参数。

2. 编写 CSS (css!)

使用 css! 宏可以在 Rust 代码中直接编写 CSS,并享受自动哈希(Scoped CSS)和压缩功能。

#![allow(unused)]
fn main() {
let (color, _) = Signal::pair("white".to_string());
let scale = Signal::pair(1.0).0;

let btn_class = css! {
    background-color: blue;
    color: $(color); /* 支持动态 Signal 插值 */
    transform: scale($(scale)); /* 自动处理任何实现了 IntoSignal 的类型 */
    padding: 10px;

    &:hover {
        background-color: darkblue;
    }
};

button(()).class(btn_class).text("Styled Button")
}

宏会返回一个唯一的类名(如 slx-1a2b3c),并将样式自动注入到页面 <head> 中。

高级类型校验 (Compile-time Type Safety): css!styled! 宏原生支持编译期类型安全。它们会自动感知插值所处的 CSS 属性名(如 width),并限制传入信号或变量的值类型。配合 silex::css::types::props 和如 px(100), pct(50) 这样的包装类,能够完美防范因忘记写单位引发的 CSS 无效问题:

#![allow(unused)]
fn main() {
use silex::css::types::{px, pct};
use silex::css::types::{border, BorderStyleKeyword, UnsafeCss, hex};

let w = Signal::pair(px(100)); // Px 类型被限定允许给 Width
let bd = Signal::pair(border(px(1), BorderStyleKeyword::Solid, hex("#ccc"))); // 专属工厂函数保障多位组合安全
let custom_calc = Signal::pair(UnsafeCss::new("calc(100% - 20px)")); // 若需超出约束边界请显式包装

let cls = css! {
    width: $(w); /* ✅ 合规 */
    height: $(pct(50.0)); /* ✅ 合规 */
    border: $(bd); /* ✅ 单值化强类型复合体合规 */
    margin: $(custom_calc); /* ⚠️ 显式越权非安全逃逸 */
    /* color: $(123.45); ❌ 编译报错:the trait `ValidFor<Color>` is not implemented for `f64` */
    /* z-index: $(px(99)); ❌ 编译报错:拦住企图把像素单位送给 ZIndex 的不合规行为 */
    /* padding: $("10px 20px"); ❌ 编译报错:阻绝散乱的字符串拼接(除非用 UnsafeCss 或是 padding::x_y 构建器)*/
};
}

底层解析重构 (AST-driven Compiler)css! 的内部机制基于强大的强类型解析引擎。首先由专用语法解析树(ast.rs)利用 syn 将输入 Token 流递归解析为 CssDeclarationCssNestedCssAtRule(支持 @media 等)语法单元。其次交由 CssCompiler 进行语义提取:

  • 静态压缩:通过 lightningcss 进行极致压缩和语法验证。
  • Token 间隙优化:编译器内置了智能间隙提取逻辑,确保诸如 font-family 或自定义值中的标识符与字面量之间保留正确的空格。
  • 自动类型映射:宏会自动将 kebab-case 的属性名(如 background-color)映射到运行时的 PascalCase 类型标签(如 props::BackgroundColor),实现无感知识库同步的编译期验证。

3. 样式组件 (styled!)

使用 styled! 宏可以带来类似 styled-components 的极致开发体验。它允许直接定义带作用域样式的组件,免去手写类名绑定,并且原生支持变体 (Variants) 和 局部动态规则 (Dynamic Rules)

#![allow(unused)]
fn main() {
styled! {
    pub StyledButton<button>(
        children: Children,
        #[prop(into)] color: Signal<String>,
        #[prop(into)] hover_color: Signal<String>,
        #[prop(into)] size: Signal<String>,
        #[prop(into)] pseudo_state: Signal<String>,
    ) {
        background-color: rgb(98, 0, 234);
        color: $(color); /* 基础值插值 */
        padding: 8px 16px;
        border-radius: 4px;
        border: none;
        cursor: pointer;
        transition: transform 0.1s, color 0.2s, background-color 0.2s;

        /* 动态规则插值:连选择器和部分块属性也能被 Signal 控制!*/
        &:$(pseudo_state) {
            background-color: $(hover_color);
            transform: scale(1.05);
        }

        // 静态变体 (Variants) 支持,通过纯类名直接切换响应无需 CSS 变量分配。
        variants: {
            size: {
                small: { padding: 4px 8px; font-size: 12px; }
                medium: { padding: 8px 16px; font-size: 14px; }
                large: { padding: 12px 24px; font-size: 18px; }
            }
        }
    }
}

// 在任意组件中透明且类型安全地使用:
// 由于 children 是 StyledButton 的第一个参数,它可以直接传入构造函数
StyledButton("Click me!")
    .color(my_color)
    .hover_color("#ff4081")
    .pseudo_state("active") // 可以按需改变触发条件!
    .size("large")
    .class("additional-external-classes") // 完全享受透传能力
    .on(event::click, move |_| console_log("Clicked!"))
}

核心优势

  1. 脱糖直接兼容 #[component]:生成的组件会自动返回基础节点构建并且注入所需属性重载和 _pending_attrs,完美支持外部 .class(), .id(), .on_click() 等链式方法调用重写。
  2. 动态规则与纯享原生能力:允许使用 &:$(pseudo) 的超强局部动态注入技术,这意味着我们可以安全地将 Signal 应用于伪类、伪元素乃至媒体查询触发值的热更新上!
  3. 纯静态性能级变体 Variants:对于非连续动画类的多属性集合变化(如主/从色彩模式、按键大中小模式),使用纯 CSS 类生成的 Variant 来规避运行时频繁覆盖及下发样式的系统开销。

4. 类型安全路由 (#[derive(Route)])

通过宏自动从 Enum 生成基于 Radix Tree 的高性能路由匹配和渲染逻辑。

#![allow(unused)]
fn main() {
#[derive(Route, Clone, PartialEq)]
enum AppRoute {
    // 静态路径
    #[route("/", view = Home)]
    Home,

    // 带参数路径 (:id 会映射到字段 id)
    #[route("/user/:id", view = UserProfile)]
    User { id: String },

    // 嵌套路由
    #[route("/admin")]
    Admin(
        #[nested] AdminRoute // AdminRoute 也是一个实现了 Routable 的 Enum
    ),

    // 404 捕获
    #[route("/*", view = NotFound)]
    NotFound,
}
}

路由守卫 (Route Guards)

你可以为路由添加 guard 参数来拦截或包装路由渲染。Guard 本质上是一个接收 children 的组件(Middleware)。

#![allow(unused)]
fn main() {
#[derive(Route, Clone, PartialEq)]
enum AppRoute {
    #[route("/dashboard", view = Dashboard, guard = AuthGuard)]
    Dashboard,

    // 支持多个 Guard,执行顺序由外向内: LogGuard -> AuthGuard -> Mount
    #[route("/admin", view = AdminPanel, guard = [LogGuard, AuthGuard])]
    Admin,
}
}

Guard 组件示例:

#![allow(unused)]
fn main() {
#[component]
pub fn AuthGuard(children: Children) -> impl View {
    // 假设我们有一个全局用户状态
    let user_name = use_context::<ReadSignal<String>>()
        .unwrap_or(Signal::pair("Guest".to_string()).0);
    
    move || {
         if user_name.get() != "Guest" {
             // 验证通过,渲染子视图
             children.clone()
         } else {
             // 验证失败,显示提示或重定向
             div![
                 h3("🔒 Restricted Access"),
                 p("Please login to view this content."),
             ].style("color: red; border: 1px solid red; padding: 10px;")
             .into_any()
         }
    }
}
}

5. 全局状态 Store (#[derive(Store)])

快速创建深层响应式的数据结构,并自动生成 Context 访问钩子。

#![allow(unused)]
fn main() {
#[derive(Clone, Default)]
struct UserConfig {
    theme: String,
    notifications: bool,
}

#[derive(Store, Clone, Copy)]
#[store(name = "use_config", err_msg = "Config not found")]
struct GlobalStore {
    pub config: UserConfig, // 注意:derive(Store) 目前只展开一层 Struct
                            // 若需嵌套,建议扁平化或手动组合
}
}

自动生成的代码

宏会自动生成以下内容:

  1. 响应式结构体 GlobalStoreStore:所有字段被包装在 RwSignal 中。
  2. 构造方法 GlobalStoreStore::new(source: GlobalStore)
  3. 快照方法 GlobalStoreStore::get(&self) -> GlobalStore
  4. Store Trait 实现:实现 silex::store::Store,提供 provide() 等方法。
  5. 全局 Hookuse_config() (根据 name 属性或默认生成 use_global_store)。

使用示例

#![allow(unused)]
fn main() {
// 1. 在根组件提供 Store
let config = UserConfig::default();
let store = GlobalStoreStore::new(GlobalStore { config });
store.provide(); // 注入 Context

// 2. 在子组件使用生成的 Hook 获取
let store = use_config();
let theme_signal = store.config; // RwSignal<UserConfig>
}

属性参数 (#[store(...)])

  • name: 自定义生成的 Hook 函数名(默认为 use_{snake_case_struct_name})。
  • err_msg: 自定义 Context 缺失时的 Panic 信息。

注意:目前的 implementation 只是简单的字段 Signal 化,对于嵌套结构需要组合使用。

6. 样式与类名助手

classes!

动态生成类名列表。

#![allow(unused)]
fn main() {
div(())
    .attr("class", classes![
        "container",
        "is-active" => is_active_signal.get() // 仅当 true 时添加
    ])
}

7. 强类型主题系统 (theme!)

Silex 提供了高度集成的强类型主题系统,保障在 CSS 中使用主题变量时的类型安全。

定义主题

使用 theme! 声明具有严格类型约束的主题结构:

#![allow(unused)]
fn main() {
theme! {
    #[theme(main, prefix = "slx")] // 使用 main 标记为主主题,供其他宏自动关联
    pub struct AppTheme {
        pub primary_color: silex::css::types::props::Color,
        pub base_padding: silex::css::types::props::Padding,
    }
}
}

宏会自动生成:

  1. AppTheme 结构体。
  2. 隐藏的内部 Trait(如 AppThemeFields)及其实现,以便在编译期抽取验证每个字段的类型。
  3. 实现 ThemeTypeThemeToCss,支持自动将字段展开为原生的全量 CSS 变量 (--slx-theme-primary_color) 提供给 DOM 树。

与样式组件紧密结合

styled! 宏定义的组件中,你可以通过 $Path::TO::CONST 语法(通常是 $AppTheme::FIELD)直接引用主题库中的值。这种方式利用了 Rust 原生的路径解析,提供了绝对的鲁棒性。

#![allow(unused)]
fn main() {
styled! {
    pub ThemedBox<div>(
        children: Children,
    ) {
        // 直接使用 $AppTheme:: 引用常量
        // 系统在编译期利用 Rust 的类型系统强检查字段类型是否适用于属性!
        padding: $AppTheme::BASE_PADDING; 
        background-color: $AppTheme::PRIMARY_COLOR;
        border-radius: 8px;
    }
}
}

styled! 宏的编译器会自动识别 $ 后跟随的 Rust 路径。它不仅能正确解析常量引用的 CSS 变量值,还能在编译期直接捕获类型错误。

Important

语法迁移提示:旧有的 $theme.field 语法现已彻底移除。如果你在代码中继续使用它,编译器将抛出一个友好的错误提示,引导你迁移到新的路径语法。