<Form>
declare function Form(props: FormProps): React.ReactElement;
interface FormProps
extends React.FormHTMLAttributes<HTMLFormElement> {
method?: "get" | "post" | "put" | "patch" | "delete";
encType?:
| "application/x-www-form-urlencoded"
| "multipart/form-data"
| "text/plain";
action?: string;
onSubmit?: React.FormEventHandler<HTMLFormElement>;
fetcherKey?: string;
navigate?: boolean;
preventScrollReset?: boolean;
relative?: "route" | "path";
reloadDocument?: boolean;
replace?: boolean;
state?: any;
unstable_viewTransition?: boolean;
}
Form 组件是围绕普通 HTML form 的包装器,它模拟浏览器进行客户端路由和数据变异。它不是一个像你在 React 生态系统中习惯的那样的表单验证/状态管理库(为此,我们建议使用浏览器的内置 HTML 表单验证 和后端服务器上的数据验证)。
import { Form } from "react-router-dom";
function NewEvent() {
return (
<Form method="post" action="/events">
<input type="text" name="title" />
<input type="text" name="description" />
<button type="submit">Create</button>
</Form>
);
}
FormData
将不包含该字段的值。
所有这些都将触发对任何渲染的 useNavigation
钩子的状态更新,以便你可以在异步操作进行中构建挂起指示器和乐观 UI。
如果表单感觉不像导航,你可能需要 useFetcher
.
action
表单将提交到的 URL,就像 HTML form action 一样。唯一的区别是默认操作。对于 HTML 表单,它默认为完整 URL。对于 <Form>
,它默认为上下文中最近路由的相对 URL。
考虑以下路由和组件
function ProjectsLayout() {
return (
<>
<Form method="post" />
<Outlet />
</>
);
}
function ProjectsPage() {
return <Form method="post" />;
}
<DataBrowserRouter>
<Route
path="/projects"
element={<ProjectsLayout />}
action={ProjectsLayout.action}
>
<Route
path=":projectId"
element={<ProjectsPage />}
action={ProjectsPage.action}
/>
</Route>
</DataBrowserRouter>;
如果当前 URL 是 "/projects/123"
,则子路由 ProjectsPage
中的表单将具有你预期的默认操作:"/projects/123"
。在这种情况下,路由是最深匹配的路由,<Form>
和普通 HTML 表单的结果相同。
但是 ProjectsLayout
中的表单将指向 "/projects"
,而不是完整 URL。换句话说,它指向渲染表单的路由的 URL 的匹配段。
这有助于可移植性以及表单与其操作处理程序的共置,如果你在路由模块中添加了一些约定。
如果你需要发布到不同的路由,则添加一个 action 属性
<Form action="/projects/new" method="post" />
另请参阅
useResolvedPath
文档中的 Splat 路径 部分,了解 future.v7_relativeSplatPath
未来标志对 splat 路由中相对 useNavigate()
行为的影响。
method
这决定了要使用的 HTTP 动词。与普通 HTML form method 相同,除了它还支持 "put"、"patch" 和 "delete",以及 "get" 和 "post"。默认值为 "get"。
默认方法是 "get"。GET 提交不会调用操作。GET 提交与普通导航(用户点击链接)相同,除了用户可以从表单中提供要转到 URL 的搜索参数。
<Form method="get" action="/products">
<input
aria-label="search products"
type="text"
name="q"
/>
<button type="submit">Search</button>
</Form>
假设用户输入 "running shoes" 并提交表单。React Router 模拟浏览器,并将表单序列化为 URLSearchParams,然后将用户导航到 "/products?q=running+shoes"
。这就像你作为开发人员渲染了 <Link to="/products?q=running+shoes">
一样,但你让用户动态地提供查询字符串。
你的路由加载器可以通过从 request.url
创建一个新的 URL
来最方便地访问这些值,然后加载数据。
<Route
path="/products"
loader={async ({ request }) => {
let url = new URL(request.url);
let searchTerm = url.searchParams.get("q");
return fakeSearchProducts(searchTerm);
}}
/>
所有其他方法都是 "mutation 提交",这意味着你打算使用 POST、PUT、PATCH 或 DELETE 更改数据。请注意,普通 HTML 表单仅支持 "post" 和 "get",我们也倾向于坚持使用这两个方法。
当用户提交表单时,React Router 将匹配 action
到应用程序的路由,并使用序列化的 FormData
调用 <Route action>
。当操作完成后,页面上的所有加载器数据将自动重新验证,以使你的 UI 与你的数据保持同步。
该方法将在调用的路由操作中的 request.method
上可用。你可以使用它来指示你的数据抽象提交的意图。
<Route
path="/projects/:id"
element={<Project />}
loader={async ({ params }) => {
return fakeLoadProject(params.id);
}}
action={async ({ request, params }) => {
switch (request.method) {
case "PUT": {
let formData = await request.formData();
let name = formData.get("projectName");
return fakeUpdateProject(name);
}
case "DELETE": {
return fakeDeleteProject(params.id);
}
default: {
throw new Response("", { status: 405 });
}
}
}}
/>;
function Project() {
let project = useLoaderData();
return (
<>
<Form method="put">
<input
type="text"
name="projectName"
defaultValue={project.name}
/>
<button type="submit">Update Project</button>
</Form>
<Form method="delete">
<button type="submit">Delete Project</button>
</Form>
</>
);
}
如你所见,两种表单都提交到相同的路由,但你可以使用 request.method
来分支你打算做什么。操作完成后,loader
将重新验证,UI 将自动与新数据同步。
navigate
你可以通过指定 <Form navigate={false}>
来告诉表单跳过导航并使用内部的 fetcher。这本质上是 useFetcher()
+ <fetcher.Form>
的简写,你不在乎结果数据,只想要启动提交并通过 useFetchers()
访问挂起状态。
fetcherKey
当使用非导航 Form
时,你也可以选择通过 <Form navigate={false} fetcherKey="my-key">
指定你自己的 fetcher 密钥。
replace
指示表单替换历史记录堆栈中的当前条目,而不是推送新条目。
<Form replace />
默认行为取决于表单行为
method=get
表单默认为 false
formAction
和 action
行为action
抛出异常,则它将默认为 false
action
重定向到当前位置,则它将默认为 true
action
重定向到其他位置,则它将默认为 false
formAction
是当前位置,则它将默认为 true
false
我们发现对于 get
,你通常希望用户能够点击 "back" 来查看之前的搜索结果/过滤器等。但对于其他方法,默认值为 true
以避免 "你确定要重新提交表单吗?" 提示。请注意,即使 replace={false}
,React Router 也不会 在点击后退按钮且方法为 post、put、patch 或 delete 时重新提交表单。
换句话说,这实际上只对 GET 提交有用,并且你希望避免后退按钮显示之前的结果。
relative
默认情况下,路径相对于路由层次结构,因此 ..
将向上移动一个 Route
层级。偶尔,你可能会发现你拥有不合理嵌套的匹配 URL 模式,并且你更喜欢使用相对路径路由。你可以使用 <Form to="../some/where" relative="path">
选择此行为。
reloadDocument
指示表单跳过 React Router 并使用浏览器的内置行为提交表单。
<Form reloadDocument />
建议使用 <form>
,这样你就可以获得默认和相对 action
的好处,但除此之外,它与普通 HTML 表单相同。
如果没有像 Remix 这样的框架,或者你自己的服务器处理对路由的发布,这将不是很有用。
另请参阅
state
state
属性可用于为新位置设置一个有状态的值,该值存储在 历史记录状态 中。随后可以通过 useLocation()
访问此值。
<Form
method="post"
action="new-path"
state={{ some: "value" }}
/>
你可以在 "new-path" 路由上访问此状态值
let { state } = useLocation();
preventScrollReset
如果您正在使用 <ScrollRestoration>
,这可以防止表单操作重定向到新位置时滚动位置重置到窗口顶部。
<Form method="post" preventScrollReset={true} />
另请参阅:<Link preventScrollReset>
unstable_viewTransition
unstable_viewTransition
属性通过将最终状态更新包装在 document.startViewTransition()
中,为此次导航启用 视图转换。如果您需要为此视图转换应用特定样式,您还需要利用 unstable_useViewTransitionState()
。
待办事项:更多示例
GET 提交的一个常见用例是过滤大型列表,例如电子商务和旅行预订网站。
function FilterForm() {
return (
<Form method="get" action="/slc/hotels">
<select name="sort">
<option value="price">Price</option>
<option value="stars">Stars</option>
<option value="distance">Distance</option>
</select>
<fieldset>
<legend>Star Rating</legend>
<label>
<input type="radio" name="stars" value="5" />{" "}
★★★★★
</label>
<label>
<input type="radio" name="stars" value="4" /> ★★★★
</label>
<label>
<input type="radio" name="stars" value="3" /> ★★★
</label>
<label>
<input type="radio" name="stars" value="2" /> ★★
</label>
<label>
<input type="radio" name="stars" value="1" /> ★
</label>
</fieldset>
<fieldset>
<legend>Amenities</legend>
<label>
<input
type="checkbox"
name="amenities"
value="pool"
/>{" "}
Pool
</label>
<label>
<input
type="checkbox"
name="amenities"
value="exercise"
/>{" "}
Exercise Room
</label>
</fieldset>
<button type="submit">Search</button>
</Form>
);
}
当用户提交此表单时,表单将被序列化到 URL 中,类似于以下内容,具体取决于用户的选择。
/slc/hotels?sort=price&stars=4&amenities=pool&amenities=exercise
您可以从 request.url
中访问这些值。
<Route
path="/:city/hotels"
loader={async ({ request }) => {
let url = new URL(request.url);
let sort = url.searchParams.get("sort");
let stars = url.searchParams.get("stars");
let amenities = url.searchParams.getAll("amenities");
return fakeGetHotels({ sort, stars, amenities });
}}
/>
另请参阅