【React Router】初识路由(下)

Form

普通搜索框的表单提交

jsx 复制代码
<form id="search-form" role="search">
    <input
        id="q"
        className={searching ? "loading" : ""}
        aria-label="Search contacts"
        placeholder="Search"
        type="search"
        // URL 中有 `?q=`
        name="q"
    />
    <div
        id="search-spinner"
        hidden={!searching}
        aria-hidden
    />
    <div
        className="sr-only"
        aria-live="polite"
    ></div>
</form>

form 默认为 get 请求,将表单数据放入get 请求的URLSearchParams中。如果 method 设为 post,那么将放入 post 主体中。

Form 表单提交

将 form 改为 Form,使用客户端路由来提交此表单,并可以在现有的加载器中对数据进行操作。

比如:

jsx 复制代码
export async function loader({ request }) {
    const url = new URL(request.url);
    const q = url.searchParams.get("q");
    const contacts = await getContacts(q);
    return { contacts, q };
}

因为是 GET 请求而不是 POST 请求,所以 React Router 不会调用 action

提交 GET 表单与点击链接一样:只是 URL 发生了变化。

优化表单

  1. 如果在搜索后刷新页面,表单字段中应保留输入的值
    解决:设置 q 为默认值(q是我们自定义的表单某项的 key)
jsx 复制代码
const { contacts, q } = useLoaderData();//使用q
jsx 复制代码
<input
    id="q"
    className={searching ? "loading" : ""}
    aria-label="Search contacts"
    placeholder="Search"
    type="search"
    name="q"
    defaultValue={q}
/>
  1. 在搜索后点击返回,列表已不再过滤,表单字段需要清空输入的值
jsx 复制代码
useEffect(() => {
    document.getElementById("q").value = q;
}, [q]);

当然这里同时使用 useEffect, useState 也是可以的

  1. 在每次按键时进行过滤,而不是在表单明确提交时进行筛选

使用 React Router 自带的 useSubmit

jsx 复制代码
const submit = useSubmit();
jsx 复制代码
<input
    id="q"
    className={searching ? "loading" : ""}
    aria-label="Search contacts"
    placeholder="Search"
    type="search"
    name="q"
    defaultValue={q}
    onChange={(event) => {
        submit(event.currentTarget.form);
    }}
/>

现在,当输入时,表格就会自动提交。

currentTarget 是事件附加到的 DOM 节点, currentTarget.form 是输入的父表单节点。submit 函数将序列化并提交传递给它的任何表单。

  1. 由于每次按键都会提交表单,所以如果我们输入 "seba "字符,然后用退格键删除它们,历史堆栈中就会出现 7 个新条目😂(长按回退按钮可以查看)。通过将历史记录堆栈中的当前条目替换为下一页,而不是推入下一页,来避免这种情况。
jsx 复制代码
<input
    id="q"
    className={searching ? "loading" : ""}
    aria-label="Search contacts"
    placeholder="Search"
    type="search"
    name="q"
    defaultValue={q}
    onChange={(event) => {
        //修改:添加replace
        const isFirstSearch = q == null;
        submit(event.currentTarget.form, {
            replace: !isFirstSearch,
        });
    }}
/>

非导航的数据改变

之前的所有的更改数据都是使用表单导航,在历史堆栈中创建新条目。

如果我们不希望引起导航更改数据,可以使用useFetcher钩子函数。它允许我们与loadersactions进行通信,而不会导致导航。

jsx 复制代码
const fetcher = useFetcher();
jsx 复制代码
<fetcher.Form method="post">
    <button
        name="favorite"
        value={favorite ? "false" : "true"}
        aria-label={
            favorite
                ? "Remove from favorites"
                : "Add to favorites"
        }
    >
        {favorite ? "★" : "☆"}
    </button>
</fetcher.Form>

method="post" ,它就会调用action。由于没有提供 <fetcher.Form action="..."> 属性,它将提交到呈现表单的路由。

jsx 复制代码
export async function action({ request, params }) {
    let formData = await request.formData();
    return updateContact(params.contactId, {
        favorite: formData.get("favorite") === "true",
    });
}

然后在路由中配置 action 即可。

局部捕获错误异常

jsx 复制代码
export async function loader({ params }) {
    const contact = await getContact(params.contactId);
    if (!contact) {
        throw new Response("", {
            status: 404,
            statusText: "Not Found",
        });
    }
    return { contact };
}

这样操作完成之后,我们发现异常页是全局的,所以就需要在路由中配置,将子路由包裹在无路径路由中变成局部异常页。

jsx 复制代码
{
    path: '/',
    // 将<Root>设置为根路由element
    element: <Root/>,
    // 将<ErrorPage>设置为根路由上的errorElement
    errorElement: <ErrorPage/>,
    // 配置 loader
    loader: rootLoader,
    // 配置 action
    action: rootAction,
    // 子路由
    children: [
        // 添加无路径路由
        {
            errorElement: <ErrorPage />,
            children: [
                { index: true, element: <Index /> },
                {
                    path: 'contacts/:contactId',
                    element: <Contact/>,
                    loader: contactLoader,
                    action: contactAction,
                },
                {
                    path: "contacts/:contactId/edit",
                    element: <EditContact/>,
                    loader: contactLoader,
                    action: editAction
                },
                {
                    path: 'contacts/:contactId/destroy',
                    action: destroyAction,
                    errorElement: <div>Oops! There was an error.</div>,
                },
            ]
        }
    ]
},
相关推荐
昙鱼1 分钟前
springboot创建web项目
java·前端·spring boot·后端·spring·maven
天天进步20157 分钟前
Vue项目重构实践:如何构建可维护的企业级应用
前端·vue.js·重构
小华同学ai10 分钟前
vue-office:Star 4.2k,款支持多种Office文件预览的Vue组件库,一站式Office文件预览方案,真心不错
前端·javascript·vue.js·开源·github·office
APP 肖提莫11 分钟前
MyBatis-Plus分页拦截器,源码的重构(重构total总数的计算逻辑)
java·前端·算法
问道飞鱼22 分钟前
【前端知识】强大的js动画组件anime.js
开发语言·前端·javascript·anime.js
k093324 分钟前
vue中proxy代理配置(测试一)
前端·javascript·vue.js
傻小胖25 分钟前
React 脚手架使用指南
前端·react.js·前端框架
程序员海军38 分钟前
2024 Nuxt3 年度生态总结
前端·nuxt.js
m0_748256781 小时前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
web135085886351 小时前
前端node.js
前端·node.js·vim