25 KiB
Yew Framework Documentation for AI Coders: Core Concepts & Coding
This document provides a comprehensive overview of the Yew framework's core coding concepts, designed to equip AI robot coders with the knowledge necessary to build web applications using Rust and WebAssembly.
Yew is a modern Rust framework for building client-side web applications using WebAssembly (Wasm). It enables the development of highly performant web UIs by leveraging Rust's strong type system and rich ecosystem. Yew promotes a component-based architecture for building reusable and maintainable UI elements.
Fundamental Building Blocks
The html! Macro for UI Composition
Yew employs the html! procedural macro for declarative UI construction, drawing inspiration from JSX. This macro is the primary way to define the structure of your component's output.
Syntax and Features:
- Single Root Node: The
html!macro always expects a single root HTML node. To render multiple top-level elements without a wrapping container, use the fragment syntax:<> ... </>. - Embedding Rust Expressions: Any valid Rust expression can be embedded within the markup using curly braces (
{ expression }). The expression must evaluate to a type that implementsInto<Html>.let header_text = "Welcome to Yew".to_string(); let count = 5; html! { <> <h1>{ header_text }</h1> <p>{"Current count: "}{ count }</p> </> }- Literals: String literals are typically enclosed in quotes and then a
{}, e.g.,{"Hello"}. They are treated asTextnodes, inherently mitigating common HTML injection (XSS) risks.
- Literals: String literals are typically enclosed in quotes and then a
- Element / Component Definition:
- HTML Elements: Standard HTML elements are written as
<tagname property="value">child</tagname>or self-closing<tagname property="value" />. - Dynamic Tag Names: For situations where the HTML tag name is determined at runtime, use the
@{expression}syntax. The expression must be a string.let level = 3; html! { <@{format!("h{}", level)} class="subtitle">{"Dynamic Heading"}</@> } - Yew Components: Yew components are instantiated like custom HTML tags, using PascalCase for their names:
<MyComponent property={value} />.
- HTML Elements: Standard HTML elements are written as
- Attributes and Properties:
- HTML Attributes: Set on elements directly:
<div attribute={rust_value} />. - Boolean Attributes: Set with
trueorfalse.falseis equivalent to omitting the attribute entirely.html! { <input type="checkbox" checked={some_boolean_var} /> } - String-like Attributes: Can accept
&str,String, or Yew's optimizedAttrValue(a cheaply cloneableRc<str>or&'static str).AttrValueis generally recommended for performance-sensitive scenarios, especially when passing values as properties to other components. - Optional Attributes: Use
Option<T>for attribute values. If theOptionisNone, the attribute will not be rendered in the DOM.let maybe_id: Option<&str> = Some("unique-element"); html! { <div id={maybe_id}></div> } // Renders with id or not - Yew-Specific Properties (Special Props): These are not directly reflected in the DOM but serve as instructions to Yew's Virtual DOM.
ref={node_ref_handle}: Connects aNodeRefto a DOM element, allowing direct programmatic access to the underlying DOM node (e.g., for canvas manipulation, scrolling, form input values).key={unique_key}: Provides a unique identifier for elements within a list. Crucial for performance optimization, as Yew uses keys to efficiently reconcile list items during updates, preventing unnecessary re-renders or DOM manipulations. Keys must be unique within their immediate siblings (the list itself) and should be stable/deterministic, not based on item position.
- HTML Attributes: Set on elements directly:
- Conditional Rendering: Use standard Rust
ifandif letcontrol flow structures directly within thehtml!macro to conditionally render content.let show_message = true; html! { if show_message { <p>{"This message is visible."}</p> } else { <p>{"Message hidden."}</p> } } - List Rendering / Iteration:
- Use
forsyntax directly inhtml!:{ for collection.iter() }. This expects the iterator items to be renderableHtml. - Alternatively, map the collection to
Htmlelements and use.collect::<Html>(). - Always use
keyfor list items where the order or presence of items can change, as this drastically improves reconciliation performance.
let items = vec!["Apple", "Banana", "Cherry"]; html! { <ul> { for items.iter().map(|item| html! { <li key={item.to_string()}>{item}</li> }) } </ul> } - Use
Components: Function Components
Yew applications are built from components, which encapsulate UI logic and presentation. Function components are the recommended and most common way to define components in modern Yew.
- Definition: Declare a function component using the
#[function_component]attribute on afnthat returnsHtml. By convention, component names are PascalCase.use yew::prelude::*; #[function_component] fn MySimpleComponent() -> Html { html! { <p>{"Hello from a component!"}</p> } } - Properties (Props): Data is passed from parent to child components using "props". Props are defined by a struct that must implement
Properties(usually via#[derive(Properties)]) andPartialEq. The function component accepts an&Propsreference as its single argument.#[derive(Properties, PartialEq)] pub struct GreetProps { pub name: String, #[prop_or_default] // Field attribute for optional props with default value pub greeting_text: String, } #[function_component] fn Greeter(props: &GreetProps) -> Html { let greeting = if props.greeting_text.is_empty() { "Hello".to_string() } else { props.greeting_text.clone() }; html! { <p>{ format!("{}, {}!", greeting, props.name) }</p> } } // Usage: html! { <Greeter name="Alice" /> } or html! { <Greeter name="Bob" greeting_text="Hi" /> }- Reactive Nature of Props: Yew automatically re-renders a component when its props change (detected via
PartialEq). props!Macro: Allows buildingPropertiesstructs programmatically.use yew::props; let my_props = props! { GreetProps { name: "Charlie".to_string() } }; html! { <Greeter ..my_props /> }
- Reactive Nature of Props: Yew automatically re-renders a component when its props change (detected via
- Children (Special Prop): If a component's
Propsstruct includes apub children: Htmlfield, it can accept nestedhtml!content.#[derive(Properties, PartialEq)] pub struct CardProps { pub title: String, pub children: Html, // This field name is special } #[function_component] fn Card(props: &CardProps) -> Html { html! { <div class="card"> <h2>{ &props.title }</h2> <div class="card-content"> { props.children.clone() } </div> </div> } } // Usage: html! { <Card title="My Card"> <p>Some content here.</p> </Card> } - Generic Components: Function components can be generic over types, provided the generic type parameters meet the necessary trait bounds (e.g.,
PartialEq).#[derive(Properties, PartialEq)] pub struct ItemDisplayProps<T: PartialEq + ToHtml> { pub item: T, } #[function_component] pub fn ItemDisplay<T>(props: &ItemDisplayProps<T>) -> Html where T: PartialEq + ToHtml + 'static, // 'static for use in VDOM { html! { <p>{ &props.item }</p> } } // Usage: html! { <ItemDisplay<i32> item=123 /> } - Pure Components: A function component is "pure" if its output
Htmlis solely determined by its props, and it has no side effects or internal mutable state. Yew's reconciliation benefits from pure components.- Simple pure components with no hooks can sometimes be implemented as regular functions returning
Htmlto reduce overhead.
- Simple pure components with no hooks can sometimes be implemented as regular functions returning
- Communication Patterns:
State Management and Interactivity
Yew provides "hooks" to manage mutable state and side effects within function components.
Hooks
Hooks are functions that allow "hooking into" the lifecycle and state management capabilities of function components.
Rules of Hooks:
- Naming Convention: Hook function names must start with
use_. - Top-Level Calls Only: Hooks can only be called at the top level of a function component or another hook, and not inside loops, conditionals without
if let, or nested functions unless within the scrutinee of a top-leveliformatchexpression. - Consistent Call Order: Hooks must be called in the exact same order on every render. This enables Yew to correctly associate state with hook calls.
- No Early Return: A component using hooks cannot
returnearly before all hooks are called unless using Suspense.
Common Pre-defined Hooks:
use_state<T>() -> UseStateHandle<T>: Manages local component state. Returns a handle that can be dereferenced to get the current value and has a.set(new_value)method to update the state. Updating state triggers a re-render.use yew::prelude::*; #[function_component] fn ClickCounter() -> Html { let count = use_state(|| 0); // Initializes with 0 let onclick = { let count = count.clone(); // Clone the handle for the closure move |_| { count.set(*count + 1); // Update the state } }; html! { <button {onclick}>{"Clicked "}{*count}{" times"}</button> } }use_state_eq<T: PartialEq>() -> UseStateHandle<T>: Similar touse_state, but only triggers a re-render if the new value is not equal to the current value (usingPartialEq).use_memo<T, D>(f: impl FnOnce(D) -> T, deps: D) -> Rc<T>: Memoizes an expensive computation. Thefclosure is only re-executed ifdeps(dependencies) change.use_callback<IN, OUT, F>(f: F) -> Callback<IN, OUT>: Memoizes aCallbackinstance. TheCallbackis only recreated if its dependencies (captured variables) change.use_mut_ref<T>() -> Rc<RefCell<T>>: Provides a mutable reference (RefCell) that persists across re-renders without triggering them. Useful for mutable data that doesn't directly affect rendering or for interop with mutable JS APIs.use_node_ref() -> NodeRef: Creates aNodeRefhandle, used to get a direct reference to a rendered DOM element.use yew::prelude::*; use web_sys::HtmlInputElement; #[function_component] fn MyInput() -> Html { let input_ref = use_node_ref(); let current_value = use_state(|| String::new()); let on_input_change = { let input_ref = input_ref.clone(); let current_value = current_value.clone(); move |_| { if let Some(input) = input_ref.cast::<HtmlInputElement>() { current_value.set(input.value()); } } }; html! { <div> <input ref={input_ref} type="text" oninput={on_input_change} /> <p>{"Input Value: "}{&*current_value}</p> </div> } }use_reducer<R>() -> UseReducerHandle<R>: Manages more complex state using a reducer pattern (similar to Redux), allowing state updates based on dispatched "actions". The state must implement theReducibletrait.use_reducer_eq<R: PartialEq>() -> UseReducerHandle<R>: Similar touse_reducer, but dispatches only if the new state differs from the old.use_effect(f: impl FnOnce() -> impl FnOnce()): Runs a side effect after every render. Can return a cleanup closure that runs before the next effect or when the component is unmounted.use_effect_with<D>(deps: D, f: impl FnOnce(D) -> impl FnOnce()): Runs a side effect only whendeps(dependencies) change.use_context<T: Clone + PartialEq>() -> Option<Rc<T>>: Accesses a context value provided by an ancestorContextProvidercomponent.use_force_update(): Returns a callback that, when emitted, forces a re-render of the component. Use sparingly.
Custom Hooks: Components can extract reusable stateful logic into custom hooks by defining functions starting with use_ and marking them with #[hook]. Custom hooks compose existing hooks.
Callbacks
Callback is a crucial type for event handling and child-to-parent communication. It wraps an Fn closure in an Rc, making it cheaply clonable.
Callback::from(closure): Creates aCallbackfrom a closure.callback.emit(value): Invokes the wrapped closure with the given value.- DOM Events: Event handlers in
html!(e.g.,onclick,oninput) expect aCallbackthat takes the correspondingweb_sysevent type as an argument.use yew::prelude::*; use web_sys::MouseEvent; #[function_component] fn MyButton() -> Html { let onclick_handler = Callback::from(move |e: MouseEvent| { // Access event properties: log::info!("Click event at: ({}, {})", e.client_x(), e.client_y()); // More complex logic... }); html! { <button onclick={onclick_handler}>{"Click Me"}</button> } } TargetCastTrait: Provided by Yew (withinyew::prelude::*), this trait extendsweb_sys::Eventto safely cast the event target to a specific HTML element type (e.g.,HtmlInputElement).event.target_dyn_into::<HtmlElementType>() -> Option<HtmlElementType>(safe, checked)event.target_unchecked_into::<HtmlElementType>() -> HtmlElementType(unchecked, use with caution when type is guaranteed)
Contexts
Contexts provide a way to pass data deeply through the component tree without manually "prop drilling" at every level.
- Provider (
ContextProvider): An ancestor component wraps its children withContextProvider<T>, providing a value of typeT.Tmust implementCloneandPartialEq.// Define your context data #[derive(Clone, Debug, PartialEq)] struct Theme { foreground: String, background: String, } #[function_component] fn ThemeProvider(props: &ChildrenProps) -> Html { // ChildrenProps from yew::html let theme = use_state(|| Theme { foreground: "#000".to_string(), background: "#eee".to_string(), }); html! { <yew::ContextProvider<Theme> context={(*theme).clone()}> { props.children.clone() } </yew::ContextProvider<Theme>> } } // Usage: html! { <ThemeProvider> <ArbitraryDeepComponent /> </ThemeProvider> } - Consumer (
use_contexthook): Descendant function components useuse_context::<T>()to retrieve the provided value.#[function_component] fn ThemedText() -> Html { let theme = use_context::<Theme>() // Retrieve the Theme context .expect("Theme context not provided!"); html! { <p style={format!("color: {}; background: {};", theme.foreground, theme.background)}> {"This text is themed."} </p> } } - Mutable Contexts: To allow children to modify a context value, combine
ContextProviderwithuse_reducerfor a predictable state update mechanism.
Event Handling and Delegation
Yew integrates with web-sys for DOM events. Yew's event system employs event delegation: listeners are not directly attached to individual elements but are handled by a single delegate at the application's root. Events then "bubble up" through Yew's Virtual DOM hierarchy.
- Event Listener Names: In
html!, event listeners start withonfollowed by the event name (e.g.,onclick,oninput). - Manual Event Listeners: For events not directly supported by
html!or for fine-grained control, useuse_effect_withandgloo-events(EventListener) to manually attach/detach event listeners toNodeRef-obtained DOM elements.use gloo::events::EventListener; use yew::prelude::*; use web_sys::HtmlElement; #[function_component] fn CustomEventHandler() -> Html { let div_ref = use_node_ref(); use_effect_with(div_ref.clone(), { let div_ref_clone = div_ref.clone(); move |_| { let mut listener_obj: Option<EventListener> = None; if let Some(div_element) = div_ref_clone.cast::<HtmlElement>() { let listener = EventListener::new(&div_element, "custom-event", move |event| { log::info!("Custom event received!"); // event.dyn_into::<web_sys::CustomEvent>() for richer data }); listener_obj = Some(listener); } move || drop(listener_obj) // Cleanup when effect re-runs or component unmounts } }); html! { <div ref={div_ref}>{"Div with custom event listener"}</div> } }
Advanced Topics
Interacting with JavaScript (JS) and Web APIs
Yew compiles to Wasm, but direct interaction with browser APIs often involves wasm-bindgen and related crates.
wasm-bindgen: Bridges calls between Rust and JavaScript. Used for defining FFI (Foreign Function Interface) to import/export functions.web-sys: Provides Rust bindings for all Web APIs (DOM, Fetch, etc.). This is the primary way to interact with the browser from Rust.- Features:
web-sysis heavily feature-gated; enable only the necessary features inCargo.tomlto avoid bloat (e.g.,features = ["Document", "HtmlElement", "Window"]). - Inheritance:
web-systypes simulate JavaScript inheritance using Rust'sDerefandAsReftraits. For instance, anHtmlElementcan deref toElement, thenNode, thenEventTarget, finallyJsValue. This allows calling methods from ancestor types. JsCastTrait: Crucial for downcastingJsValueor genericEventTarget(fromweb_sys) to specific, more concrete types (e.g.,HtmlInputElement). Providesdyn_into(checked, returnsResult) andunchecked_into(unchecked, faster).
- Features:
js-sys: Provides Rust bindings for JavaScript's standard, built-in objects (e.g.,Date,Object).wasm-bindgen-futures: Bridges RustFutures with JavaScriptPromises.spawn_local(future): Spawns a RustFutureto run on the current thread's event loop, enabling asynchronous operations (e.g.,async fetch()calls).
Asynchronous Operations and Suspense
Yew's Suspense component allows suspending component rendering while waiting for an asynchronous task (e.g., data fetching) to complete, showing a fallback UI in the interim. This enables a "Render-as-You-Fetch" pattern.
SuspenseComponent: Wraps children components that might "suspend". Requires afallbackprop, which is theHtmlto render during suspension.- Suspending Hooks: A hook can signal suspension by returning
Err(Suspension). Suspension::new(): Creates aSuspensionand aSuspensionHandle. Whenhandle.resume()is called (orhandleis dropped), the suspended component re-renders.use yew::prelude::*; use yew::suspense::{Suspension, SuspensionResult}; // A hypothetical async data loading function async fn fetch_user_data() -> String { "AI Robot Coder".to_string() } // This hook will suspend rendering until user data is fetched #[hook] fn use_current_user() -> SuspensionResult<String> { let (suspension, handle) = Suspension::new(); // Create suspension let user_data_state = use_state(|| None ); // Local state for fetched data // If data is already there, return Ok. if let Some(data) = &*user_data_state { return Ok(data.clone()); } // If not, spawn an async task and signal suspension. let user_data_state_clone = user_data_state.clone(); wasm_bindgen_futures::spawn_local(async move { let data = fetch_user_data().await; user_data_state_clone.set(Some(data)); // Update state handle.resume(); // Signal the suspension to resume }); Err(suspension) // Signal that the component should suspend } #[function_component] fn UserDisplay() -> HtmlResult { // HtmlResult instead of Html for suspending components let user_name = use_current_user()?; // The '?' operator propagates the suspension Ok(html! { <p>{ format!("Hello, {}!", user_name) }</p> }) } #[function_component] fn App() -> Html { html! { <Suspense fallback={html!{<p>{"Loading user..."}</p>}}> <UserDisplay /> </Suspense> } }
Routing (yew-router crate)
yew-router provides client-side routing for Single Page Applications (SPAs).
RoutableEnum: Define routes as an enum derivingRoutable. Each variant maps to a URL path using#[at("/path")].#[not_found]designates the fallback route.- Path Segments: Define dynamic segments with
:segment_name(for single segments) or*wildcard(for multi-segment wildcards). These become fields in the enum variant.
- Path Segments: Define dynamic segments with
BrowserRouter: The main router component that manages browser history. All<Switch />and<Link />components must be descendants ofBrowserRouter.SwitchComponent: Takes anRoutableenum and arenderfunction. Therenderfunction receives the matched route variant and returns theHtmlto display.LinkComponent: A component that renders an<a>tag but performs client-side navigation (pushState) instead of a full page reload.- Navigation API (
use_navigator()): Provides programmatic navigation vianavigator.push(&Route)ornavigator.replace(&Route). - Query Parameters: Can be included in navigation by passing a serializable struct/map to
push_with_query()or retrieved fromlocation.query(). - Nested Routers: Allows for modular routing within sub-sections of the application, often using a
*wildcard in the parent router to delegate to a child router. - Basename: Configure a common prefix for all routes, useful when the application is served from a sub-path.
Web Workers and Agents
Yew Agents are a mechanism for offloading tasks to Web Workers, enabling concurrent processing and preventing UI unresponsiveness.
- Types of Agents:
Public: A single instance shared across all bridges in a web worker.Private: A new instance spawned for each bridge connection.
- Communication:
Bridges: Bi-directional communication between a component and an agent, or between agents.Dispatchers: Uni-directional communication from a component to an agent.
- Overhead: Agents introduce serialization/deserialization overhead for messages between threads (using
bincode), making them suitable for chunky computations rather than frequent, small messages.
Deployment Considerations
- Release Build: Use
trunk build --releasefor optimized, production-ready builds. - Server Configuration:
- SPA Fallback: Configure the HTTP server to serve
index.htmlas a fallback for any unmatched URL paths, allowing theyew-routerto handle client-side routing. - MIME-type: Ensure
.wasmfiles are served with theapplication/wasmMIME-type.
- SPA Fallback: Configure the HTTP server to serve
- Relative Paths: Use
<base data-trunk-public-url />inindex.htmlandtrunk build --public-url /your/path/to deploy the app under a sub-path. - Environment Variables: Use
std::env!("VAR_NAME")to embed environment variables at compile time (runtime access in browser is not direct).
Debugging and Testing
Debugging
- Panics: Yew automatically logs Rust panics to the browser's developer console.
- Console Logging:
wasm-logger: Integrates Rust'slogcrate with the browser console.gloo-console: Provideslog!macro for directJsValuelogging to console.tracing-web: Integratestracingframework with browser console and performance API.
- Component Lifecycles: Use
tracingto gain insights into component re-renders and hook execution. - Source Maps: Limited support available; requires specific configuration.
Testing
wasm_bindgen_test: Enables running Rust tests directly in a browser environment.- Snapshot Testing: Yew provides utilities (
yew::tests::layout_tests) for snapshot testing component output. - Shallow Rendering: Work in progress for testing components in isolation without rendering their full subtree.