rs-yew-crate

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Yew Framework (v0.22)

Yew框架(v0.22)

Yew is a modern Rust framework for creating multi-threaded frontend web applications compiled to WebAssembly. It uses a virtual DOM, component-based architecture, and a JSX-like
html!
macro. MSRV: Rust 1.84.0.
Yew是一款现代Rust框架,用于创建可编译为WebAssembly的多线程前端Web应用。它采用虚拟DOM、基于组件的架构,以及类JSX的
html!
宏。最低支持Rust版本(MSRV):1.84.0。

Project Setup

项目搭建

Prerequisites

前置条件

bash
rustup target add wasm32-unknown-unknown
cargo install --locked trunk
bash
rustup target add wasm32-unknown-unknown
cargo install --locked trunk

New Project (manual)

手动创建新项目

bash
cargo new yew-app && cd yew-app
Cargo.toml:
toml
[package]
name = "yew-app"
version = "0.1.0"
edition = "2021"

[dependencies]
yew = { version = "0.22", features = ["csr"] }
Feature
csr
enables
Renderer
and client-side rendering code. Omit for library crates. Use
ssr
for server-side rendering. Use
serde
for serde integration on
AttrValue
.
index.html (project root):
html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Yew App</title>
  </head>
  <body></body>
</html>
src/main.rs:
rust
use yew::prelude::*;

