React Router V6 入门指南

2023-03-05 下午前端 108 次浏览暂无评论

说明

最近使用React Router V6较多,记录一些常用的功能和API。

详细内容请自行查阅官方文档:传送门

一、路由配置

1. Router组件

它本身不做任何展示,仅提供路由模式配置,另外,该组件会产生一个上下文,上下文中会提供一些实用的对象和方法,供其他相关组件使用。

  1. <HashRouter>:使用hash模式匹配
  2. <BrowserRouter>:使用BrowserHistory模式匹配

通常情况下,Router组件只有一个,包裹整个<App/>

//- src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { store } from "./store";
import { Provider } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <Router>
        <App />
      </Router>
    </Provider>
  </React.StrictMode>
);

2. Routes组件

v6版本中移出了先前的<Switch>,引入了新的替代者:<Routes>

<Routes>的子元素只能有<Route>组件,因为 <Routes> 组件会循环所有子元素,然后让每个子元素去完成匹配,若匹配到,则渲染对应的组件,然后停止循环。

路由是根据最佳匹配而不是按顺序选择的

嵌套的 parent route 的 path 不用加。但如果不是嵌套,而是分散在子组件中,就需要尾部加上。

推荐使用React Router V6提供的**useRoutes()**配置 路由表

3. Route组件

Route 组件必须使用 Routes 嵌套。

根据不同的地址,展示不同的组件。

