likes
comments
collection
share

react-router-dom(6.23.1)最新最全指南

作者站长头像
站长
· 阅读数 52

前言

针对react-router-dom V6,鉴于目前网络上的相关资源较少,本篇特地汇总了一些日常开发中常用的功能点。

本篇示例地址:github.com/cwjbjy/vite…

安装:yarn add react-router-dom

一、基本使用

提示:以下所有文件都新建在src目录下

1. 定义路由

1. 新建components/lazyImportComponent.tsx

import { Suspense, LazyExoticComponent } from 'react';

const LazyImportComponent = (props: { lazyChildren: LazyExoticComponent<() => JSX.Element> }) => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <props.lazyChildren />
    </Suspense>
  );
};

export default LazyImportComponent;

对使用懒加载的路由组件,使用Suspense包裹,用于异步加载组件时显示过渡UI

2. 新建router/routes.tsx

import { lazy } from 'react';

import LazyImportComponent from '@/components/lazyImportComponent';

const routes = [
  {
    path: '/login',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/login'))} />,
  },
  {
    path: '/',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
  },
];

export default routes;

使用lazy延迟加载组件的代码(路由懒加载)

2. 创建路由实例

新建src/router/index.tsx

import { createBrowserRouter } from 'react-router-dom';

import routes from './routes';

//可传第二个参数,配置base路径,例如{ basename: "/app"}
const router = createBrowserRouter(routes);

export default router;

3. 管理路由

修改main.tsx

import { RouterProvider } from 'react-router-dom';

import ReactDOM from 'react-dom/client';

import router from './router';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(<RouterProvider router={router} />);

RouterProvider:提供路由配置并管理应用的导航逻辑

二、嵌套路由

如果要实现登录之后左侧菜单栏不变,右侧随路由的切换变化显示的内容,需使用嵌套路由

1. 定义路由配置文件

修改router/routes.tsx

import { lazy } from 'react';

import LazyImportComponent from '@/components/lazyImportComponent';

const routes = [
  {
    path: '/login',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/login'))} />,
  },
  {
    path: '/',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    children: [
      {
        path: '/user',
        element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/user'))} />,
      },
      {
        path: '/manage',
        element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/manage'))} />,
      },
    ],
  },
];

export default routes;

2. 配置嵌套路由路口

修改layout.tsx

import { Outlet } from 'react-router-dom';

export default function Layout() {
  return (
    <div className="main">
      菜单栏
      <Outlet />
    </div>
  );
}

Outlet组件呈现匹配的子路由,类似vue的router-view组件

三、索引路由

索引路由的内容渲染在父路由的Outlet中,类似于默认子路由。

在上述嵌套路由中,如果直接访问路由"/",则没有匹配到任何子路由,页面只会显示“菜单栏”。可新增索引路由,在父路由没有匹配到任何子路由的情况下,显示默认的子路由内容

修改router/routes.tsx

const routes = [
  //...
  {
    path: '/',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    children: [
      {
        index: true,
        element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/home'))} />,
      },
      //...
    ],
  },
];

四、重定向

索引路由可以用来路由重定向,当访问“/”使,重定向到“/user”

修改router/routes.tsx

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

const routes = [
  //...
  {
    path: '/',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    children: [
      {
        index: true,
        element: <Navigate to={'/user'} replace={true} />,
      },
      //...
    ],
  },
];

replace={true}:进行重定向

五、404页面

修改router/routes.tsx

const routes = [
  //...
  {
    path: '*',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/notFound'))} />,
  },
]

404路由放在路由配置表的最后

六、声明式、编程式导航

1. 声明式导航

<NavLink to="/manage">manage</NavLink>

2. 编程式导航

import { useNavigate } from 'react-router-dom';

export default function User() {
  const navigation = useNavigate();
  return (
    <div>
      user
      <button onClick={() => navigation('/manage')}>manage</button>
    </div>
  );
}

七、路由传参

1. search传参

1. 编程式传参

<NavLink to={{ pathname: '/file', search: '?sort=name&id=2' }}>file</NavLink>

2. 命令式传参

<button onClick={() => navigation({ pathname: '/file', search: '?sort=name&id=2' })}>file</button>

3. 页面接参

import { useSearchParams } from 'react-router-dom';

export default function File() {
  const [searchParams] = useSearchParams();
  return (
    <div>
      获取id:{searchParams.get('id')}
      获取sort:{searchParams.get('sort')}
    </div>
  );
}

2. state传参

1. 编程式传参

<NavLink to="/file" state={{ id: 1 }}> file </NavLink>

2. 命令式传参

<button onClick={() => navigation("/file", { state: { id: "2" } })}> file </button>

3. 页面接参

import { useLocation } from 'react-router-dom';

export default function File() {
  const { state } = useLocation();
  return <div>{state.id}</div>;
}

特点:参数不会显示在路径上

3. 动态路由匹配

1. 定义动态路由

修改router/routes.tsx

const routes = [
  //... 
  {
    path: '/',  
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    children: [
      //... 
      //新增
      {
        path: '/file/:id?',
        element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/file'))} />,
      },
    ],
  },
];

2. 编程式传参

<NavLink to="/file/123">file</NavLink>

3. 命令式传参

<button onClick={() => navigation('/file/123')}>file</button>

4. 页面接参

import { useParams } from 'react-router-dom';
export default function File() {
  const { id } = useParams();
  return <div>{id}</div>;
}

八、错误处理

在路由组件加载过程中发生错误时展示的元素

1. 新建components/errorBoundary.tsx

import { useRouteError } from 'react-router-dom';

export default function ErrorBoundary() {
  const error = useRouteError();
  //错误信息,可用来错误上报
  console.log(error);
  return <>错误页面</>;
}

