食無魚
open main menu

React-router 总结

/ 7 min read

react-router

应用一般划分为三大板块:

  • Layouts: 内嵌子页面
  • Pages: 页面
  • Components: 复用组件

pages vs components

  • page 也是一个 component,但是它是与具体的路由绑定的
  • component 一般是抽象出来方便复用的组件,
  • Link 拦截浏览器默认行为,但是仅限于左键单击, 其他类型的点击(右键。。)不受影响。
  • history.pushState() 来改变页面 url,触发 hashChange 事件,被 router 捕捉来处理跳转
  • change history, location and render new matches in routes
  • 的“跳转”行为只会触发相匹配的 对应的页面内容更新,而不会刷新整个页面。
  • NavLink 是特殊的 Link,会记录当前的 Link 是否是 active 状态,体现在 class 之中
  • NavLink active 匹配多个路由, ./a/b/c 会匹配 /a, /a/b …
  • end property: 它将确保当其后代路径匹配时,此组件不会被匹配为“活动”, 当且仅当在其根目录下时才表现为 active

获取页面 url

  • 当前页面 url,使用 useLocation 或者 useNavigation
//1
const location = useLocation();
// 获取当前页面的路径
location.pathname;

//2
const navigation = useNavigation();
navigation.location.pathname;
  • 下一步跳转的页面
const navigation = useNavigation();
const nextUrl = navigation.formAction;

解析路由所提供的 params

使用 useParams

解析 request 的 params

使用 URL 方法

// 获取 query
new URL(request.url).searchParams.get("query");
// 获取 pathname
new URL(request.url).pathname;

useNavigate vs redirect

useNavigate 作为 hook 仅能在组件顶部和自定义 hook 内,而redirect则可以广泛运用于 loader 和 action 之中

useNavigation

useNavigation 返回整个 navigate 行为状态的对象, 详见useNavigation

const navigation = useNavigation();
navigation.state; // "idle" | "submitting" | "loading"
navigation.formData; //是否有提交内容
navigatin.location; //下一个 location 会是哪里
navigation.formAction === navigation.location.pathname
  ? "reloading"
  : "redirecting"; // 判断是重载还是跳转

常用场景: 渲染 button

<button disabled={navigation.state === "submitting"}>
  {navigation.state === "submitting" ? "Logging in..." : "Log in"}
</button>

保存当前 url 的信息至下一个页面

在跳转前保存本页面 url 的信息,比如 ?query= 或者 filter 则使用

  • Link

<Link state={query: ...}>

  • useNavigate
const navigate = useNavigate();
navigate("/new-route", { state: { key: "value" } });

等到了新页面,使用 useLocation 来取得先前储存的 state 信息,在返回

useLoaderData

  • loader 属性从路由接收参数,比如 params(:id..),和 request
  • useLoaderData 不使用 defer 方法时,等待 promise resolved,返回 json. 如果使用 defer 方法,返回一个包裹 promise 的 json 对象,然后直接进入渲染环节,在 ui 部分使用Await去等待 resolved

对比 useEffect

  • useEffect 缺点
  1. 客户端负责获取数据并渲染,则由服务端提供的初始的 html 将不包含数据;
  2. useEffect 直接请求数据会引发网络瀑布,父组件->子组件会反反复复;
  3. useEffect 直接请求数据无法预加载或者缓存,组件卸载再挂载将重复请求数据;
  4. 需要编写大量样板代码,并且分别处理状态,错误处理等
  • useLoaderData 优点
  1. 预加载数据成功后才会渲染,统一进行错误处理
  2. 客户端缓存,避免重复请求,类似的还有React Query,useSWR等等
  3. 并行请求数据避免网络瀑布阻塞
  4. 避免大量样本代码

dataLoader 与 defer

当使用 dataLoader 时候,都是先 load 数据完成再渲染页面,这可能会等待较长时间,由于不使用 useEffect,无法独立的处理状态,因此有了 defer,Await,Suspense 的运用。

loader 函数中不再等待 promise 状态结束而是直接返回一个对象,其中键值对中,值为 promise 类型

return defer({ data: getDatas() });

在需要 defer loading 的组件之上套一层Suspense组件来提供 status 信息。

const loaderData = useLoaderData()
// fallback to show "Loading..." when promise not returned yet
<Suspense fallback={...}>
<Await resolve={loaderData...}>
{...}
</Await>
</Suspense>

outlet && useOutletContext

嵌套子组件获取父组件中的信息

登录权限 Login

  1. 路由 loader 函数从 localstorage 读取登录状态, 如果为 false, 则 redirect
export async function requireAuth(request) {
  const pathname = new URL(request.url).pathname;
  const isLoggedIn = localStorage.getItem("loggedin");

  if (!isLoggedIn) {
    throw redirect(
      `/login?message=You must log in first.&redirectTo=${pathname}`,
    );
  }
}
  1. 跳转登录页面, loader 提取 message 并显示
export function loader({ request }) {
  return new URL(request.url).searchParams.get("message");
}
  1. 提交表单 =>触发 action, 核实后 redirect 原先的 path
export async function action({ request }) {
  const formData = await request.formData();
  const email = formData.get('email');
  const password = formData.get('password');
  const pathname = new URL(request.url).searchParams.get('redirectTo') || '/host';

  try {
    const data = await loginUser({ email, password });
    localStorage.setItem('loggedin', true);
    return redirect(pathname);
  } catch (err) {
    return err.message;
  }
  • true, 无事发生,正常跳转

lazy

<Route lazy={() => import("./....jsx")}

在路由层面定义懒加载和缓存数据,返回一个 promise 或者 thenable 对象

Form

  • React Router 将 HTML 表单导航模拟为数据突变原型,符合 JavaScript 大爆发之前的网络开发。它为您提供了客户端呈现应用程序的用户体验功能和 “老式 “网络模型的简洁性。虽然 HTML 表单对于一些网络开发人员来说并不熟悉,但它实际上是在浏览器中进行导航,就像点击链接一样。唯一的区别在于请求:链接只能更改 URL,而表单还可以更改请求方法(GET 与 POST)和请求主体(POST 表单数据)。如果没有客户端路由,浏览器会自动序列化表单数据,并将其作为 POST 的请求体和 GET 的 URLSearchParams 发送到服务器。React Router 也会做同样的事情,只不过它不是将请求发送到服务器,而是使用客户端路由将其发送到路由操作。

  • to 如果是相对路径,则默认是解析路径根据 parent route, 即`relative="route"`.设置为`relateive="path"` 则是根据 path 解析。