重要属性:

  • path:匹配的路径

    ​ 1. 默认不区分大小写,可以设置**caseSensitive**属性,来区分大小写

    ​ 2. 默认精准匹配,可以设置path路径为/a/*,来动态匹配路由

    ​ 3. 如果不写path,默认匹配不到路由

    ​ 4. 设置path路径为*,则匹配除已设置的路由以外的任意路径

    ​ 5. 设置path路径为 / ,则匹配为整个网站的空路径。

  • index:是否是主路由,如果设置为 true 的话不能有 children

  • element:匹配成功后显示的组件,可以向组件传入props

嵌套路由

  • 嵌套路由的 path 可以不用写父级,会直接拼接。

  • 动态路由通过 :id 的形式实现。

  • /a/b 的匹配度大于 /a/* ,输入精确地址会精确匹配,而不是匹配到动态路由。

  • 嵌套路由必须在父级追加 <Outlet/> 组件,作为子级组件的占位符。

  • 除了使用<Route>组件作为<Routes>组件的children的方式,还可以通过 useRoutes() 生成对应的element

路由配置流程

step1: 使用 Router组件包裹整个 <App/>

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { store } from "./store";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
);

step2: 配置路由,有两种方案,使用**useRoutes()钩子配置路由表**,或使用组件进行嵌套。推荐使用第一种。

方案一: 使用**useRoutes()配置路由表**:

  • 使用<Suspense>组件用来进行路由组件的懒加载,进行性能优化。
//- src/App.js
import { routes } from "./router";
import { useRoutes } from "react-router-dom";
import "./App.css";

function App() {
  const element = useRoutes(routes);
  return element;
}

export default App;

//- src/router/index.js
import { Suspense } from "react";
import Home from "../pages/home";
import Login from "../pages/login";
import Demo from "../pages/demo";
import BaseLayout from "../layouts/BaseLayout";

//- 懒加载优化
const lazyLoad = (children) => {
  return <Suspense fallback={<h1>Loading...</h1>}>{children}</Suspense>;
};

export const routes = [
  {
    path: "/",
    element: <BaseLayout />,
    children: [
      {
        index: true,
        element: lazyLoad(<Home />),
      },
      {
        path: "/demo",
        element: lazyLoad(<Demo />),
      },
    ],
  },
  {
    path: "/login",
    element: lazyLoad(<Login />),
  },
];

方案二: 直接使用<Routes>组件配置路由:

//- src/App.js
import { Routes, Route } from "react-router-dom";
import "./App.css";
import BaseLayout from "./layouts/BaseLayout";
import Home from "./pages/home";
import Counter from "./pages/counter";
import Demo from "./pages/demo";
import Login from "./pages/login";

function App() {
  return (
    <Routes>
      <Route path="/" element={<BaseLayout />}>
        <Route index element={<Home />} />
        <Route path="/counter" element={<Counter />} />
        <Route path="/demo" element={<Demo />} />
      </Route>
      <Route path="/login" element={<Login />} />
    </Routes>
  );
}

export default App;

step3:Layout中添加 <Outlet/> 组件,作为子级路由的占位符。

//- src/layouts/BaseLayout.jsx
import React from "react";
import { Link, Outlet } from "react-router-dom";

const BaseLayout = () => {
  return (
    <div>
      <main>
        <Link to="/">首页</Link>
        <Link to="/demo">测试页</Link>
      </main>
      <div>
        <Outlet />
      </div>
    </div>
  );
};

export default BaseLayout;

二、路由跳转

1. link跳转

<Link> 组件

组件属性:

  1. **to:**是基于父级 Route 的 path。
    • 字符串:跳转的目标。
    • 对象:pathname(url路径)、search(字符串类型)、hash(字符串类型)。
  2. **replace:**boolean,默认 false,即跳转路由要用 push 还是 replace。
  3. target:_self、_blank、_parent、_top。
  4. **state:**object,即点击后可以给 to 传对应的 state。
  5. **ref:**可以将内部a元素的ref附着在传递的对象或函数参数上。
  6. **reloadDocument:**boolean,是否重新加载文档。
<Link to="/路径">按钮</Link>

2. NavLink组件跳转

  1. 通常用作导航栏中的跳转。
  2. 默认会给当前活动的NavLink的组件加一个.active。
  3. 会给当前活动的NavLink传递一个isActive属性,可以在className、style、children中使用该属性。
  4. **caseSensitive:**boolean,是否区分大小写。
<NavLink className={({ isActive }) => isActive ? "red" : "blue"} />

3. Navigate 重定向组件

  1. 可以用来匹配未定义的路由。
  2. to: 基于父级 Route 的 path。
    • 字符串:跳转的目标。
    • 对象:pathname(url路径)、search(字符串类型)、hash(字符串类型)。
  3. **replace:**boolean,默认 false,即跳转路由要用 push 还是 replace。
  4. **state:**object,传递 state。
<Route path="*" element={<Navigate to="/"/>} />

4. useNavigate()跳转

useNavigate()函数用来实现编程式导航。

函数参数:

  • path:跳转的路径
    • naviaget("xxx")默认就是 history.push
    • naviaget("xxx", { replace: true })就是 history.replace
    • naviaget(to: number)就是 history.go
    • 如果携带params参数或search参数。
  • option:配置对象。
    • replace: boolean,相当于history.replace
    • state: object,用于传递信息。
import React from 'react'
import {useNavigate} from 'react-router-dom'
 
export default function Demo() {
  const navigate = useNavigate()
  const handleTo = () => {
    navigate('/login', {
      replace: false,
      state: {a:1, b:2}
    }) 
  }
  
  return (
    <div>
      <button onClick={handleTo}>跳转</button>
    </div>
  )

三、获取参数

1. 获取state数据

  1. useLocation() 获取state数据。
import { useLocation } from "react-router-dom";
let location = useLocation()
let data=location.state

2. 获取地址栏数据

  1. useParams() 获取动态路由的值。
  2. useSearchParams() 获取查询字符串的值。
  // 获取动态路由的值
  const params = useParams();
  const data=params.参数名

  // 获取查询字符串的值
  const [searchParams, setSearchParams] = useSearchParams();
  const data=searchParams.get('参数名')
  // 修改查询字符串的值
  setSearchParams('a=1&b=2&c=3')

3. 解析Url中的参数值

  1. useResolvedPath() 给定一个 URL,解析其中的pathsearchhash值。
console.log('useResolvedPath',useResolvedPath('/user?id=001&name=tom#qws'))
 //输出:{pathname: "/user", search: "?id=001&name=tom", hash: "#qws"}

四、导航守卫

应用场景:当进入首页或任意页面时,先判断是否已经登录,如果未登录,会自动重定向到登录页。

实现原理:创建一个高阶组件,将原来的组件包裹,通过高阶组件进行判断,如果条件成立返回children,否则进行重定向。

//- src/router/AuthProvider.jsx
import { useSelector } from 'react-redux';
import { Navigate, useLocation } from 'react-router-dom';

const AuthProvider = ({ children }) => {
  const location = useLocation();
  const isLogin = useSelector(state => state.user.isLogin);
  const userInfo = useSelector(state => state.user.userInfo);

  // 如果是登录页面,直接跳转
  if (location.pathname === '/login' || location.pathname === '/register') {
    return children;
  };

  // 如果登录信息有问题,重新登录。否则直接跳转
  if (!isLogin || !userInfo) {
    return <Navigate to="/login" />;
  } else {
    return children;
  }
};

export default AuthProvider;
//- src/router/index.jsx

import { Navigate, useRoutes } from 'react-router-dom';
import Home from '../pages/Home';
import AddOrEdit from '../pages/AddOrEdit';
import Detail from '../pages/Detail';
import About from '../pages/About';
import Email from '../components/Email';
import Tel from '../components/Tel';
import Login from '../pages/Login';
import BaseLayout from '../components/BaseLayout';
import AuthProvider from './AuthProvider';

export default function RouterConfig() {
  const routes = useRoutes([
    {
      path: '/',
      element: <BaseLayout />,
      children: [
        {
          path: '/home',
          element: <Home />
        }, {
          path: '/add',
          element: <AddOrEdit />
        }, {
          path: '/edit/:id',
          element: <AddOrEdit />
        }, {
          path: '/detail/:id',
          element: <Detail />
        }, {
          path: '/about',
          element: <About />
        }
      ]
    }, {
      path: '/login',
      element: <Login />
    }, {
      path: '*',
      element: <Navigate to='/home' replace />
    }
  ]);
  return <AuthProvider>{routes}</AuthProvider>;
}

五、路由过渡动画

实现思路:

  1. <TransitionGroup/>组件将<Routes/>组件包裹,因为<Routes/>组件下只能包含<Route/>组件
  2. 创建一个<TransitionRoute/>的高阶组件,将要加入动画的组件包裹,传入in、key属性,以供内部的<CSSTransition/>组件使用。
  3. 内容使用<CSSTransition/>组件包裹props.children,通过自定义css文件或者引入animate.css库,添加动态效果。
  4. className使用对象的方式添加:classNames={{ enter: "animate__fadeIn" }}
//- App.js
import React from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import StudentList from "./pages/student/StudentList";
import StudentAdd from "./pages/student/StudentAdd";
import TransitionRoute from "./TransitionRoute";
import { useLocation } from "react-router-dom";
import { TransitionGroup } from "react-transition-group";

function App() {
  const location = useLocation();
  return (
	<TransitionGroup>
        <Route
          path="/student/list"
          element={
            <TransitionRoute keyText={location.key} in={location.pathname==="/student/list"}>
              <StudentList />
            </TransitionRoute>
          }
        />
        <Route
          path="/student/add"
          element={
            <TransitionRoute keyText={location.key} in={location.pathname==="/student/add"}>
              <StudentAdd />
            </TransitionRoute>
          }
        />
      </Route>
    </Routes>
    </TransitionGroup>
  );
}

export default App;
//- src/TransitionRoute.js
import React from "react";
import { CSSTransition } from "react-transition-group";
import "animate.css";
// import './TransitionRoute.css'
/**
 * 传入两个属性
 * key:csstransition组件的key值
 * in:是否改变动画状态
 * timeout:多长时间后,去掉之前的样式,添加样式enter-done/exit-done,默认0.8秒
 */
