8. 编辑&更新数据

嗯,没什么好说的,主要是对action的应用,提两个注意点

action函数的返回可用于重定向,用react自带的redirect方法,执行完自动跳转到原地址后+拼接地址。

export async function action({ request, params }) {
  const formData = await request.formData();
  const updates = Object.fromEntries(formData);
  await updateContact(params.contactId, updates);
  return redirect(`/contacts/${params.contactId}`);
}

Form表单的提交信息会被送到原地址+action内容拼接成的新路由里的action函数

<Form action="edit">
    <button type="submit">Edit</button>
</Form>

9. NavLink 导航链接

当前 URL 匹配到这个 to 路径时,它就会自动获得一个默认的 active 类,获得相关样式,而Link标签不会

<NavLink to={`contacts/${contact.id}`}>
  {contact.name}
</NavLink>

灵活用法:className 接收函数

                  <NavLink
                    to={`contacts/${contact.id}`}
                    className={({ isActive, isPending }) =>
                      isActive
                        ? "active"
                        : isPending
                        ? "pending"
                        : ""
                    }
                  >
                    {/* other code */}
                  </NavLink>

与之前不显式加这个类目最大的区别就是有一个加载中pending(待处理),如果你想在加载中加小圈圈样式的话就这样写 (这个箭头函数是三元运算符的嵌套使用,我一开始看了很久也没看出来)

10. useNavigation | 导航对象

作用:在整个应用级别追踪导航状态,让你知道页面是否正在跳转、加载数据、提交表单。

navigation.state 的三种取值

"idle"

  • 没有正在进行的导航

  • 应用处于静止状态

"loading"

  • 正在跳转到新页面(新路由的 loader 在跑)

  • 常用于显示全局的 页面加载中 指示

"submitting"

  • 表单提交时(<Form method="post">fetcher.submit

  • 常用于显示 表单提交中 状态

基本用法:

import { useNavigation } from "react-router-dom";

export default function App() {
  const navigation = useNavigation();

  return (
    <div>
      {navigation.state === "loading" && <p>Loading...</p>}
      {navigation.state === "submitting" && <p>Submitting...</p>}
    </div>
  );
}

11. 上下文错误处理

我在destroy.jsx 文件里的action 加上这么一句throw new Error("oh dang!");

import { redirect } from "react-router-dom";
import { deleteContact } from "../contacts";

export async function action({ params }) {
  throw new Error("oh dang!");
  await deleteContact(params.contactId);
  return redirect("/");
}

这样,当我每次点击删除按钮,进入destroy的路由,会直接抛出错误,进入错误页面而不会返回根路由,除非刷新,用户实际上无法执行任何作来从此屏幕中恢复。

然后我在配置路由里加上 errorElement: <div>Oops! There was an error.</div>,

// 1.创建路由对象
const router = createBrowserRouter([
  {
    path : '/',
    element :<Root></Root>,
    errorElement : <ErrorPage/>,
    loader : rootLoader,
    action: rootAction,
    children : [
     /* other routes */
    {
       path: "contacts/:contactId/destroy",
       action: destroyAction,
       errorElement: <div>Oops! There was an error.</div>,
    }
  ]
  }

])

这时错误信息就变成子组件,只渲染在子组件的那部分

补充细节:errorElement 的层级

  • 错误会被最近的带有 errorElement 的路由捕获

  • 如果当前路由没有配置 errorElement,错误会冒泡到父路由的 errorElement(所以一开始进入的错误页面是根路由下的)

  • 如果一路冒泡都没人接,最终会由根路由的 errorElement 兜底

12. 索引路由

其实可以理解为在你还没有选择任何子路由时进入的默认子路由

作用:用于给父路由的 <Outlet /> 提供默认内容

// existing code
import Index from "./routes/index";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    loader: rootLoader,
    action: rootAction,
    children: [
      { index: true, element: <Index /> },
      /* existing routes */
    ],
  },
]);

13.取消按钮

使用 useNavigate 添加取消按钮点击处理程序,实现回退操作

import {  useNavigate, } from "react-router-dom";

const navigate = useNavigate();

//使用前在合适位置加入这两行

        <button
          type="button"
          onClick={() => {
            navigate(-1);
          }}
        >
          Cancel
        </button>


14.  将URL同步到表单

<form role="search">
  <input name="q" type="search" placeholder="Search" />
</form>

输入 ryan 回车:

