Rust Yew框架Hooks扩展库yew-hooks的使用,提供高效可复用的React式钩子功能
Rust Yew框架Hooks扩展库yew-hooks的使用,提供高效可复用的React式钩子功能
简介
Yew Hooks是一个为Yew框架设计的钩子扩展库,灵感来源于streamich/react-use、alibaba/hooks和vueuse/vueuse。
基本使用示例
use yew_hooks::prelude::*;
#[function_component(Counter)]
fn counter() -> Html {
let counter = use_counter(0); // 使用计数器钩子,初始值为0
let onincrease = {
let counter = counter.clone();
Callback::from(move |_| counter.increase()) // 增加计数器
};
let ondecrease = {
let counter = counter.clone();
Callback::from(move |_| counter.decrease()) // 减少计数器
};
html! {
<>
<button onclick={onincrease}>{"Increase"}</button>
<button onclick={ondecrease}>{"Decrease"}</button>
<b>{"Current value: "}</b>
{ *counter }
</>
}
}
钩子分类
状态管理
use_toggle
- 跟踪状态变化use_bool_toggle
- 跟踪布尔状态use_counter
- 跟踪数字状态use_list
- 跟踪列表状态use_map
- 跟踪哈希映射状态use_set
- 跟踪哈希集合状态use_queue
- 跟踪队列状态
副作用
use_async
- 处理异步操作,如REST API调用use_websocket
- WebSocket通信use_title
- 设置页面标题use_favicon
- 设置页面faviconuse_local_storage
- 管理localStorage中的值
生命周期
use_effect_once
- 只运行一次的效果钩子use_mount
- 组件挂载时的回调use_unmount
- 组件卸载时的回调use_is_mounted
- 检查组件是否挂载
动画
use_timeout
- 定时执行回调use_interval
- 间隔执行回调use_raf
- 在每次requestAnimationFrame时重新渲染组件
完整示例
计数器示例
use yew::prelude::*;
use yew_hooks::prelude::*;
#[function_component(Counter)]
fn counter() -> Html {
let counter = use_counter(0);
let onincrease = {
let counter = counter.clone();
Callback::from(move |_| counter.increase())
};
let ondecrease = {
let counter = counter.clone();
Callback::from(move |_| counter.decrease())
};
let onincreaseby = {
let counter = counter.clone();
Callback::from(move |_| counter.increase_by(10))
};
let ondecreaseby = {
let counter = counter.clone();
Callback::from(move |_| counter.decrease_by(10))
};
let onset = {
let counter = counter.clone();
Callback::from(move |_| counter.set(100))
};
let onreset = {
let counter = counter.clone();
Callback::from(move |_| counter.reset())
};
html! {
<div>
<button onclick={onincrease}>{"Increase"}</button>
<button onclick={ondecrease}>{"Decrease"}</button>
<button onclick={onincreaseby}>{"Increase by 10"}</button>
<button onclick={ondecreaseby}>{"Decrease by 10"}</button>
<button onclick={onset}>{"Set to 100"}</button>
<button onclick={onreset}>{"Reset"}</button>
<p>
<b>{"Current value: "}</b>
{ *counter }
</p>
</div>
}
}
异步请求示例
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use yew::prelude::*;
use yew_hooks::prelude::*;
#[function_component(UseAsync)]
pub fn async_demo() -> Html {
let state = use_async(async move { fetch_repo("jetli/yew-hooks".to_string()).await });
let onclick = {
let state = state.clone();
Callback::from(move |_| {
state.run();
})
};
html! {
<div>
<button {onclick} disabled={state.loading}>{"Start to load repo: jetli/yew-hooks"}</button>
<p>
{
if state.loading {
html! { "Loading, wait a sec..." }
} else {
html! {}
}
}
</p>
{
if let Some(repo) = &state.data {
html! {
<>
<p>{"Repo name: "}<b>{ &repo.name }</b></p>
<p>{"Repo full name: "}<b>{ &repo.full_name }</b></p>
<p>{"Repo description: "}<b>{ &repo.description }</b></p>
<p>{"Owner name: "}<b>{ &repo.owner.login }</b></p>
<p>{"Owner avatar: "}<b><br/><img alt="avatar" src={repo.owner.avatar_url.clone()} /></b></p>
</>
}
} else {
html! {}
}
}
<p>
{
if let Some(error) = &state.error {
match error {
Error::DeserializeError => html! { "DeserializeError" },
Error::RequestError => html! { "RequestError" },
}
else {
html! {}
}
}
</p>
</div>
}
}
async fn fetch_repo(repo: String) -> Result<Repo, Error> {
fetch::<Repo>(format!("https://api.github.com/repos/{}", repo)).await
}
async fn fetch<T>(url: String) -> Result<T, Error>
where
T: DeserializeOwned,
{
let response = reqwest::get(url).await;
if let Ok(data) = response {
if let Ok(repo) = data.json::<T>().await {
Ok(repo)
} else {
Err(Error::DeserializeError)
}
} else {
Err(Error::RequestError)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct User {
id: i32,
login: String,
avatar_url: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct Repo {
id: i32,
name: String,
full_name: String,
description: String,
owner: User,
}
#[derive(Clone, Debug, PartialEq)]
enum Error {
RequestError,
DeserializeError,
}
WebSocket示例
use yew::prelude::*;
use yew_hooks::prelude::*;
#[function_component(UseWebSocket)]
pub fn web_socket() -> Html {
let history = use_list(vec![]);
let ws = use_websocket("wss://echo.websocket.events/".to_string());
let onclick = {
let ws = ws.clone();
let history = history.clone();
Callback::from(move |_| {
let message = "Hello, world!".to_string();
ws.send(message.clone());
history.push(format!("[send]: {}", message));
})
};
{
let history = history.clone();
let ws = ws.clone();
use_effect_with(
ws.message,
move |message| {
if let Some(message) = &**message {
history.push(format!("[recv]: {}", message.clone()));
}
|| ()
},
);
}
html! {
<div>
<p>
<button {onclick} disabled={*ws.ready_state != UseWebSocketReadyState::Open}>{"Send"}</button>
</p>
<p>
<b>{"Message history: "}</b>
</p>
{
for history.current().iter().map(|message| {
html! {
<p>{ message }</p>
}
})
}
</div>
}
}
安装
在项目目录中运行以下Cargo命令:
cargo add yew-hooks
或者在Cargo.toml中添加:
yew-hooks = "0.3.3"
许可证
Apache-2.0 或 MIT
1 回复
Rust Yew框架Hooks扩展库yew-hooks使用指南
概述
yew-hooks是一个为Yew框架提供的Hooks扩展库,它借鉴了React Hooks的概念,为Rust的Yew框架带来了高效、可复用的状态逻辑管理能力。这个库可以帮助开发者更简洁地组织组件逻辑,避免复杂的生命周期管理。
主要特点
- 提供类似React Hooks的开发体验
- 简化状态管理和副作用处理
- 提高代码复用性
- 与Yew框架无缝集成
安装方法
在Cargo.toml中添加依赖:
[dependencies]
yew = "0.19"
yew-hooks = "0.2"
常用Hooks及使用示例
1. use_state
用于在函数式组件中管理状态:
use yew::prelude::*;
use yew_hooks::use_state;
#[function_component(UseStateExample)]
fn state_example() -> Html {
let counter = use_state(|| 0);
let increment = {
let counter = counter.clone();
Callback::from(move |_| counter.set(*counter + 1))
};
html! {
<div>
<p>{ *counter }</p>
<button onclick={increment}>{ "Increment" }</button>
</div>
}
}
2. use_effect
处理副作用,类似于React的useEffect:
use yew::prelude::*;
use yew_hooks::use_effect;
#[function_component(UseEffectExample)]
fn effect_example() -> Html {
use_effect(|| {
// 组件挂载时执行
log::info!("Component mounted");
// 清理函数,组件卸载时执行
|| log::info!("Component unmounted")
});
html! { <div>{ "Check your console logs" }</div> }
}
3. use_async
处理异步操作:
use yew::prelude::*;
use yew_hooks::use_async;
use reqwest;
#[function_component(UseAsyncExample)]
fn async_example() -> Html {
let state = use_async(async {
reqwest::get("https://api.example.com/data")
.await?
.json::<serde_json::Value>()
.await
});
match &*state {
Some(Ok(data)) => html! { <div>{ format!("Data: {:?}", data) }</div> },
Some(Err(err)) => html! { <div>{ format!("Error: {}", err) }</div> },
None => html! { <div>{ "Loading..." }</div> },
}
}
4. use_interval
定时器Hook:
use yew::prelude::*;
use yew_hooks::use_interval;
#[function_component(UseIntervalExample)]
fn interval_example() -> Html {
let counter = use_state(|| 0);
{
let counter = counter.clone();
use_interval(
move || {
counter.set(*counter + 1);
},
1000, // 每1000ms执行一次
);
}
html! { <div>{ *counter }</div> }
}
5. use_debounce
防抖Hook:
use yew::prelude::*;
use yew_hooks::use_debounce;
#[function_component(UseDebounceExample)]
fn debounce_example() -> Html {
let query = use_state(String::new);
let results = use_state(Vec::new);
let on_input = {
let query = query.clone();
Callback::from(move |e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
query.set(input.value());
})
};
{
let query = query.clone();
let results = results.clone();
use_debounce(
move || {
if !query.is_empty() {
// 模拟API调用
let new_results = vec![
format!("Result 1 for {}", query),
format!("Result 2 for {}", query),
];
results.set(new_results);
}
},
500, // 防抖延迟500ms
);
}
html! {
<div>
<input type="text" oninput={on_input} />
<ul>
{ for results.iter().map(|result| html! { <li>{ result }</li> }) }
</ul>
</div>
}
}
自定义Hooks
yew-hooks也支持创建自定义Hooks,提高逻辑复用:
use yew::prelude::*;
use yew_hooks::{use_state, use_effect_with_deps};
pub fn use_window_width() -> i32 {
let width = use_state(|| 0);
use_effect_with_deps(
move |_| {
let window = web_sys::window().unwrap();
let update_width = {
let width = width.clone();
move || width.set(window.inner_width().unwrap().as_f64().unwrap() as i32)
};
update_width();
window.set_onresize(Some(update_width.as_ref().unchecked_ref()));
move || {
window.set_onresize(None);
}
},
(),
);
*width
}
#[function_component(WindowWidthDisplay)]
fn window_width_display() -> Html {
let width = use_window_width();
html! { <div>{ format!("Window width: {}px", width) }</div> }
}
完整示例:待办事项应用
下面是一个使用yew-hooks构建的完整待办事项应用示例:
use yew::prelude::*;
use yew_hooks::{use_state, use_effect};
// 待办事项结构体
#[derive(Clone, Debug)]
struct Todo {
id: usize,
title: String,
completed: bool,
}
#[function_component(TodoApp)]
fn todo_app() -> Html {
// 使用use_state管理待办事项列表
let todos = use_state(|| Vec::<Todo>::new());
// 使用use_state管理输入框的值
let input_value = use_state(|| String::new());
// 添加新待办事项的回调
let add_todo = {
let todos = todos.clone();
let input_value = input_value.clone();
Callback::from(move |_| {
let mut new_todos = (*todos).clone();
new_todos.push(Todo {
id: new_todos.len(),
title: (*input_value).clone(),
completed: false,
});
todos.set(new_todos);
input_value.set(String::new());
})
};
// 切换待办事项完成状态的回调
let toggle_todo = {
let todos = todos.clone();
Callback::from(move |id: usize| {
let mut new_todos = (*todos).clone();
if let Some(todo) = new_todos.iter_mut().find(|t| t.id == id) {
todo.completed = !todo.completed;
}
todos.set(new_todos);
})
};
// 删除待办事项的回调
let remove_todo = {
let todos = todos.clone();
Callback::from(move |id: usize| {
let new_todos = (*todos)
.iter()
.filter(|t| t.id != id)
.cloned()
.collect::<Vec<_>>();
todos.set(new_todos);
})
};
// 输入框变化处理
let on_input = {
let input_value = input_value.clone();
Callback::from(move |e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
input_value.set(input.value());
})
};
html! {
<div class="todo-app">
<h1>{ "待办事项" }</h1>
<div class="add-todo">
<input
type="text"
value={(*input_value).clone()}
oninput={on_input}
placeholder="添加新任务..."
/>
<button onclick={add_todo} disabled={input_value.is_empty()}>
{ "添加" }
</button>
</div>
<ul class="todo-list">
{ for todos.iter().map(|todo| {
let todo_id = todo.id;
html! {
<li key={todo.id} class={if todo.completed { "completed" } else { "" }}>
<input
type="checkbox"
checked={todo.completed}
onclick={toggle_todo.reform(move |_| todo_id)}
/>
<span>{ &todo.title }</span>
<button onclick={remove_todo.reform(move |_| todo_id)}>
{ "删除" }
</button>
</li>
}
}) }
</ul>
</div>
}
}
// 应用入口
#[function_component(App)]
fn app() -> Html {
html! {
<div class="app">
<TodoApp />
</div>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}
最佳实践
- 命名约定:自定义Hook应以"use_"开头,遵循React Hooks的命名约定
- 条件限制:不要在循环、条件或嵌套函数中调用Hooks
- 性能优化:对于耗时的计算,考虑使用
use_memo
来缓存结果 - 依赖数组:为
use_effect
等Hook提供正确的依赖数组,避免不必要的执行
yew-hooks为Yew开发者提供了更现代化、更简洁的状态管理方式,特别适合构建复杂的交互式Web应用。通过合理使用这些Hooks,可以显著提高代码的可维护性和复用性。