// A component's props are a typed struct. The compiler checks // every call site against it — no untyped prop bags. ui! { button(label = "Save".to_string(), on_click = on_save) } // A server function's signature is the wire contract. The same // types are checked on the client (the RPC stub) and the server // (the handler) — they cannot drift out of sync. #[server] async fn save_todo(input: NewTodo) -> Result<Todo, ServerError> { ... }
// In a dynamically-typed world, every combination is constructible: { loading: true, data: result, error: "oops" } // ...valid?! // With a sum type, the nonsense states simply don't exist: enum FetchState<T> { Idle, Loading, Loaded(T), Error(String), }
let view = match fetch_state.get() { FetchState::Idle => idle_view(), FetchState::Loading => spinner(), FetchState::Loaded(d) => results_view(d), FetchState::Error(msg) => error_view(msg), }; // Add `FetchState::Cached(T)` later, and EVERY match over // FetchState becomes a compile error until you handle it.
// You never hold a raw handle. You read it through a closure, // and only while the node is actually mounted: btn_ref.with(|handle| handle.focus()); // Returns Option<R> — None when the button isn't mounted. // There's no way to stash a handle and call .focus() later, // after the component might already be gone.
// Variants and states are typed axes on the stylesheet, // not magic strings the compiler can't see. let style = NavLink().active(derived(move || { if is_current.get() { NavLinkActive::On } else { NavLinkActive::Off } }));