路由是 React Router 应用程序中最重要的部分。它们将 URL 段与组件、loader
和action
耦合在一起。通过路由嵌套,使得复杂的应用程序布局和数据依赖关系变得简单且具有声明性。
复制 const router = createBrowserRouter([
{
// 渲染组件
element: <Team/>,
// URL => http://localhost:3000/teams/1
path: "teams/:teamId",
// 在组件呈现之前加载数据
loader: async ({params}) => {
return fetch(`/api/teams/${params.teamId}`)
},
// 在数据提交时执行的突变
action: async ({request}) => {
return updateFakeTeam(await request.formData());
},
// 发生错误时呈现的组件
errorElement: <ErrorBounday/>
}
])
动态路由
如果路由段以:
开头,则它变成“动态段”。当路由与URL匹配时,动态段将从 URL 中解析并作为 params
提供给其他路由API。
复制 const router = createBrowserRouter([
{
path: '/',
element: <App/>,
children: [
{path: 'team/:teamId', element: <Team/>},
]
}
])
function Component() {
const params = useParams()
return <div>{params.teamId}</div>
}
在一个路由段中可以有多个动态路由,例如:
复制 const router = createBrowserRouter([
{
path: '/',
element: <App/>,
children: [
{path: ':userId/team/:teamId', element: <Team/>},
]
}
])
可选路由
通过在路由段的末尾添加一个?
来让其变成可选的。例如让一个动态路由段变成可选路由。
复制 const router = createBrowserRouter([
{
/**
* http:localhost:3000/categories
* http:localhost:3000/en/categories
* http:localhost:3000/cn/categories
*/
path: "/:lang?/categories",
element: <Categories/>,
loader: ({params}) => {},
action: ({params}) => {}
}
])
除了修饰动态路由段之外,还可以修饰静态路由段
复制 const router = createBrowserRouter([
{
path: "/project/task?/:taskId",
element: <Categories/>,
}
])
通配符Splats
如果路由路径模式以/*
结尾,那么它将匹配后面的任何字符,包括其他/
字符。
复制 const router = createBrowserRouter([
{
/**
* http:localhost:3000/files
* http:localhost:3000/files/one
* http:localhost:3000/files/one/two
*/
path: "/files/*",
element: <Files/>,
loader: ({params}) => {
console.log(params["*"]) // one/two
},
action: ({params}) => {}
}
])
function Files() {
const params = useParams()
console.log(params["*"]) // one/two
return <div>Files</div>
}
默认子路由
当子路由中设置index: true
的时候,该组件在你访问父路由的时候,它就会被默认渲染。因此该路由不需要设置额外的path
属性。
复制 const router = createBrowserRouter([
{
element: <Team/>,
path: "/teams/:teamId",
children: [
{
element: <TeamHome/>,
index: true
},
{
element: <TeamConfig/>,
path: "home-config"
}
]
}
])
loader
路由加载器在组件加载之前被调用,向组件提供数据,在组件内通过useLoaderData
钩子获取数据。
复制 const router1 = createBrowserRouter([
{
element: <Team/>,
path: "teams/:teamId",
children: [
{
element: <TeamHome/>,
index: true,
loader: async ({params, request, context}) => {
return fetch(`https://httpbin.org/get?username=zs&age=20&id={params.id}`).then(data => data.json())
}
}
]
}
])
function TeamHome() {
// 获取 loader 函数的返回值
const loaderData = useLoaderData();
return <div></div>
}
当你在loader
进行网络请求时,如果这个请求的过程很慢,它会阻碍路由的跳转以及视图的加载,直到你请求到了数据,路由才会跳转。为了解决这个问题,React Router 利用 React18 的 Suspense
通过 defer
相应实用工具和Await
组件或useAsyncValue
钩子进行数据获取。通过使用这些API,你可以解决以下两个问题:
你的数据不再在瀑布流上:文档->JavaScript->惰性加载路由和数据(并行)
使用defer
将你在loader
中需要返回的数据使用defer包裹,视图渲染的部分通过Suspense
和Aswit
包裹。
此时,当你的网络请求很慢的情况下,它会展示一个loading
状态。
或者你可以使用useAsyncValue
钩子,但你需要将代码分离到另一个组件中:
复制 interface IUser {
name: string,
age: number
}
function sleep(ms: number): Promise<IUser> {
return new Promise(resolve => setTimeout(() => resolve({
name: "zs", age: 20
}), ms));
}
export function loader({params, request, context}: LoaderFunctionArgs) {
return defer({
user: sleep(1000)
})
}
export const Component = () => {
const data = useLoaderData() as { user: IUser };
return (
<div>
<h1>welcome</h1>
<React.Suspense fallback={<div>loading</div>}>
<Await resolve={data.user} errorElement={
<p>Error loading package location!</p>
}>
<User/>
</Await>
</React.Suspense>
</div>
);
};
function User() {
const user = useAsyncValue() as IUser
return (
<p>
Your name is {user.name}, age is {user.age}
</p>
);
}
现在,我们不必再等待请求到数据,才路由跳转,而是在开始路由跳转后立即开始数据请求,在此过程中,异步数据渲染的部分会展示你提供的loading。
此外,你可以根据是否包含await关键字来切换某些内容是否将被延迟。
action
当提交从Form、fetcher、submission发送到路由时调用的操作。
复制 const router = createBrowserRouter([
{
element: <Team/>,
path: "teams/:teamId",
children: [
{
element: <TeamHome/>,
index: true,
loader: ({params}) => {
return fetch(`/api/teams/${params.teamId}/home`).then(res => res.json())
},
action: async ({request}) => {
const formData = await request.formData();
return updateTeam(formData)
}
},
{
element: <Home/>,
path: "home"
}
]
}
])
element/Component
当URL匹配时,需要呈现出的React组件。
复制 <Route element={<Team />} path="/team" />
<Route Component={<Team />} path="/team" />
errorElement/ErrorBoundary
当组件在渲染中抛出异常,或者在loader
或action
中发生错误时,,此 React 组件将渲染你提供的错误提示组件。
复制
const router = createBrowserRouter([
{
path: "/",
element: <App/>,
children: [
{path: '/', element: <Home/>, errorElement: "页面渲染发生错误"},
{path: "/about", element: <About/>},
{path: "/profile", element: <Profile/>},
{path: "/*", element: "404页面"}
]
},
], {
basename: "/webapp"
})
当你为该路由组件提供了errorElement
属性后,React 错误边界捕获的异常组件渲染将会失效。
lazy
未来保持应用程序包较小,并支持路由的代码分割,每个路由都可以提供一个async函数来解决路由定义中的非路由匹配部分(loader、action、Component/element,ErrorBoundary/errorElement等等)。
复制 const router = createBrowserRouter([
{
element: <Team/>,
path: "teams/:teamId",
children: [
{
lazy: () => import("../pages/team/home"),
index: true,
},
{
lazy: () => import("../pages/team/about"),
path: "about"
}
]
}
])
在懒加载路由中,导出你想要为路由定义的属性:
复制 export async function loader({request}) {
let data = await fetchData(request);
return json(data)
}
export function Team() {
let data = useLoaderData()
return <div>{data}</div>
}