Dioxus框架中用户输入(User Input)

433 阅读3分钟

用户输入

界面通常需要提供一种输入数据的方式:例如文本、数字、复选框等。在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事件(类似于控制组件)来访问它,或者,如果输入是表单的一部分,你可以在表单事件中访问表单数据(例如oninputonsubmit):

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);
            }
        }
    }
}

img