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 asText
nodes, 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
true
orfalse
.false
is 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
).AttrValue
is generally recommended for performance-sensitive scenarios, especially when passing values as properties to other components. - Optional Attributes: Use
Option<T>
for attribute values. If theOption
isNone
, 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 aNodeRef
to 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
if
andif let
control 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
for
syntax directly inhtml!
:{ for collection.iter() }
. This expects the iterator items to be renderableHtml
. - Alternatively, map the collection to
Html
elements and use.collect::<Html>()
. - Always use
key
for 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 afn
that 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&Props
reference 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 buildingProperties
structs 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
Props
struct includes apub children: Html
field, 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
Html
is 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
Html
to 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-levelif
ormatch
expression. - 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
return
early 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. Thef
closure is only re-executed ifdeps
(dependencies) change.use_callback<IN, OUT, F>(f: F) -> Callback<IN, OUT>
: Memoizes aCallback
instance. TheCallback
is 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 aNodeRef
handle, 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 theReducible
trait.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 ancestorContextProvider
component.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 aCallback
from a closure.callback.emit(value)
: Invokes the wrapped closure with the given value.- DOM Events: Event handlers in
html!
(e.g.,onclick
,oninput
) expect aCallback
that takes the correspondingweb_sys
event 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> } }
TargetCast
Trait: Provided by Yew (withinyew::prelude::*
), this trait extendsweb_sys::Event
to 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
.T
must implementClone
andPartialEq
.// 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_context
hook): 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
ContextProvider
withuse_reducer
for 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 withon
followed 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_with
andgloo-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-sys
is heavily feature-gated; enable only the necessary features inCargo.toml
to avoid bloat (e.g.,features = ["Document", "HtmlElement", "Window"]
). - Inheritance:
web-sys
types simulate JavaScript inheritance using Rust'sDeref
andAsRef
traits. For instance, anHtmlElement
can deref toElement
, thenNode
, thenEventTarget
, finallyJsValue
. This allows calling methods from ancestor types. JsCast
Trait: Crucial for downcastingJsValue
or 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 RustFuture
s with JavaScriptPromise
s.spawn_local(future)
: Spawns a RustFuture
to 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.
Suspense
Component: Wraps children components that might "suspend". Requires afallback
prop, which is theHtml
to render during suspension.- Suspending Hooks: A hook can signal suspension by returning
Err(Suspension)
. Suspension::new()
: Creates aSuspension
and aSuspensionHandle
. Whenhandle.resume()
is called (orhandle
is 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).
Routable
Enum: 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
.Switch
Component: Takes anRoutable
enum and arender
function. Therender
function receives the matched route variant and returns theHtml
to display.Link
Component: 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 --release
for optimized, production-ready builds. - Server Configuration:
- SPA Fallback: Configure the HTTP server to serve
index.html
as a fallback for any unmatched URL paths, allowing theyew-router
to handle client-side routing. - MIME-type: Ensure
.wasm
files are served with theapplication/wasm
MIME-type.
- SPA Fallback: Configure the HTTP server to serve
- Relative Paths: Use
<base data-trunk-public-url />
inindex.html
andtrunk 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'slog
crate with the browser console.gloo-console
: Provideslog!
macro for directJsValue
logging to console.tracing-web
: Integratestracing
framework with browser console and performance API.
- Component Lifecycles: Use
tracing
to 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.