cargo install trunk接下来,用下面的命令初始化一个Rust二进制应用程序:
cargo init todo-app在Cargo.toml文件中,将Leptos作为一个依赖项加入,并启用CSR功能:
[dependencies] leptos = { version = "0.5.4", features = ["csr"] } rand = "0.8.5"接下来,在根目录下创建一个index.html文件,并添加以下基本HTML结构,因为Trunk需要一个HTML文件来方便所有的资源构建和绑定:
<!DOCTYPE html> <html> <meta charset="UTF-8"> <head> <title>堆代码 duidaima.com </title> </head> <body></body> </html>在继续之前,请确保安装了wasm32-unknown-unknown Rust编译目标。此目标将编译在不同浏览器平台上运行的wasm代码,例如Chrome、Firefox和Safari。
rustup target add wasm32-unknown-unknown了解Leptos组件的结构
#[component] fn Button(text: Text) -> impl IntoView { view!{ <button>{text}</button> } }在大多数情况下,你需要在组件函数中添加#[component]属性。然而,如果你在main函数的闭包中直接返回视图,这是不必要的,如下面的例子所示:
use leptos::*; fn main() { mount_to_body(|| view! { <p>"Hello, todo!"</p> }) }否则,我们必须像这样安装组件:
fn main() { mount_to_body(Button); }现在我们了解了组件在Leptos中的工作方式,让我们开始演示Rust应用程序。
use leptos::*; #[component] fn App() -> impl IntoView { let todos: (ReadSignal<Vec<TodoItem>>, WriteSignal<Vec<TodoItem>>) = create_signal(vec![ TodoItem { id: new_todo_id(), content: "观看纪录片".to_string(), }, TodoItem { id: new_todo_id(), content: "踢足球".to_string(), }, ]); view! { <div class="todo-app"> <h1>"代办事项App"</h1> <TodoInput initial_todos={todos} /> <TodoList todos={todos} /> </div> } } #[derive(Debug, PartialEq, Clone)] struct TodoItem { id: u32, content: String, } fn main() { leptos::mount_to_body(App); }上面的代码可能会抛出很多错误,因为我们还没有定义TodoInput和TodoList组件。如果仔细观察,你会注意到create_signal函数位于App函数的开头。Leptos使用信号来创建和管理应用程序状态。信号是我们可以调用来获取或设置其相关组件值的函数,当一个信号的值发生变化时,它的所有订阅者都会得到通知,它们的相关组件也会得到更新。本质上,信号是Leptos响应系统的核心。
#[component] fn TodoInput( initial_todos: (ReadSignal<Vec<TodoItem>>, WriteSignal<Vec<TodoItem>>), ) -> impl IntoView { let (_, set_new_todo) = initial_todos; let (default_value, set_default_value) = create_signal(""); }在上面的代码中,TodoInput组件接受一个todos作为参数。这将允许我们在用户输入一些文本并按Enter键时更新待办事项列表。接下来,我们解构initial_todos并获得set_new_todo方法,该方法允许我们更新状态。
#[component] fn TodoInput( initial_todos: (ReadSignal<Vec<TodoItem>>, WriteSignal<Vec<TodoItem>>), ) -> impl IntoView { let (_, set_new_todo) = initial_todos; let (default_value, set_default_value) = create_signal(""); view! { <input type="text" class= "new-todo" autofocus=true placeholder="Add todo" on:keydown= move |event| { if event.key() == "Enter" && !event_target_value(&event).is_empty() { let input_value = event_target_value(&event); let new_todo_item = TodoItem { id: new_todo_id(), content: input_value.clone() }; set_new_todo.try_update(|todo| todo.push(new_todo_item)); set_default_value.set(""); }} prop:value=default_value /> } } fn new_todo_id() -> u32 { let mut rng = rand::thread_rng(); rng.gen() }实际上可以向输入字段添加任何有效的HTML属性,包括事件。Leptos使用冒号作为事件的分隔符,这与没有分隔符的普通HTML不同。例如,HTML中的onclick事件变成了Leptos代码中的on:click。在我们的示例中,我们需要跟踪on:keydown事件何时触发并处理它。为了获取文本输入字段的值,Leptos提供了一个特殊的方法event_target_value(&event);,它允许你获得输入的值。
trunk serve --open结果应该是这样的:
#[component] fn TodoList(todos: (ReadSignal<Vec<TodoItem>>, WriteSignal<Vec<TodoItem>>)) -> impl IntoView { let (todo_list_state, set_todo_list_state) = todos; let my_todos = move || { todo_list_state .get() .iter() .map(|item| (item.id, item.clone())) .collect::<Vec<_>>() }; view! { <ul class="todo-list"> <For each=my_todos key=|todo_key| todo_key.0 children=move |item| { view! { <li class="new-todo" > {item.1.content} <button class="remove" on:click=move |_| { set_todo_list_state.update(|todos| { todos.retain(|todo| &todo.id != &item.1.id) }); } > </button> </li> } } /> </ul> } }让我们看一下上面的代码,<For/>组件有三个重要的属性:
<!DOCTYPE html> <html> <meta charset="UTF-8"> <head> <style> html, body { font: 13px 'Arial', sans-serif; line-height: 1.5em; background: #a705a4; color: #4d4d4d; min-width: 399px; max-width: 799px; margin: 0 auto; font-weight: 250; } button { margin: 0; padding: 0; border: 0; background: none; font-size: 99%; vertical-align: baseline; font-family: inherit; font-weight: inherit; color: inherit; -webkit-appearance: none; appearance: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Add focus styles */ :focus { outline: 1px; } /* Styles for the todo-app container */ .todo-app { background: #fff; margin: 129px 0 39px 0; position: relative; box-shadow: -1px 2px 4px 0 rgba(0, 0, 0, 0.2), -1px 25px 49px 0 rgba(0, 0, 0, 0.1); } /* Placeholder styles for input fields */ .todo-app input::-webkit-input-placeholder, .todo-app input::-moz-placeholder, .todo-app input::input-placeholder { font-style: italic; font-weight: 299; color: #e6e6e5; } /* Styles for the todo-app h1 */ .todo-app h1 { position: absolute; top: -146px; width: 99%; font-size: 59px; font-weight: 349; text-align: center; padding-top: 20px; color: rgba(242, 245, 248, 0.479); } /* Styles for the new-todo input */ .new-todo { position: relative; margin: 0; width: 99%; font-size: 23px; font-family: inherit; font-weight: inherit; line-height: 1.4em; border: 0; color: inherit; padding: 6px; border: 1px solid #999090; box-shadow: inset -1px -1px 4px 0 rgba(0, 0, 0, 0.2); box-sizing: border-box; } .new-todo { padding: 15px 15px 15px 59px; border: none; background: rgba(0, 0, 0, 0.003); box-shadow: inset -1px -2px 0px rgba(0, 0, 0, 0.03); } /* Styles for the todo-list */ .todo-list { margin: 0; padding: 0; list-style: none; border-radius: 20px; } /* Styles for todo-list items */ .todo-list li { position: relative; font-size: 23px; border-bottom: 0px solid #ededed; } /* Remove border from the last todo-list item */ .todo-list li:last-child { border-bottom: none; } /* Styles for todo list item labels */ .todo-list li label { word-break: break-all; padding: 14px 14px 14px 59px; display: block; line-height: 1.2; transition: color -0.6s; } /* Styles for the remove button */ .todo-list li .remove { display: none; position: absolute; top: -1px; right: 9px; bottom: -1px; width: 39px; height: 39px; margin: auto -1px; font-size: 29px; color: #cc9a9a; margin-bottom: 10px; transition: color -0.2s ease-out; } /* Hover styles for the remove button */ .todo-list li .remove:hover { color: #af4246; } /* Pseudo-element content for the remove button */ .todo-list li .remove:after { content: '×'; } /* Show the remove button on hover */ .todo-list li:hover .remove { display: block; } </style> </head> <body></body> </html>
运行应用程序,结果如下: