react-router

react-router

一、在react项目中安装路由

官方文档:https://reactrouter.com/en/v6.3.0/getting-started/installation#basic-installation

npm

$ npm install react-router-dom@6

二、使用react-route-dom

1、选择路由模式:在入口文件index.js中

  1. 选择路由模式:BrowserRouter 历史记录路由模式;HashRouter 哈希路由模式

  2. <BrowserRouter>

    <app/>

    </BrowserRouter> 路由模式包裹根组件

import React from "react";  // 引入react => 获取到react提供的api
import ReactDOM from "react-dom/client";    // 将虚拟dom => 真实dom
import "./index.css";
import App from "./App";    //引入根组件
import { BrowserRouter,HashRouter } from "react-router-dom";   // BrowserRouter => 选择路由模式 => history

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
  {/* 
    这个里面的组件 如果要使用路由 就会采用history => 本质 provider => 在父组件中提供数据,在自己组件就是可以使用 react-router-dom 提供的api 提供的那些东西 => 路由模式, 路由api 
  */}
  {/* react => 组件渲染的结构 => 父子结构 */}
    <App />
  </BrowserRouter>
);

三、创建路由表:写组件和路径对应关系

  1. 在选择路由模式后,通过 react-router-dom提供的api 来实现,可以使用全局组件:

    1. Routes 路由表 2. Route 路由信息 3. Link 相当于a标签 可以跳转路由 4. OutLet 路由占位符

  2. 路由模块化:配置路由表

    1. 在sr目录下创建一个文件夹=》router/index.js文件 =》创建路由表=》就是一个组件

    2. 有几个属性:path 写路径 element写组件

    3. <Routes>

      <Route path="/login" element={<Login></Login>}></Route>

      </Routes>

    4. 在到根组件app.js中进行引入 =》使用这个组件

  3. 嵌套路由

    1. 在按个路由组件进行嵌套 => 直接添加路由信息组件

    2. 嵌套路径 的路径会进行拼接

    3. 根据路径匹配嵌套路由 => 路由占位符 <OutLet> </OutLet>=> 在哪里展示这个嵌套路由就需要在哪里写路由占位符

import { Routes, Route } from "react-router-dom";
// Routes =》 路由表
// Route  =>  路由信息

import { useNavigate } from "react-router-dom";
// 从首页跳转到login
// 语法: useNavigate() =》 返回值也是一个方法,这个方法有两个参数

// Link => 相当于A标签
// let routes =[
// {path:'/',component:'组件'}
// ]

// 四 创建路由表
// 通过react-router-dom提供的api 来实现
//  有几个属性 => path element

//  五 链式的路由跳转
// 方式 push , replace , go
// 1 push :浏览器历史栈中有记录,可以回退
// 2 reaplace: 浏览器历史栈没有记录,不可以回退
// 3 go   : 前进和后退

// 八 嵌套路由
// 概念:在页面级组件中展示页面级组件

// 用法: 1 配置嵌套路由

import Index from "../views/Index/Index";
import Login from "../views/Login/Index";
import Me from "../views/Me/Index";
import IndexShow from "../views/IndexShow/Index";
import NotFind from "../views/NotFind/Index";

function RouterList() {
  return (
    <Routes>
      <Route path="/" element={<Index></Index>}>
        {/* 
        嵌套路由
            1. 在按个路由组件进行嵌套 => 直接添加路由信息组件
            2. 嵌套路径 的路径会进行拼接
            3. 根据路径匹配嵌套路由 => 路由占位符 => 在哪里展示这个嵌套路由就需要在哪里写路由占位符
    */}
        <Route path="indexshow" element={<IndexShow></IndexShow>}></Route>
      </Route>
      <Route path="/login" element={<Login></Login>}></Route>
      <Route path="/Me" element={<Me></Me>}></Route>
      <Route path="*" element={<NotFind></NotFind>}></Route>
    </Routes>
  );
}

export default RouterList;

展示嵌套路由,在哪里展示这个嵌套路由就需要在哪里写路由占位符 "../views/Index/Index";

 return(
        <div>
            index
            <button onClick={()=>goPath()}>路由传递参数 可见</button>
            <button onClick={()=>goPath2()}>路由传递参数 不可见2</button>

            <Outlet></Outlet>
        </div>
    )

4、实现路由懒加载

作用:用户没有进入到这个页面,这个页面的代码就不会在首次加载项目的时候,加载处理,提供项目首次加载的速度

语法:React.lazy() 结合 Suspense

React.lazy() 语法 =》 React.lazy(()=>import('组件路径')) =》路由懒加载组件

Suspense 语法=》 < Suspense fallback ={先展示的组件(异步组件)} ></Suspense>

Suspense不能直接包围整个路由表,否则会出现页面创建两次的bug

哪些需要展示异步组件,路由懒加载的就包裹哪些组件即可

/* 
  十二: 路由懒加载
        作用:用户没有进入到这个页面,这个页面的代码就不会在首次加载项目的时候,加载处理,提供项目首次加载的速度


        // 语法 :React.lazy() 结合Suspense

        React.lazy()  语法 => React.lazy(()=>import('组件路径'))  => 路由懒加载组件

        Suspense  语法=> <Suspense fallback={先展示的组件}></Suspense>
        
        Suspense
*/

import { Routes, Route,Link } from "react-router-dom";
import React,{Suspense} from "react";

/* 
  路由懒加载 + 异步组件
    React.lazy(()=>{}) => 返回值 就是 我们的路由懒加载数据

    // Suspense =》 结合懒加载使用,当路由懒加载组件还没有显示,先加载Suspense 提供组件
   
*/

// 写一个公共的方法 处理懒加载组件=》他的返回值就是懒加载组件



import Index from "../views/Index/Index";
import Login from "../views/Login/Index";
// import Me from "../views/Me/Index";
// import IndexShow from "../views/IndexShow/Index";
import NotFind from "../views/NotFind/Index";

// 封装路由懒加载的方法
function ChangeLazyC(name){
       let Com
        Com  = React.lazy(()=>import(`../views/${name}/Index`))  //返回懒加载组件
        return <Com></Com>
}

function RouterList() {
  return (

  <Suspense fallback={<div>loading...</div>}>
    <Routes>

      <Route path="/" element={<Index></Index>}>
        <Route path="indexshow" element={ChangeLazyC('IndexShow')}></Route>
      </Route>

      <Route path="/login" element={<Login></Login>}></Route>

      <Route path="/Me" element={ChangeLazyC('Me')}></Route>

      <Route path="*" element={<NotFind></NotFind>}></Route>

    </Routes>
  </Suspense>
  );
}

export default RouterList;

优化封装 自动处理成 懒加载组

//写一个公共的方式 处理懒加载组件 =>他的返回值就是懒加载组件

function ChangeLazyC(name){
       let Com
        Com  = React.lazy(()=>import(`../views/${name}/index`))  //返回懒加载组件
        return <Com></Com>
}

4、在根组件App.js中引入路由表

  1. 在根组件中引入路由表

import './App.css';

import { Link } from 'react-router-dom';
// 引入路由表
import RouterList from './router/index'
import useRedirect from './useHooks/routerRedirect';
import useBeforEach from './useHooks/routerBeforEach';
import useRouteReEa from './useHooks/routerReEa';

// 创建路由表 => 全局组件 => react-router-dom

// Link => 相当于A标签
// let routes =[
  // {path:'/',component:'组件'}
// ]

function App() {
  // useRedirect();    // 路由重定向
  // useBeforEach();   // 路由守卫
  useRouteReEa();   // 路由重定向 路由守卫 合并
  return (
    <div className="App">
      
      <div>
        <Link to="/">首页</Link> | <Link to='/login'>登录</Link>
      </div>
      <RouterList></RouterList>    // 引入路由表
    </div>
  );
}

export default App;

四、链式的路由跳转

  1. 通过 react-router-dom提供的api : useNavigate()导航

  2. 语法:let navigate = useNavigate(); 返回值也是一个方法navigate,这个方法有两个参数,参数一:路由,参数二:配置项{ },改变跳转方式 和 路由传递参数

  3. navigate('/跳转路由',{跳转方式:true})

  4. 跳转方式:

  5. push:语法:navigate('/跳转路由'); 浏览器历史栈中有记录,可以回退;它是默认跳转方式;

  6. reaplace:语法:navigate('/路由', {replace:true}); 浏览器历史栈中没有记录,不可以回退

  7. go: 语法:navigate(1) 正数前进 ; navigate(-1) 负数后退

import {useNavigate,Outlet} from 'react-router-dom'

// 从首页跳转到 login
// 语法: useNavigate() => 返回值也是一个方法,这个方法有两个参数
    // 参数一:路由
    // 参数二:配置项 => {}  => 改变跳转方式 和 路由传递参数
    
// function Index(){
//     let navigate=useNavigate(); // 路由跳转的方式useRouter
//     // console.log(navigate)
//     const goLogin=()=>{
//         navigate('/login'); // 默认是push 跳转方式
//     }
//     const goLoginR=()=>{
//         navigate('/login',{replace:true});  // replace
//     }
//     const goLoginG=()=>{
//         navigate(1)
//     }

//     return(
//         <div>
//             <h2>这里是首页哦!</h2> 
//            <button onClick={()=>{goLogin()}}>去登录 push</button> 
//            <button onClick={()=>{goLoginR()}}>去登陆 replace</button>
//            <button onClick={()=>{goLoginG()}}>Go/Back go</button>
//         </div>
//     )
// }

// export default Index

// /* 
//     总结 路由跳转的方式

//         1 push => 默认的
//         2 replace => 需要自己进行配置   => useNavigate()
//         3 go    => useNavigate()   => 返回方法 它的参数写数字(正数前进,负数后退)
// */

