用户输入
界面通常需要提供一种输入数据的方式:例如文本、数字、复选框等。在Dioxus中,有有两种方式可以处理用户输入。
控制输入(Controlled Inputs)
使用控制输入时,你直接负责输入的状态。这给你提供了很多灵活性,并使得保持同步变得容易。例如,以下是如何创建一个控制文本输入:
pub fn App() -> Element {
let mut name = use_signal(|| "bob".to_string());
rsx! {
input {
// 我们告诉组件要渲染什么
value: "{name}",
// 以及当值改变时应该做什么
oninput: move |event| name.set(event.value())
}
}
}
注意这种灵活性——你可以:
- 同时在另一个元素中显示相同的内容,它们将保持同步
- 每次修改时转换输入(例如,确保它是大写的)
- 每次输入改变时验证输入
- 当输入改变时发生自定义逻辑(例如,自动补全的网络请求)
- 以编程方式改变值(例如,一个“随机化”按钮,用无意义的内容填充输入)
非控制输入(Uncontrolled Inputs)
作为控制输入的替代方案,你可以简单地让平台跟踪输入值。如果我们不告诉HTML输入应该有什么内容,它仍然可以编辑(这在浏览器中是内置的)。这种方法可能性能更好,但灵活性较低。例如,保持输入与另一个元素同步更困难。
由于你不一定有非控制输入的当前值在状态中,你可以通过监听oninput事件(类似于控制组件)来访问它,或者,如果输入是表单的一部分,你可以在表单事件中访问表单数据(例如oninput或onsubmit):
pub fn App() -> Element {
rsx! {
form { onsubmit: move |event| { log::info!("Submitted! {event:?}") },
input { name: "name" }
input { name: "age" }
input { name: "date" }
input { r#type: "submit" }
}
}
}
Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "date": "1966", "name": "Fred"} } }
处理文件
你可以通过使用类型为file的输入元素来插入文件选择器。这个元素支持multiple属性,让你可以一次选择多个文件。你可以通过添加directory属性来选择一个文件夹:Dioxus会将这个属性映射到浏览器特定的属性,因为还没有标准化的方式来允许选择一个目录。
type是Rust的关键字,所以在指定输入字段的类型时,你必须写成r#type:"file"。
提取选定的文件与你在JavaScript中通常使用的有所不同。
FormData事件包含一个files字段,其中包含有关上传文件的数据。这个字段包含一个FileEngine结构体,它让你可以获取用户选择的文件名。以下示例将选定文件的文件名保存到Vec中:
pub fn App() -> Element {
let mut filenames: Signal<Vec<String>> = use_signal(Vec::new);
rsx! {
input {
// 告诉输入选择一个文件
r#type: "file",
// 列出接受的扩展名
accept: ".txt,.rs",
// 选择多个文件
multiple: true,
onchange: move |evt| {
if let Some(file_engine) = &evt.files() {
let files = file_engine.files();
for file_name in files {
filenames.write().push(file_name);
}
}
}
}
}
}
如果你打算读取文件内容,你需要异步地做这件事,以保持UI的其余部分交互性。以下示例事件处理器在异步闭包中加载选定文件的内容:
onchange: move |evt| {
async move {
if let Some(file_engine) = evt.files() {
let files = file_engine.files();
for file_name in &files {
if let Some(file) = file_engine.read_file_to_string(file_name).await
{
files_uploaded.write().push(file);
}
}
}
}
}
最后,这个示例展示了如何通过设置directory属性为true来选择一个文件夹。
input {
r#type: "file",
// 通过设置directory属性选择一个文件夹
directory: true,
onchange: move |evt| {
if let Some(file_engine) = evt.files() {
let files = file_engine.files();
for file_name in files {
println!("{}", file_name);
}
}
}
}