#[component]
fn App() -> Html {
    let counter = use_state(|| 0);
    let onclick = {
        let counter = counter.clone();
        move |_| { counter.set(*counter + 1); }
    };

    html! {
        <div>
            <button {onclick}>{ "+1" }</button>
            <p>{ *counter }</p>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
bash
trunk serve --open
bash
cargo new yew-app && cd yew-app
Cargo.tom:
toml
[package]
name = "yew-app"
version = "0.1.0"
edition = "2021"

[dependencies]
yew = { version = "0.22", features = ["csr"] }
特性
csr
启用
Renderer
和客户端渲染代码。库 crate 可省略该特性。使用
ssr
启用服务端渲染。使用
serde
可在
AttrValue
上集成序列化功能。
index.html(项目根目录):
html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Yew App</title>
  </head>
  <body></body>
</html>
src/main.rs:
rust
use yew::prelude::*;

#[component]
fn App() -> Html {
    let counter = use_state(|| 0);
    let onclick = {
        let counter = counter.clone();
        move |_| { counter.set(*counter + 1); }
    };

    html! {
        <div>
            <button {onclick}>{ "+1" }</button>
            <p>{ *counter }</p>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
bash
trunk serve --open

Starter Template

启动模板

bash
cargo generate --git https://github.com/yewstack/yew-trunk-minimal-template
trunk serve
bash
cargo generate --git https://github.com/yewstack/yew-trunk-minimal-template
trunk serve

Trunk Configuration (Trunk.toml)

Trunk配置(Trunk.toml)

toml
[serve]
address = "127.0.0.1"
port = 8080
toml
[serve]
address = "127.0.0.1"
port = 8080

Function Components

函数组件

The recommended component model. Annotate with
#[component]
, name in PascalCase, return
Html
.
rust
use yew::{component, html, Html};

#[component]
fn HelloWorld() -> Html {
    html! { "Hello world" }
}

// Usage:
html! { <HelloWorld /> }
推荐使用的组件模型。使用
#[component]
注解,组件名采用大驼峰命名,返回
Html
类型。
rust
use yew::{component, html, Html};

#[component]
fn HelloWorld() -> Html {
    html! { "Hello world" }
}

// 用法:
html! { <HelloWorld /> }

Properties (Props)

属性(Props)

Derive
Properties + PartialEq
. Use
AttrValue
instead of
String
for attribute values (cheap to clone).
rust
use yew::{component, html, Html, Properties};

#[derive(Properties, PartialEq)]
pub struct Props {
    pub name: AttrValue,
    #[prop_or_default]
    pub is_loading: bool,
    #[prop_or(AttrValue::Static("default"))]
    pub label: AttrValue,
    #[prop_or_else(|| AttrValue::from("computed"))]
    pub dynamic: AttrValue,
}

#[component]
fn Greeting(&Props { ref name, is_loading, .. }: &Props) -> Html {
    if is_loading { return html! { "Loading" }; }
    html! { <p>{"Hello, "}{name}</p> }
}

// Usage:
html! { <Greeting name="Alice" is_loading=false /> }
Children: Use
children: Html
field name in props to receive nested content.
rust
#[derive(Properties, PartialEq)]
pub struct ContainerProps {
    pub children: Html,
}

#[component]
fn Container(props: &ContainerProps) -> Html {
    html! { <div class="wrapper">{ props.children.clone() }</div> }
}
Auto-props (
yew-autoprops
crate): Generate
Properties
struct from function args.
rust
use yew_autoprops::autoprops;
// #[autoprops] must appear BEFORE #[component]
#[autoprops]
#[component]
fn Greetings(#[prop_or_default] is_loading: bool, message: &AttrValue) -> Html {
    html! { <>{message}</> }
}
派生
Properties + PartialEq
。属性值使用
AttrValue
而非
String
(克隆成本低)。
rust
use yew::{component, html, Html, Properties};

#[derive(Properties, PartialEq)]
pub struct Props {
    pub name: AttrValue,
    #[prop_or_default]
    pub is_loading: bool,
    #[prop_or(AttrValue::Static("default"))]
    pub label: AttrValue,
    #[prop_or_else(|| AttrValue::from("computed"))]
    pub dynamic: AttrValue,
}

#[component]
fn Greeting(&Props { ref name, is_loading, .. }: &Props) -> Html {
    if is_loading { return html! { "Loading" }; }
    html! { <p>{"Hello, "}{name}</p> }
}

// 用法:
html! { <Greeting name="Alice" is_loading=false /> }
子组件:在Props中使用
children: Html
字段接收嵌套内容。
rust
#[derive(Properties, PartialEq)]
pub struct ContainerProps {
    pub children: Html,
}

#[component]
fn Container(props: &ContainerProps) -> Html {
    html! { <div class="wrapper">{ props.children.clone() }</div> }
}
自动Props
yew-autoprops
crate):从函数参数自动生成
Properties
结构体。
rust
use yew_autoprops::autoprops;
// #[autoprops]必须出现在#[component]之前
#[autoprops]
#[component]
fn Greetings(#[prop_or_default] is_loading: bool, message: &AttrValue) -> Html {
    html! { <>{message}</> }
}

Callbacks

回调函数

Callback<IN, OUT>
wraps
Fn
in
Rc
. Use
Callback::from(|e| ...)
for event handlers. Pass down as props for child-to-parent communication.
rust
// Passing callbacks as props
#[derive(Properties, PartialEq)]
pub struct ButtonProps {
    pub on_click: Callback<Video>,
}

// Creating callbacks with state
let selected = use_state(|| None);
let on_select = {
    let selected = selected.clone();
    Callback::from(move |video: Video| selected.set(Some(video)))
};
Callback<IN, OUT>
Fn
包裹在
Rc
中。使用
Callback::from(|e| ...)
创建事件处理器。可作为Props传递,实现子组件到父组件的通信。
rust
// 将回调作为Props传递
#[derive(Properties, PartialEq)]
pub struct ButtonProps {
    pub on_click: Callback<Video>,
}

// 结合状态创建回调
let selected = use_state(|| None);
let on_select = {
    let selected = selected.clone();
    Callback::from(move |video: Video| selected.set(Some(video)))
};

Hooks

Hooks

Rules: (1) name starts with
use_
, (2) call only at top-level of component/hook, (3) same call order every render.
Pre-defined hooks:
  • use_state
    /
    use_state_eq
    — reactive local state
  • use_reducer
    /
    use_reducer_eq
    — complex state via reducer pattern
  • use_effect
    /
    use_effect_with
    — side effects (not run during SSR)
  • use_memo
    — memoized computation
  • use_callback
    — memoized callback
  • use_context
    — consume context value
  • use_ref
    /
    use_mut_ref
    — persistent mutable reference
  • use_node_ref
    — DOM node reference
  • use_force_update
    — manual re-render trigger
rust
// State
let counter = use_state(|| 0);
counter.set(*counter + 1);

// Effect with deps (runs when deps change)
{
    let data = data.clone();
    use_effect_with(deps, move |_| {
        // setup logic
        || () // cleanup
    });
}

// Reducer
use yew::prelude::*;

enum Action { Increment, Decrement }
struct State { count: i32 }

impl Reducible for State {
    type Action = Action;
    fn reduce(self: Rc<Self>, action: Action) -> Rc<Self> {
        match action {
            Action::Increment => Self { count: self.count + 1 }.into(),
            Action::Decrement => Self { count: self.count - 1 }.into(),
        }
    }
}

let state = use_reducer(|| State { count: 0 });
state.dispatch(Action::Increment);
Custom hooks: Annotate with
#[hook]
, compose from built-in hooks.
rust
use yew::prelude::*;

#[hook]
pub fn use_toggle(initial: bool) -> (bool, Callback<()>) {
    let state = use_state(move || initial);
    let toggle = {
        let state = state.clone();
        Callback::from(move |_| state.set(!*state))
    };
    (*state, toggle)
}
规则:(1)名称以
use_
开头;(2)仅在组件/Hook的顶层调用;(3)每次渲染的调用顺序一致。
预定义Hooks
  • use_state
    /
    use_state_eq
    — 响应式本地状态
  • use_reducer
    /
    use_reducer_eq
    — 通过reducer模式管理复杂状态
  • use_effect
    /
    use_effect_with
    — 副作用(SSR期间不执行)
  • use_memo
    — 记忆化计算
  • use_callback
    — 记忆化回调
  • use_context
    — 消费上下文值
  • use_ref
    /
    use_mut_ref
    — 持久化可变引用
  • use_node_ref
    — DOM节点引用
  • use_force_update
    — 手动触发重新渲染
rust
// 状态
let counter = use_state(|| 0);
counter.set(*counter + 1);

// 带依赖的Effect(依赖变化时执行)
{
    let data = data.clone();
    use_effect_with(deps, move |_| {
        // 初始化逻辑
        || () // 清理逻辑
    });
}

// Reducer
use yew::prelude::*;

enum Action { Increment, Decrement }
struct State { count: i32 }

impl Reducible for State {
    type Action = Action;
    fn reduce(self: Rc<Self>, action: Action) -> Rc<Self> {
        match action {
            Action::Increment => Self { count: self.count + 1 }.into(),
            Action::Decrement => Self { count: self.count - 1 }.into(),
        }
    }
}

let state = use_reducer(|| State { count: 0 });
state.dispatch(Action::Increment);
自定义Hooks:使用
#[hook]
注解,基于内置Hooks组合实现。
rust
use yew::prelude::*;

#[hook]
pub fn use_toggle(initial: bool) -> (bool, Callback<()>) {
    let state = use_state(move || initial);
    let toggle = {
        let state = state.clone();
        Callback::from(move |_| state.set(!*state))
    };
    (*state, toggle)
}

html! Macro

html!宏

For detailed syntax rules, element attributes, conditional rendering, fragments, and lists, see references/html_and_events.md.
Key rules:
  1. Single root node required (use
    <>...</>
    fragments for multiple)
  2. String literals must be quoted and wrapped in braces:
    { "text" }
  3. Tags must self-close (
    <br />
    ) or have matching close tags
  4. Rust expressions go in
    { ... }
  5. if
    /
    if let
    for conditional rendering
  6. for item in iter { ... }
    for lists (add
    key
    prop)
rust
html! {
    <>
        <h1>{ "Title" }</h1>
        if show_subtitle {
            <p>{ "Subtitle" }</p>
        }
        for item in &items {
            <li key={item.id}>{ &item.name }</li>
        }
    </>
}
关于详细的语法规则、元素属性、条件渲染、片段和列表的使用,请参考references/html_and_events.md
核心规则:
  1. 必须有单个根节点(使用
    <>...</>
    片段包裹多个节点)
  2. 字符串字面量必须加引号并放在大括号中:
    { "text" }
  3. 标签必须自闭合(
    <br />
    )或有匹配的闭合标签
  4. Rust表达式放在
    { ... }
  5. 使用
    if
    /
    if let
    实现条件渲染
  6. 使用
    for item in iter { ... }
    渲染列表(添加
    key
    属性)
rust
html! {
    <>
        <h1>{ "Title" }</h1>
        if show_subtitle {
            <p>{ "Subtitle" }</p>
        }
        for item in &items {
            <li key={item.id}>{ &item.name }</li>
        }
    </>
}

Routing

路由

Add
yew-router
crate. See references/routing.md for nested routers, basename, and query parameters.
rust
use yew::prelude::*;
use yew_router::prelude::*;

#[derive(Clone, Routable, PartialEq)]
enum Route {
    #[at("/")]
    Home,
    #[at("/post/:id")]
    Post { id: String },
    #[not_found]
    #[at("/404")]
    NotFound,
}

fn switch(route: Route) -> Html {
    match route {
        Route::Home => html! { <h1>{"Home"}</h1> },
        Route::Post { id } => html! { <p>{format!("Post {}", id)}</p> },
        Route::NotFound => html! { <h1>{"404"}</h1> },
    }
}

#[component]
fn App() -> Html {
    html! {
        <BrowserRouter>
            <Switch<Route> render={switch} />
        </BrowserRouter>
    }
}
Navigation:
<Link<Route> to={Route::Home}>{"Home"}</Link<Route>>
or
use_navigator()
hook.
添加
yew-router
crate。关于嵌套路由、基准路径和查询参数的使用,请参考references/routing.md
rust
use yew::prelude::*;
use yew_router::prelude::*;

#[derive(Clone, Routable, PartialEq)]
enum Route {
    #[at("/")]
    Home,
    #[at("/post/:id")]
    Post { id: String },
    #[not_found]
    #[at("/404")]
    NotFound,
}

fn switch(route: Route) -> Html {
    match route {
        Route::Home => html! { <h1>{"Home"}</h1> },
        Route::Post { id } => html! { <p>{format!("Post {}", id)}</p> },
        Route::NotFound => html! { <h1>{"404"}</h1> },
    }
}

#[component]
fn App() -> Html {
    html! {
        <BrowserRouter>
            <Switch<Route> render={switch} />
        </BrowserRouter>
    }
}
导航:使用
<Link<Route> to={Route::Home}>{"Home"}</Link<Route>>
use_navigator()
Hook。

Contexts

上下文

Provide shared state without prop drilling. See references/contexts_and_state.md.
rust
#[derive(Clone, PartialEq)]
struct Theme { foreground: String, background: String }

// Provider
#[component]
fn App() -> Html {
    let theme = Theme { foreground: "#000".into(), background: "#fff".into() };
    html! {
        <ContextProvider<Theme> context={theme}>
            <ThemedButton />
        </ContextProvider<Theme>>
    }
}

// Consumer
#[component]
fn ThemedButton() -> Html {
    let theme = use_context::<Theme>().expect("no theme context");
    html! { <button style={format!("color: {}", theme.foreground)}>{"Click"}</button> }
}
无需Props透传即可共享状态。关于详细使用,请参考references/contexts_and_state.md
rust
#[derive(Clone, PartialEq)]
struct Theme { foreground: String, background: String }

// 提供者
#[component]
fn App() -> Html {
    let theme = Theme { foreground: "#000".into(), background: "#fff".into() };
    html! {
        <ContextProvider<Theme> context={theme}>
            <ThemedButton />
        </ContextProvider<Theme>>
    }
}

// 消费者
#[component]
fn ThemedButton() -> Html {
    let theme = use_context::<Theme>().expect("no theme context");
    html! { <button style={format!("color: {}", theme.foreground)}>{"Click"}</button> }
}

Data Fetching

数据获取

Use
gloo-net
for HTTP,
serde
for deserialization,
wasm-bindgen-futures
for async.
toml
[dependencies]
yew = { version = "0.22", features = ["csr", "serde"] }
gloo-net = "0.6"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen-futures = "0.4"
rust
use gloo_net::http::Request;
use yew::prelude::*;

#[component]
fn App() -> Html {
    let data = use_state(Vec::new);
    {
        let data = data.clone();
        use_effect_with((), move |_| {
            let data = data.clone();
            wasm_bindgen_futures::spawn_local(async move {
                let resp: Vec<Item> = Request::get("/api/items")
                    .send().await.unwrap()
                    .json().await.unwrap();
                data.set(resp);
            });
            || ()
        });
    }
    // render data...
    html! {}
}
使用
gloo-net
处理HTTP请求,
serde
进行反序列化,
wasm-bindgen-futures
处理异步操作。
toml
[dependencies]
yew = { version = "0.22", features = ["csr", "serde"] }
gloo-net = "0.6"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen-futures = "0.4"
rust
use gloo_net::http::Request;
use yew::prelude::*;

#[component]
fn App() -> Html {
    let data = use_state(Vec::new);
    {
        let data = data.clone();
        use_effect_with((), move |_| {
            let data = data.clone();
            wasm_bindgen_futures::spawn_local(async move {
                let resp: Vec<Item> = Request::get("/api/items")
                    .send().await.unwrap()
                    .json().await.unwrap();
                data.set(resp);
            });
            || ()
        });
    }
    // 渲染数据...
    html! {}
}

Events and DOM Interaction

事件与DOM交互

See references/html_and_events.md for full event type table and typed event targets.
rust
// Input handling with TargetCast
use web_sys::HtmlInputElement;
use yew::prelude::*;

#[component]
fn TextInput() -> Html {
    let value = use_state(String::default);
    let oninput = {
        let value = value.clone();
        Callback::from(move |e: InputEvent| {
            let input: HtmlInputElement = e.target_unchecked_into();
            value.set(input.value());
        })
    };
    html! { <input type="text" {oninput} value={(*value).clone()} /> }
}
完整的事件类型表和类型化事件目标,请参考references/html_and_events.md
rust
// 使用TargetCast处理输入
use web_sys::HtmlInputElement;
use yew::prelude::*;

#[component]
fn TextInput() -> Html {
    let value = use_state(String::default);
    let oninput = {
        let value = value.clone();
        Callback::from(move |e: InputEvent| {
            let input: HtmlInputElement = e.target_unchecked_into();
            value.set(input.value());
        })
    };
    html! { <input type="text" {oninput} value={(*value).clone()} /> }
}

Server-Side Rendering

服务端渲染

See references/ssr.md for SSR setup, hydration, Suspense integration, and component lifecycle during SSR.
关于SSR搭建、Hydration、Suspense集成以及SSR期间的组件生命周期,请参考references/ssr.md

Agents (Web Workers)

Agents(Web Workers)

Offload tasks to web workers for concurrent processing. Use
yew-agent
crate. Agents support Public (singleton) and Private (per-bridge) modes. Communication via bridges (bidirectional) and dispatchers (unidirectional). Messages serialized with
bincode
.
将任务卸载到Web Workers进行并发处理。使用
yew-agent
crate。Agents支持Public(单例)和Private(每个桥接实例一个)模式。通过桥接(双向)和调度器(单向)进行通信。消息通过
bincode
序列化。

Anti-Patterns to Avoid

需避免的反模式

  1. String
    in props
    — Use
    AttrValue
    (cheap clone via
    Rc<str>
    )
  2. Vec<T>
    in props
    — Use
    IArray<T>
    from
    implicit-clone
  3. Interior mutability (
    RefCell
    ,
    Mutex
    ) — Yew can't detect changes; use hooks
  4. Side effects in render — Move to
    use_effect
    /
    use_effect_with
  5. Missing keys on lists — Always add
    key
    to iterated elements
  6. Accessing Web APIs during SSR — Isolate in
    use_effect
    (not run server-side)
  1. Props中使用
    String
    — 改用
    AttrValue
    (通过
    Rc<str>
    实现低成本克隆)
  2. Props中使用
    Vec<T>
    — 改用
    implicit-clone
    中的
    IArray<T>
  3. 内部可变性
    RefCell
    Mutex
    )— Yew无法检测到变化;使用Hooks替代
  4. 渲染期间执行副作用 — 移至
    use_effect
    /
    use_effect_with
  5. 列表缺少key属性 — 始终为迭代元素添加
    key
  6. SSR期间访问Web API — 隔离到
    use_effect
    中(服务端不执行)

Ecosystem & Common Dependencies

生态系统与常用依赖

CratePurpose
yew-router
Client-side routing
gloo
Ergonomic web APIs (net, timers, storage, events, dialogs)
gloo-net
HTTP requests (fetch)
wasm-bindgen
Rust ↔ JS interop
wasm-bindgen-futures
Async/await in WASM
web-sys
Raw Web API bindings
js-sys
JavaScript standard built-in bindings
serde
/
serde_json
Serialization
stylist
CSS-in-Rust
yewdux
Redux-like state management
yew-hooks
Community hooks collection
yew-autoprops
Auto-generate props structs
bounce
State management (Redux/Recoil-inspired)
implicit-clone
Cheap-clone types (
IString
,
IArray
)
Crate用途
yew-router
客户端路由
gloo
易用的Web API封装(网络、定时器、存储、事件、对话框)
gloo-net
HTTP请求(fetch)
wasm-bindgen
Rust ↔ JS 互操作
wasm-bindgen-futures
WASM中的Async/await支持
web-sys
原始Web API绑定
js-sys
JavaScript标准内置对象绑定
serde
/
serde_json
序列化
stylist
Rust中编写CSS
yewdux
类Redux的状态管理
yew-hooks
社区Hooks集合
yew-autoprops
自动生成Props结构体
bounce
状态管理(灵感来自Redux/Recoil)
implicit-clone
低成本克隆类型(
IString
IArray

References

参考资料

  • HTML macro, events, and DOM interaction: references/html_and_events.md
  • Routing (yew-router) patterns: references/routing.md
  • Contexts and state management: references/contexts_and_state.md
  • Server-side rendering and hydration: references/ssr.md
  • HTML宏、事件与DOM交互references/html_and_events.md
  • 路由(yew-router)模式references/routing.md
  • 上下文与状态管理references/contexts_and_state.md
  • 服务端渲染与Hydrationreferences/ssr.md