五、路由传递参数

  1. 路由参数在路由地址上可见

  2. 传递路由参数: 字符串模板拼接(最多使用) navigate(/login?id${要传递的参数} && ids=300)

  3. 获取路由参数:通过 react-router-dom提供的api : useLocation() 1. useLocatinos(获取当前url上的信息); // 相当于vue-router中的useRoute路由信息 2. 语法:let location = useLocation(); 1. 通过location.search; 获取到的路由参数是一个字符串 2. 利用插件利用插件query-string 解析url上面的参数

  4. 路由参数在路由地址上不可见

  5. 传递路由参数: 1. 语法:navigate(/login,{state:{id:100}); // 通过state传参不可见

  6. 获取路由参数:

路由传参:

//  路由传递参数
function Index(){
    
    let navigate=useNavigate();

    // 1 可见  
    // 字符模板拼接 最多
    const goPath=()=>{
        let id=100;
        navigate(`/login?id=${100}&&ids=300`)
        }

    // 不可见
    const goPath2=()=>{
        navigate(`/me`,{
            state:{id:1100}
        })
    }

    return(
        <div>
            index
            <h2>这里是首页哦!</h2> 
            <button onClick={()=>goPath()}>路由传递参数,可见</button>

            <button onClick={()=>goPath2()}>路由传递参数,不可见2</button>
            <Outlet></Outlet>
        </div>
    );
}

export default Index;

获取到路由参数

import { useNavigate, useLocation,Outlet } from "react-router-dom";
import qs from "query-string";

// 获取到路由信息=》 路由参数可见 => 解析过来的参数是一个字符串

//  我们需要将这个字符串变成 =》 对象的形式 => 第三方库 query-string
// 1 项目中安装 =》 npm install query-string
// 2 在那里需要解析url 参数的地方引入 import qs from 'query-string'
// 3 使用它里面的一个方式qs.parse(需要解析的url地址)

// useLocation => 相 当于vue-router  useRoute
// 语法 =》 let location = useLocation()

// 获取到路由参数
// 1) 获取可见的路由参数

function Login() {
  let navigate = useNavigate();
  let location = useLocation();

  // 如果路由参数可以见 =》 获取到的路由参数是一个字符串 ?id=100&&ids=300  =》{id:100,ids:300}

  // 解决方法 -=》 通过第三方的一个库 query-string => 解决 url上面的参数
  console.log(location);
  console.log(qs.parse(location.search));

  const goPath = () => {
    navigate("/me");
  };

  const logins=()=>{
    alert('登录成功');
    sessionStorage.setItem('token','11111')
  }

  //  登录
  return (
    <div>
      <h2>这里是登录页哦!</h2>
      <button onClick={() => goPath()}>去个人页面 me</button>
        <button onClick={()=>{logins()}}>登录</button>
    </div>
  );
}

export default Login;

六、路由重定向

  1. 需要自己定义方法来处理:自定义hooks

1、自定义hooks

  1. 概念:

    1. 就是react 没有给我提供这个 hooks,我自己定义的方法,是按照一定的规则

    2. 以use 声明的方法

    3. 可以是 react 提供的hooks

  2. 案例:实现路由重定向 => 当我访问 首页的路径 => 重定向到首页展示路径

  3. 操作步骤:

    1. 在src目录下定义一个useHooks 存放自定义的hooks

    2. 再创建一个routerRedirect.js文件

    3. 在app文件中引入这个自定义的Hooks,直接调用

import {useLocation,useNavigate} from 'react-router-dom'
import {useEffect} from 'react'

function useRedirect(){
//   实例路由重定向  =》 当我访问 首页的路径 =》重定向到首页展示路径
// 1 知道当前的路径
let location = useLocation()

// 2 在路由跳转
let navigate = useNavigate()

//监听当前的路由信息
useEffect(()=>{
 
    if(location.pathname=='/'){
        navigate('/indexshow',{replace:true})
    }
},[location.pathname])
}

export default useRedirect

在App.js根组件直接使用:useRedirect() //自定义hooks,实现路由重定向

import RouterList from './router/index'
import useBeforEach from './useHooks/routerBeforEach'
import useRedirect from './useHooks/routerRedirect'

function App() {
    useRedirect() //自定义hooks,实现路由重定向
      useBeforEach()    //自定义hooks,实现路由守卫
  
  return (
    <div className="App">
        <RouterList></RouterList>
    </div>
  );
}
export default App;

七、路由守卫

1. 在react中=》我定义自定义hooks

  1. 处理内部页面和外部页面

  2. 案例:判断本地存储中是否有"token", 如果没有就跳转到登录页面

  3. 操作步骤

    1. 在src目录下定义一个useHooks 存放自定义的hooks

    2. 再创建一个routerRedirect.js文件

    3. 在app文件中引入这个自定义的Hooks,直接调用

    4. 在app根组件直接使用:useRedirect() //自定义hooks,实现路由守卫区分内部页面与外部页面

import { useLocation, useNavigate } from "react-router-dom";
import { useEffect } from "react";
function useBeforEach() {
  //在react中=》我定义自定义hooks=>
  //处理内部页面和外部页面

  let location = useLocation();
  let navigate = useNavigate();
  useEffect(() => {
    console.log(location);
    if (location.pathname != "/login") {
      //内部页面
      //判断用户是否登录
      if (!sessionStorage.getItem("token")) {
        // 没有登录
        //去登录页面
        navigate("/login", { replace: true });
      }
    }
  }, [location.pathname]);
}

export default useBeforEach;
合并路由守卫和路由重定向

发现这个路由守卫和路由重定向 都需要监听路由信息 =》进行合拼

import { useLocation, useNavigate } from "react-router-dom";
import { useEffect } from "react";

//这个自定义hooks => 1 实现路由重定向  2实现路由守卫=》判断内部页面和外部页面

function useRouterReEa() {
  let location = useLocation();
  let navigate = useNavigate();

  useEffect(() => {
    if (location.pathname != "/login") {
      //内部页面
      //判断用户是否登录
      if (!sessionStorage.getItem("token")) {
        // 没有登录
        //去登录页面
        navigate("/login", { replace: true });
      } else {
        //登录了
        if (location.pathname == "/") {
          navigate("/indexshow", { replace: true });
        }
      }
    }
  }, [location.pathname]);
}

export default useRouterReEa;

八、处理404页面

在路由表中配置路由

 <Route path='*' element={<NotFind ></NotFind >}></Route>

九、路由优化

  1. react执行机制:只要路由地址发生改变,路由表就会重新执行(所以路由重定向/导航组件不能发在App根组件中,而要放在Layout组件中)

  2. react 路由组件执行流程 => 去到内部页面,需要在执行路由表

  3. 总结:根据后端给的数据动态生成 =》 2 点击登录获取权限 =》 去到内部页面 =》需要重新执行路由表,只能执行一次

  4. Suspense不能直接包围整个路由表,否则会出现页面创建两次的bug

    哪些需要展示异步组件,路由懒加载的就包裹哪些组件即可

    用法见下面代码:

Layout/layout.js文件

import { Outlet } from "react-router-dom";
import "./layout.css";
import useRouterReEa from "../useHooks";


function Layout() {
  // react执行机制:只要路由地址发生改变,路由表就会重新执行(所以路由重定向/导航组件不能发在App根组件中,而要放在Layout组件中)
  useRouterReEa();
  // 这个侧边栏导航的数据需要动态生成
  // 权限: 1 基础版本 => 前端 => 内部页面和外部页面 =》 路由守卫 后端:内部接口和外部接口     官网
  //        2 进阶版本 =》 后台管理系统  =》 页面 =》 1 侧边栏导航 2 动态路由
  //        3 中级版本 =》 后台管理系统  =》 自己可以设置权限

  // 这个侧边栏导航的数据需要的动态生成 =》 根据用户登录的时候获取
    let navList = ["dashBord","order","Index"];
    
  return (
    <div>
    <div className="header">内部管理系统</div>
      <div className="layout">
        <div className="leftNav">
            {
                navList.map((item,index)=>{
                    return (
                        <div key={index}>{item}</div>
                    )
                })
            }
        </div>
        <div className="rightNav">
            <Suspense fallback={<div>loading...</div>}>    
                <Outlet></Outlet>
            </Suspense>
        </div>
      </div>
    </div>
  );
}

export default Layout;

router/index.js文件

import { Routes, Route } from "react-router-dom";
import React, { Suspense } from "react";

import Layout from "../Layout/layout";
import Login from "../views/Login/index";


// 路由懒加载
function LazyC(name) {
  let Com;
  Com = React.lazy(() => import(`../views/${name}/index`));
  return <Com></Com>;
}

function RouterList() {

console.log('路由表');

  // 需要获取到根据权限登录的时候后端返回的数据

  // 因为登录页面 =》 用户第一次进入需要加载页面 =》['dashBord','Index','Play']

  let navList=['dashBord','Index','Play']

  return (
    <Suspense fallback={<div>loading...</div>}>        // 把这里注释掉,放到包裹到具体的组件上
      <Routes>
        <Route path="/login" element={<Login></Login>}></Route>
        <Route path="/layout" element={<Layout></Layout>}>
            {/* /layout/bashbord */}
            {ListItem(navList)}
         
        </Route>
      </Routes>
    </Suspense>        // 把这里注释掉,放到包裹到具体的组件上
  );
}

function ListItem(list){
  if(list.length>0){
    return list.map((item,index)=>{
      return(<Route key={index} path="dashbord" element={LazyC("DashBord")}></Route>)
    })
  }
}

// 2 react 路由组件执行流程 =》 去到内部页面,需要在执行路由表 =》 redux(项目中讲)

// 总结 =》
  // 1 根据后端给的数据动态生成 =》 2 点击登录获取权限 =》 去到内部页面 =》需要重新执行路由表,只能执行一次

export default RouterList;