export default function TransitionRoute(props) {
  return (
    <CSSTransition
    	key={props.keyText}
    	in={props.in}
      	timeout={props.timeout}
      	classNames={{ enter: "animate__bounceIn",exit:"animate__bounceIn" }}
    >
      {props.children}
    </CSSTransition>
  );
}
TransitionRoute.defaultProps={
    timeout:800
}

六、滚动条复位

使用场景:当用户切换路由时,滚动条默认不会变化,需要我们手动将滚动条恢复至顶部。

实现方法:

step1: 首先封装一个滚动回顶部的函数resetScroll()

resetScroll()函数实现思路:

  1. 创建一个animate()函数,接受动画开始位置start,结束位置end,和要执行的回调函数callback,返回一个定时器。
  2. 调用两个animate()函数,传入scrollTopscrollLeft等参数。
let timer1, timer2;

/**
 * 滚动条横向和纵向动画复位
 */
export default function resetScroll() {
    clearInterval(timer1);
    clearInterval(timer2);
    var html = document.documentElement;
    timer1 = animate(html.scrollTop, 0, (val) => {
        html.scrollTop = val;
    })
    timer2 = animate(html.scrollLeft, 0, (val) => {
        html.scrollLeft = val;
    })
}

/**
 * 在300毫秒之内,从指定的初始值,变化到结束值
 * @param {*} start 
 * @param {*} end 
 */