2. 修改router/routes.tsx

//新增
import ErrorBoundary from '@/components/errorBoundary';

const routes = [
  //...
  {
    path: '/',
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    errorElement: <ErrorBoundary />, 
    children:[
        //...
    ]
  },
]

九、路由守卫

loader:在渲染路由组件之前执行一些操作,比如异步数据加载,权限检查。

1. 同步权限检查

判断localStorage中存储的token值。当不存在token时,在渲染路由组件之前进行重定向到登录页

1. 定义loader

新建router/loader.ts

import { redirect } from 'react-router-dom';

export function protectedLoader() {
  if (!localStorage.getItem('token')) {
    return redirect('/login');
  }
  return null;
}

2. 挂载loader

修改router/routes.tsx

//新增
import { protectedLoader } from './loader';

const routes = [
  //...
  {
    path: '/',
    loader: protectedLoader,
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    errorElement: <ErrorBoundary />,
    children:[
        //...
    ]
  },
]

2. 异步权限检查

通过接口判断当前路由是否有访问权限,没有,则在渲染路由组件之前重定向到登录页

1. 定义loader

修改router/loader.ts

/* 模拟网络接口 */
function getToken(): Promise<number> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(Math.random());
    }, 2000);
  });
}

export async function tokenLoader() {
  const num = await getToken();
  //如果随机数大于0.5则重定向到登录页
  if (num > 0.5) {
    return redirect('/login');
  } else {
    return null;
  }
}

2. 挂载loader

修改router/routes.tsx

import { tokenLoader } from './loader';

const routes = [
  //... 
  {
    path: '/',
    loader: protectedLoader,  
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    errorElement: <ErrorBoundary />,
    children: [
      //... 
      {
        path: '/manage',
        loader: tokenLoader ,     //新增
        element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/manage'))} />,
      },
    ],
  },
];

3. 增加导航加载状态

在loader完成之前,路由不会跳转,如果接口请求时间过长,会导致路由跳转卡顿明显。此时需增加导航加载状态效果

修改layout.tsx

import { Outlet, useNavigation } from 'react-router-dom';

export default function Layout() {
  const navigation = useNavigation();
  return (
    <div className="main">
      菜单栏
      {navigation.state === 'loading' && <div>数据加载中</div>}
      <Outlet />
    </div>
  );
}

3. 异步数据加载

在渲染路由组件时,调用接口获取数据。

1. 定义loader

修改router/loader.ts

//新增
export interface User {
  id: number;
  name: string;
}

/* 模拟网络接口 */
function getUsers(): Promise<User[]> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([
        { id: 1, name: 'zhangsan' },
        { id: 2, name: 'lisi' },
      ]);
    }, 2000);
  });
}

export function usersLoader() {
  const userPromise = getUsers();
  return defer({ data: userPromise });
}

defer 在 loader 内使用,表明这个 loader 需要展示 Loading 状态。

2. 挂载loader

修改router/routes.tsx

//新增
import { usersLoader } from './loader';

const routes = [
  //... 
  {
    path: '/', 
    loader: protectedLoader, 
    element: <LazyImportComponent lazyChildren={lazy(() => import('@/layout'))} />,
    errorElement: <ErrorBoundary />,
    children: [
      //... 
      {
        path: '/user',
        loader: usersLoader,     //新增
        element: <LazyImportComponent lazyChildren={lazy(() => import('@/pages/user'))} />,
      },
    ],
  },
];

3. 获取loader返回的数据

import { useLoaderData, Await, useAsyncValue } from 'react-router-dom';

import type { User } from '@/router/loader';

export default function User() {
  const { data } = useLoaderData() as { data: Promise<User[]> };

  return (
    <Await resolve={data}>
      <List />
    </Await>
  );
}

const List = () => {
  const data = useAsyncValue() as User[];
  return (
    <>
      {data.map((o) => (
        <div key={o.id}>{o.name}</div>
      ))}
    </>
  );
};

Await:需要结合<Suspense>使用,Loading 状态展示在<Suspense> 的 fallback 中

useAsyncValue:用于从最近的<Await>祖先组件返回解析后的数据

十、组件内守卫

使用情景:预防用户在还未保存修改前突然离开。

import { useState } from 'react';
import { unstable_usePrompt } from 'react-router-dom';

export default function File() {
  const [value, setValue] = useState('');

  unstable_usePrompt({
    message: '确定离开吗',
    when: ({ currentLocation, nextLocation }) => value !== '' && currentLocation.pathname !== nextLocation.pathname,
  });

  return <input onChange={(v) => setValue(v.target.value)} />;
}

十一、状态保存

在有些场景中,我们需要在浏览器刷新或关闭时保存数据状态。之前常监听浏览器的beforeunload事件,在React Router V6中,新增了 useBeforeUnload,用来将重要的应用程序状态保存在页面上

import { useState, useCallback } from 'react';
import { useBeforeUnload } from 'react-router-dom';

export default function Manage() {
  const [count, setCount] = useState(localStorage.getItem('count') || '0');

 //在浏览器关闭或者刷新时,保存数据
  useBeforeUnload(
    useCallback(() => {
      localStorage.setItem('count', count);
    }, [count]),
  );

  return (
    <div>
      {count}
      <button
        onClick={() => {
          setCount((prev) => prev + '1');
        }}
      >
        按钮
      </button>
    </div>
  );
}

结尾

action:使用react-router-dom提供的Form表单,当表单提交时action会被触发。但目前绝大部情况下都使用axios或fetch提交表单,因此action实际使用场景并不多,这里不再详细阐述

相关文章:

转载自:https://juejin.cn/post/7379897208534073398
评论
请登录