我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

本文将要讨论的钩子函数是来自react routeruseLoaderData()钩子,经过无数次的实验,苏南大叔认为:这个useLoaderData()useMatches()是无限关联的。两者之间存在着看不见理还乱的因果关系。所以,本文将选择他们两个进行代码实验对比。

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - useLoaderData钩子
React路由,如何使用useLoaderData钩子?路由数组loader(图8-1)

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0create-react-app@5.0.1react-router-dom@6.27.0react@18.3.1useLoaderData()主要用于预加载既定数据。

重要前提

useMatches()一样,useLoaderData()也是仅用于data router的。翻译一下的话,它只能配合<RouterProvider>createXxxRouter()使用。否则,就会得到下面的错误提示信息:

useLoaderData must be used within a data router.  See https://reactrouter.com/v6/routers/picking-a-router.

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - data-router
React路由,如何使用useLoaderData钩子?路由数组loader(图8-2)

<RouterProvider>createXxxRouter()的路由配置方案,可以参考文章:

App.js:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
//...
const router = createBrowserRouter([
  {
    path: "/",
    //...
  },
]);
function App() {
  return (
    <>
      <RouterProvider
        router={router}
      ></RouterProvider>
    </>
  );
}
export default App;

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - route-provider
React路由,如何使用useLoaderData钩子?路由数组loader(图8-3)

loader属性远程获取

createXxxRouter()的路由信息,由一个路由信息数组提供。必要的组成对象是pathelement。当然,也少不了children属性,和本文相关的属性,就是个loader属性,它将要提供一些【远程】数据,供组件进行使用。

这个loader属性,就是设计为获取【远程】数据的。从语法上来说,返回一个本地数据,也是完全没有问题的。但是,这个时候就没有延迟的问题了。失去了这个loader属性的存在意义。比如:

{
  //...
  loader: () => {
    return { name: "苏南大叔" };
  },
  //...
}

结合下面这篇文章,可以知道,在react router的世界里面,处理远程数据,可能有两种写法。参考文章:

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - router-loader
React路由,如何使用useLoaderData钩子?路由数组loader(图8-4)

在本文中,主要使用的promise代码方式,还是传统的async+await这种。defer的方式,也需要配合useLoaderData()钩子,但是具体上来说,和本文所要表述的主要代码区别较大,所以另开文章表述。

src/helper/Util.js

const loadDataPromise = () => {
  return new Promise((r) => {
    setTimeout(() => {
      r({ name: "苏南大叔" });
    }, 2000);
  });
};
async function loadDataAwait() {
  return await loadDataPromise();
}
export { loadDataPromise, loadDataAwait };

src/App.js:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { loadDataAwait, loadDataPromise } from "./helper/Util";
const router = createBrowserRouter([
  {
    //...
    loader: () => {
      return loadDataAwait();
    }
    //...
  },
]);
//...

组件里面使用

在当前路由层面上,任何一层(即使是父级)存在着loader定义的话,都会自动执行loader函数,获取相关远程数据。同时,获取数据期间,因为是await串行,页面会显示空白(解决方案是显示个loading界面)。【苏南大叔认为这个行为非最优解,这里仅仅出于阐述这个方案的目的。】

src/Sss.js

import { useLoaderData } from "react-router-dom";
export default () => {
  let data = useLoaderData();
  return <>欢迎光临,{data.name}【刷新显示loading】</>;
};

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - sss组件
React路由,如何使用useLoaderData钩子?路由数组loader(图8-5)

这里仅仅出于演示最简单目的,实际的场景下,还是经常要配合useEffect()useState()来使用的。比如:
src/Nnn.js

import { useEffect, useState } from "react";
import { useLoaderData } from "react-router-dom";
export default () => {
  const [result, setResult] = useState({ name: "-" });
  const data = useLoaderData();
  useEffect(() => {
    setResult(data);
  }, [data]);
  const test = (p = "大哥") => {
    setResult({ name: "苏南" + p });
  };
  const test2 = (obj, p) => {
    test(p);
  };
  return (
    <>
      欢迎光临,{result.name}
      <button onClick={test2}>大哥</button>
      <button onClick={test.bind(this, "二叔")}>二叔</button>
      【刷新显示loading】
    </>
  );
};

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - nnn组件
React路由,如何使用useLoaderData钩子?路由数组loader(图8-6)

React按钮事件绑定,参考文章:

空白变loading

因为本文描述的是个串行的过程,避免不了会显示空白。所以,ReactRouter设计了一个Provider属性fallbackElement。用于在这个总RouterProvider范围内全区域占位显示loading内容。

src/App.js:

//...
function App() {
  return (
    <>
      Public
      <RouterProvider
        router={router}
        fallbackElement={<div>Loading...</div>}
      ></RouterProvider>
    </>
  );
}
export default App;

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - 刷新显示loading
React路由,如何使用useLoaderData钩子?路由数组loader(图8-7)

不合理之处

因为只能设置一个fallbackElement,路由范围内任何一层设置了loader的话,都会导致整个Router范围内被覆盖loading内容,这很不科学。很有一点株连九族的味道。

并且Router内部,再次<Link>跳转到设置了loader的子组件的时候,也没有loading内容显示。换句话说,loading仅仅显示一次,第二次导航到这个组件的时候,虽然依然会再次请求远程数据,但是不会加载对应的loading。这完全不合理...

src/Layout.js

import { Outlet, Link, useMatches } from "react-router-dom";
export default () => {
  const tmp = useMatches();
  tmp.forEach((i, v) => {
    console.log(i, v.data);
  });
  return (
    <>
      <br />
      <Link to="/">默认首页</Link>
      &nbsp; &nbsp;
      <Link to="/s">普通延迟页面</Link>
      &nbsp; &nbsp;
      <Link to="/n">复杂延迟页面</Link>
      <br />
      <Outlet></Outlet>
    </>
  );
};

对比useMatches()

使用useMatches()之后,每条路由里面会出现个.data属性。用于承载loader:()=>{}的返回值数据。如下图所示:

苏南大叔:React路由,如何使用useLoaderData钩子?路由数组loader - usematches结果
React路由,如何使用useLoaderData钩子?路由数组loader(图8-8)

所以,也可以考虑使用useMatches().data属性,来监控当前location对应的每条路由的useLoaderData()返回值结果。

完整 App.js

src/App.js:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { loadDataAwait } from "./helper/Util";
import Layout from "./Layout";
import Sss from "./Sss";
import Nnn from "./Nnn";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout></Layout>,
    children: [
      {
        path: "s",
        element: <Sss></Sss>,
        loader: () => {
          return loadDataAwait();
        },
      },
      {
        path: "n",
        element: <Nnn></Nnn>,
        loader: () => {
          return loadDataAwait();
        },
      },
    ],
  },
]);
function App() {
  return (
    <>
      Public
      <RouterProvider
        router={router}
        fallbackElement={<div>Loading...</div>}
      ></RouterProvider>
    </>
  );
}
export default App;

结语

本文主要介绍了routerProviderfallbackElement属性,以及每条路由数组中可以设置的.loader远程获取数据的方式,最后通过useMatches()的匹配结果,查看每条路由的.data属性,即每条路由数组的.loader函数的返回值。

更多苏南大叔的react相关文章,请点击苏南大叔的博客文章:

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   react