function animate(start, end, callback) {
    var tick = 16; //每隔16毫秒完成一次变化
    var total = 300; //总时间为1000毫秒
    var times = Math.ceil(total / tick); //变化的次数
    var curTimes = 0;
    var dis = (end - start) / times; //总距离/次数,每次运动的距离
    var timer = setInterval(() => {
        curTimes++;
        start += dis;
        if (curTimes === times) {
            start = end;
            clearInterval(timer);
        }
        callback(start);
    }, tick)
    return timer;
}

**step2:**通过各种方式使用resetScroll()函数

  1. 高阶组件
//- withScroll.js:
import React from "react"
import reset from "./resetScroll"
export default function withScroll(Comp) {
    return class ScrollWrapper extends React.Component {
        componentDidMount() {
            reset();
        }
        render() {
            return <Comp {...this.props} />
        }
    }
}

使用:
const PageWithScroll = withScroll(Page1)
  1. 自定义Hook
//- useScroll.js
import { useEffect } from "react"
import reset from "./resetScroll"

export default function useScroll(pathname) {
    useEffect(reset, [pathname])
}
使用:

import useScroll from "./useScroll"
useScroll(props.location.pathname);//调用时滑动
  1. 导航守卫判断

实现思路:在当前页面中传入location.pathname给跳转后的页面,在跳转后的页面判断两个path是否相同,不同则调用resetScroll

// ResetScrollRoute.jsx:
import React from 'react'
import { useLocation } from "react-router-dom";
import reset from "./resetScroll"

export default function ResetScrollRoute({oldPathname}) {
    const location = useLocation();
    const nowPathname = location.pathname;
    if(oldPathname!==nowPathname){ 
        reset()
    }
    return props.children;
}

总结

以上就是个人总结的一些基本用法,上述仅为个人理解,如果有错误,还请路过的大佬斧正!


目录

一、路由配置
1. Router组件
2. Routes组件
3. Route组件
嵌套路由
路由配置流程
二、路由跳转
1. link跳转
2. NavLink组件跳转
3. Navigate 重定向组件
4. useNavigate()跳转
三、获取参数
1. 获取state数据
2. 获取地址栏数据
3. 解析Url中的参数值
四、导航守卫
五、路由过渡动画
六、滚动条复位
总结
ICP备案号:鲁ICP备2020040322号