浏览器会自动把输入序列化到 URL 里(没加 method="post",HTML 的 <form> 默认就是 GET

http://127.0.0.1:5173/?q=ryan

需求 : 希望url和搜索框保持同步

两步,1.获取url上q值 2.渲染到输入框 (感觉这波操作好像受控绑定)

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


//这里直接进入组件内部搜索框部分
<Form id="search-form" role="search">
  <input
  id="q"
  aria-label="Search contacts"
  placeholder="Search"
  type="search"
  name="q"
  defaultValue={q}
  />
  <div id="search-spinner" aria-hidden hidden={true} />
  <div className="sr-only" aria-live="polite"></div>
</Form>

  defaultValue={q},看起来很完美,你觉得这样好了吗?

当我们点击回退,url上参数清空,但搜索框内容并没有清空,为啥呢?

defaultValue 只在 第一次渲染时生效,回退后,React 并不会重新赋值给输入框

两种解决方案

  • 使用 <Form>key 强制重置

简单粗暴,把原来的组件销毁,创建新组件,这下总是每次都要重新渲染了吧

<Form key={q} method="get">
  <input name="q" defaultValue={q} />
</Form>
  • 用useEffect更新状态
  useEffect(() => {
    document.getElementById("q").value = q;
  }, [q]);

15. 搜索框输入内容实时过滤

需求:使用useSubmit,每次击键时进行过滤,而不是在显式提交表单时进行过滤

其实就是每次触发搜索框内容改变事件就自动提交

  1. useSubmit() 返回一个函数 submit

  2. submit(formOrData, options)

  • formOrData 可以是 <form> 元素,也可以是 FormData 或普通对象   
  • options 可以指定:action(提交到哪个路由,如果不写默认就是当前路由) method(GET/POST 等)
//引入调用
import {
  // existing code
  useSubmit,
} from "react-router-dom";

const submit = useSubmit();
//。。。添加改变事件
<Form id="search-form" role="search">
  <input
    {/* existing code */}
    onChange={(event) => {
      submit(event.currentTarget.form);
    }}
  />
 {/* existing code */}
</Form>

  • 16. 添加搜索状态提示

navigation.location 表示当前即将导航到的 URL,如果没有导航中,它可能是 null

  •   const searching =
        navigation.location &&
        new URLSearchParams(navigation.location.search).has("q");
  • navigation.location 无值  → 当前没有正在进行的导航,也就是页面已经处于稳定状态,所有数据加载完毕,直接返回
  • navigation.location 有值 → 页面正在导航
  • 我们检查 URL 是否包含我们关心的参数(比如 q) → 用这个判断“是否处于搜索加载中”
  • 不需要等目标路由其他组件的数据全部加载完成,只关注 q 对应的数据

添加类名,改变搜索框前面图标添加状态,一般是转圈的圆圈

className={searching ? "loading" : ""}

17. 替换历史记录history

前面我们实现了一个搜索框与url同步的功能,每次改搜索条件,就在历史记录里留一堆 /search?q=... 的记录,但是这样的话回退好多次才能回到最初搜索框为空的状态

怎么样才能实现点击“后退按钮”能回到最初状态呢?

onChange={(event) => {
    const isFirstSearch = q==null;
    submit(event.currentTarget.form,{
        replace:!isFirstSearch,
    })
}}

replace 是控制浏览器历史记录(history stack)的:

  • replace: false(默认)

    • 提交后会 新增一个历史记录条目

    • 用户按浏览器的 “后退” 按钮会回到之前的状态。

  • replace: true

    • 提交后会 替换当前的历史记录条目

    • 用户按“后退”不会回到前一次输入状态,而是直接跳到更早的状态。

第一次搜索会新建历史记录,之后的搜索则替换掉当前的历史记录,避免“后退”翻太多次。

18. usefetch 

用于不用跳转的突变,转到原来的路由,调用原路由action更新数据

记得把Form改成fetch.Form

const favorite = fetcher.formData?fetcher.formData.get('favorite') === 'true' : contact.favorite

fetcher捕获器可以获取表单提交数据,从而在action执行前就修改favortate状态,看起来操作更流畅

19. jsx配置路由

直接贴图吧,和之前写对象的模式有个区别就是子路由写在标签内

参考官方文档 :教程 v6.28.0 |React 路由器

参考视频:https://www.bilibili.com/video/BV1je411i7MH

就这么多啦!完结

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