Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

<ActionForm/> 组件

<ActionForm/>

<ActionForm/> 是一个专门的 <Form/>,它接收一个服务器动作(server action),并在表单提交时自动调度它。这允许你直接从 <form> 调用服务器函数,甚至在没有 JS/WASM 的情况下也可以。

<ActionForm/> is a specialized <Form/> that takes a server action, and automatically dispatches it on form submission. This allows you to call a server function directly from a <form>, even without JS/WASM.

过程非常简单:

The process is simple:

  1. 使用 #[server] 定义一个服务器函数(参见 服务器函数)。

  2. Define a server function using the #[server] macro (see Server Functions.)

  3. 使用 ServerAction::new() 创建一个动作,指定你定义的服务器函数类型。

  4. Create an action using ServerAction::new(), specifying the type of the server function you’ve defined.

  5. 创建一个 <ActionForm/>,在 action 属性中提供该服务器动作。

  6. Create an <ActionForm/>, providing the server action in the action prop.

  7. 将命名参数作为具有相同名称的表单字段传递给服务器函数。

  8. Pass the named arguments to the server function as form fields with the same names.

注意: <ActionForm/> 仅适用于服务器函数默认的 URL 编码 POST 方式,以确保作为 HTML 表单时的平稳退化/正确行为。

Note: <ActionForm/> only works with the default URL-encoded POST encoding for server functions, to ensure graceful degradation/correct behavior as an HTML form.

#[server]
pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
    todo!()
}

#[component]
fn AddTodo() -> impl IntoView {
    let add_todo = ServerAction::<AddTodo>::new();
    // 保存从服务器返回的最新值
    // holds the latest *returned* value from the server
    let value = add_todo.value();
    // 检查服务器是否返回了错误
    // check if the server has returned an error
    let has_error = move || value.with(|val| matches!(val, Some(Err(_))));

    view! {
        <ActionForm action=add_todo>
            <label>
                "Add a Todo"
                // `title` 与 `add_todo` 的 `title` 参数匹配
                // `title` matches the `title` argument to `add_todo`
                <input type="text" name="title"/>
            </label>
            <input type="submit" value="Add"/>
        </ActionForm>
    }
}

就是这么简单。在有 JS/WASM 的情况下,你的表单将在不重新加载页面的情况下提交,并将其最近的提交存储在动作的 .input() 信号中,其挂起状态存储在 .pending() 中,依此类推。(如果需要,请查看 Action 文档进行复习。)在没有 JS/WASM 的情况下,你的表单将通过页面重新加载来提交。如果你调用 redirect 函数(来自 leptos_axumleptos_actix),它将重定向到正确的页面。默认情况下,它将重定向回你当前所在的页面。HTML、HTTP 和同构渲染(isomorphic rendering)的力量意味着即使没有 JS/WASM,你的 <ActionForm/> 也能正常工作。

It’s really that easy. With JS/WASM, your form will submit without a page reload, storing its most recent submission in the .input() signal of the action, its pending status in .pending(), and so on. (See the Action docs for a refresher, if you need.) Without JS/WASM, your form will submit with a page reload. If you call a redirect function (from leptos_axum or leptos_actix) it will redirect to the correct page. By default, it will redirect back to the page you’re currently on. The power of HTML, HTTP, and isomorphic rendering mean that your <ActionForm/> simply works, even with no JS/WASM.

客户端验证

Client-Side Validation

因为 <ActionForm/> 只是一个 <form>,它会触发 submit 事件。你可以在 on:submit:capture 处理器中使用 HTML 验证或你自己的客户端验证逻辑。只需调用 ev.prevent_default() 即可阻止提交。

Because the <ActionForm/> is just a <form>, it fires a submit event. You can use either HTML validation, or your own client-side validation logic in an on:submit:capture handler. Just call ev.prevent_default() to prevent submission.

FromFormData trait 在这里很有用,可以尝试从提交的表单中解析服务器函数的数据类型。

The FromFormData trait can be helpful here, for attempting to parse your server function’s data type from the submitted form.

let on_submit = move |ev| {
	let data = AddTodo::from_event(&ev);
	// 验证的一个简单示例:如果待办事项是 "nope!",则拒绝它
	// silly example of validation: if the todo is "nope!", nope it
	if data.is_err() || data.unwrap().title == "nope!" {
		// ev.prevent_default() 将阻止表单提交
		// ev.prevent_default() will prevent form submission
		ev.prevent_default();
	}
}

// ... 将 `submit` 处理器添加到 `ActionForm`
// ... add the `submit` handler to an `ActionForm`

<ActionForm on:submit:capture=on_submit /* ... */>

:::tip{title="注意"} 请注意使用的是 on:submit:capture 而不是 on:submit。这会添加一个在浏览器事件处理的“捕获”(capture)阶段触发的事件监听器,而不是在“冒泡”(bubble)阶段触发,这意味着你的事件处理器将在 ActionForm 内置的 submit 处理器之前运行。有关更多信息,请查看此 issue

:::

注意

Note the use of on:submit:capture rather than on:submit. This adds an event listener that will fire during the browser’s “capture” phase of event handling, rather than during the “bubble” phase, which means that your event handler will run before the built-in submit handler of the ActionForm. For more information, check out this issue.

复杂输入

Complex Inputs

如果服务器函数参数是带有嵌套可序列化字段的结构体,则应使用 serde_qs 的索引表示法。

Server function arguments that are structs with nested serializable fields should make use of indexing notation of serde_qs.

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct Settings {
    display_name: String,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct HeftyData {
    first_name: String,
    last_name: String,
    settings: Settings,
}

#[component]
fn ComplexInput() -> impl IntoView {
    let submit = ServerAction::<VeryImportantFn>::new();

    view! {
      <ActionForm action=submit>
        <input type="text" name="hefty_arg[first_name]" value="leptos"/>
        <input
          type="text"
          name="hefty_arg[last_name]"
          value="closures-everywhere"
        />
        <input
          type="text"
          name="hefty_arg[settings][display_name]"
          value="my alias"
        />
        <input type="submit"/>
      </ActionForm>
    }
}

#[server]
async fn very_important_fn(hefty_arg: HeftyData) -> Result<(), ServerFnError> {
    assert_eq!(hefty_arg.first_name.as_str(), "leptos");
    assert_eq!(hefty_arg.last_name.as_str(), "closures-everywhere");
    assert_eq!(hefty_arg.settings.display_name.as_str(), "my alias");
    Ok(())
}