React路由,如何使用useLoaderData钩子?路由数组loader
发布于 作者:苏南大叔 来源:程序如此灵动~本文将要讨论的钩子函数是来自react router
的useLoaderData()
钩子,经过无数次的实验,苏南大叔认为:这个useLoaderData()
和useMatches()
是无限关联的。两者之间存在着看不见理还乱的因果关系。所以,本文将选择他们两个进行代码实验对比。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0
,create-react-app@5.0.1
,react-router-dom@6.27.0
,react@18.3.1
。useLoaderData()
主要用于预加载既定数据。
重要前提
和useMatches()
一样,useLoaderData()
也是仅用于data router
的。翻译一下的话,它只能配合<RouterProvider>
和createXxxRouter()
使用。否则,就会得到下面的错误提示信息:
useLoaderData must be used within a data router. See https://reactrouter.com/v6/routers/picking-a-router.
<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;
loader属性远程获取
createXxxRouter()
的路由信息,由一个路由信息数组提供。必要的组成对象是path
和element
。当然,也少不了children
属性,和本文相关的属性,就是个loader
属性,它将要提供一些【远程】数据,供组件进行使用。
这个loader
属性,就是设计为获取【远程】数据的。从语法上来说,返回一个本地数据,也是完全没有问题的。但是,这个时候就没有延迟的问题了。失去了这个loader
属性的存在意义。比如:
{
//...
loader: () => {
return { name: "苏南大叔" };
},
//...
}
结合下面这篇文章,可以知道,在react router
的世界里面,处理远程数据,可能有两种写法。参考文章:
在本文中,主要使用的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】</>;
};
这里仅仅出于演示最简单目的,实际的场景下,还是经常要配合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
按钮事件绑定,参考文章:
空白变loading
因为本文描述的是个串行的过程,避免不了会显示空白。所以,ReactRouter
设计了一个Provider
属性fallbackElement
。用于在这个总RouterProvider
范围内全区域占位显示loading
内容。
src/App.js
:
//...
function App() {
return (
<>
Public
<RouterProvider
router={router}
fallbackElement={<div>Loading...</div>}
></RouterProvider>
</>
);
}
export default App;
不合理之处
因为只能设置一个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>
<Link to="/s">普通延迟页面</Link>
<Link to="/n">复杂延迟页面</Link>
<br />
<Outlet></Outlet>
</>
);
};
对比useMatches()
使用useMatches()
之后,每条路由里面会出现个.data
属性。用于承载loader:()=>{}
的返回值数据。如下图所示:
所以,也可以考虑使用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;
结语
本文主要介绍了routerProvider
的fallbackElement
属性,以及每条路由数组中可以设置的.loader
远程获取数据的方式,最后通过useMatches()
的匹配结果,查看每条路由的.data
属性,即每条路由数组的.loader
函数的返回值。
更多苏南大叔的react
相关文章,请点击苏南大叔的博客文章:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。