08-React路由(Router 6版本)

发布时间:2023年12月30日

Router5和Router6的变化

部分标签产生了变化,之前的标签都有了替(主要集中在Route匹配上),所以这里先回顾一下Router5,同时引出Router6的一些新特性

其次,React官方在推出Router6之后,就明确推荐函数式组件了

破坏性变更1:废弃 Switch 组件,由 Routes 代替(使用了智能匹配路径算法)

注意:<Routes/>组件里面只接收<Route/>标签,其他的标签放进去一律报错

之前的<Switch/>标签,主要是用于解决同路径匹配问题(类似case穿透的问题)

<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/home" component={Test}></Route>

这是两个路由组件,在2,3行中,我们同时使用了相同的路径 /home,此时因为没有<Switch/>标签,就会出现类似case穿透的现象,匹配到了一个之后还会继续向下匹配,所以就会出现所有匹配上路径的标签都会被显示出来
以上就是Router5版本中<Switch/>标签的作用


在Router6版本中,<Switch/>标签被废除,引入了<Routes/>标签作为他的替代
<Switch/>标签是可选项,不选顶多会被多匹配路径
到了<Routes/>,就是必选项,如果不套上标签,就会报错

在这里插入图片描述

修改之后:

<Routes>
  <Route path="/about" element={<Abouts />} />
  <Route path="/home" element={<Home />} />
  <Route path="/home" component={Test} />
</Routes>
<Routes><Route>要配合使用,且必须要用<Routes>包裹<Route><Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。

<Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false,不区分大小写)。

当URL发生变化时,<Routes>都会查看其所有子<Route> 元素以找到最佳匹配并呈现组件(单一匹配,匹配上一个就不会往下匹配) 。

<Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。

破坏性变更2:Route标签废弃component属性改用element

注意,是element,不是elements😂,因为每个Route标签只匹配一个路由的标签,所以单数的element即可

V5版本,匹配组件的属性为component={组件}

<Switch>
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />
</Switch>

V6版本,匹配组件的属性替换成element={组件}

<Routes>
  <Route path="/about" element={<Abouts />} />
  <Route path="/home" element={<Home />} />
</Routes>

破坏性变更3:废弃Redirect,由Navigate代替

之前作为路由的兜底路径匹配(输入一个不在Route注册的路径,就会被兜底路径兜住),在Router6中,<Redirect/>标签被删除,改用<Navigate/>标签替代。但注意,<Navigate/>标签不可以直接暴露在<Routes/>标签里。否则就会报错,也就是<Routes/>组件只接收<Route/>作为其子组件。其他组件放进去一律报错。

将Navigate组件定义在<Route/>组件的element属性中,因为星号的匹配规则定义在所有路由的最后,所以只有前面的路由都没有匹配时,才会命中(相当于兜底),而<Navigate/>组件做的事很简单,就是把错误的路由重定向到一个指定的路径上(Route只管匹配,剩下的事交给element属性里的<Navigate/>来做)

<Navigate/>组件属性: <Navigate to="/home" replace /> to代表重定向的路径组件即:[ to="/home"to="{Home}"] 二者均可,且to这个属性,是Navigate的必须属性,必须写上去。replace代表对浏览器history的操作是replace而不是push操作。但具体用不用,要看是否想前进后退按钮亮起。

注意:只要<Navigate/>这个标签渲染了,就会生效去重定向!


在Route上使用的例子:
<Navigate/>重定向到某个组件

     <Routes>
       <Route path="/about" element={<Abouts />} />
       <Route path="/home" element={<Home />} />
       {/* path="*"代表可以匹配任何路径,to代表目标[可传组件或者路径],replace代表浏览器历史记录栈顶替换  */}
       <Route path="*" element={<Navigate to={Home} replace />} />
     </Routes>

<Navigate/>重定向到某个路径,兜底使其回正到/home路径上,就可以走正常路由了

     <Routes>
       <Route path="/about" element={<Abouts />} />
       <Route path="/home" element={<Home />} />
       {/* path="*"代表可以匹配任何路径,to代表目标[可传组件或者路径],replace代表浏览器历史记录栈顶替换  */}
       <Route path="*" element={<Navigate to="/home" replace />} />
     </Routes>

验证一下只要渲染<Navigate/>就生效的例子:
在某个组件里,定义一个变量,当变量变为2时,就会触发<Navigate/>标签渲染,路由变更,url变化完成页面迁移
App组件

import React from "react";
import {
  Routes,
  Link,
  Route,
  Navigate,
} from "react-router-dom";
import Home from "./components/Home/Home";
import About from "./components/About/About";

export default function App() {
  return (
    <div>
      <div>
        <div className="row">
          <h2>React Router Demo</h2>
        </div>
      </div>
      <div className="row">
        <Link className="list-group-item" to="/about">
          About
        </Link>
        <Link className="list-group-item" to="/home">
          Home
        </Link>

        <div>
        // 基础路由
          <Routes>
            <Route path="/about" element={<About />} />
            <Route path="/home" element={<Home />} />
            <Route path="/" element={<Navigate to={"/about"} />} />
          </Routes>
        </div>
      </div>
    </div>
  );
}

About组件:

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

export default function About() {
  const [count, setCount] = React.useState(1);
  return (
    <div>
      About组件当前值{count}
      {/* 判读是否为2,为2就渲染Navigate组件,并且重定向到home上 */}
      {count == 2 ? <Navigate to={"/home"} /> : <h1></h1>}
      <br></br>
      <button
        onClick={() => {
          setCount(2);
        }}
      >
        点击变成2
      </button>
    </div>
  );
}

Home组件:

export default function Home() {
  return <div>Home</div>;
}

在这里插入图片描述

破坏性变更4:使用 useNavigate 代替 useHistory

这是第三个破坏性变更:废弃useHistory,由useNavigate代替。这个属于Hooks的范畴,会在新增特性里面详细说明

v6 版本写法

const App = () => {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate('/home');
  };

  return (
    <div>
      <button onClick={handleClick}>返回首页</button>
    </div>
  );
};

对比于之前操作history对象的写法变更:

history.push("/") => navigate("/")
history.replace("/") => navigate("/",{ replace: true })
history.goBack() => navigate(-1)
history.goForward() => navigate(1)
history.go(2) => navigate(2)

另外,navigate 还支持与 Link 相同的相对路径写法

总结一下<Routes/><Route/>

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

  2. <Routes><Route>要配合使用,且必须要用<Routes>包裹<Route>

  3. <Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。

  4. <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。

  5. 当URL发生变化时,<Routes> 都会查看其所有子 <Route> 元素以找到最佳匹配并呈现组件 。

  6. <Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。

  7. 示例代码:
    这里使用Route标签实现的嵌套,后面会用路由表来做路由嵌套,更加推荐用路由表去左路由嵌套

<Routes>
    /*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/
    <Route path="/login" element={<Login />}></Route>

		/*用于定义嵌套路由,home是一级路由,对应的路径/home*/
    <Route path="home" element={<Home />}>
       /*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/
      <Route path="test1" element={<Test/>}></Route>
      <Route path="test2" element={<Test2/>}></Route>
		</Route>
	
		//Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx
    <Route path="users">
       <Route path="xxx" element={<Demo />} />
    </Route>
</Routes>

Router6新增特性

NavLink样式更新

  1. 作用: 与<Link>组件类似,且可实现导航的“高亮”效果。

  2. 示例代码:

// 注意: NavLink默认类名是active,下面是指定自定义的class

//自定义样式
<NavLink
    to="login"
    className={({ isActive }) => {
        console.log('home', isActive)
        return isActive ? 'base one' : 'base'
    }}
>login</NavLink>

/*
	如果是子NavLink亮起的时候,不希望父NavLink亮起,就可以加一个end属性,来阻止父NavLink的亮起

	默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
	当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
*/
<NavLink to="home" end >home</NavLink>

useRoutes路由表

简单来说,就是把路由定义在外部文件,这个文件里定义了路由的path以及对应的组件显示element

一般来说我们把这个路由定义在route目录下,并且命名index.js。也就是route/index.js


注意路径写法
path有几种写法:

  • “/路径” 这是绝对路径,无论之前是否有父级目录路径,当前路径都直接作为绝对路径
  • “./路径” 这是相对路径,相对于当前父级目录下,再追加一个子路径
  • “路径” 只单写一个路径,和相对路径的效果是一样的

路由表:

import About from "../components/About/About";
import Home from "../components/Home/Home";
import { Navigate } from "react-router-dom";

export default [
  {
    path: "/about",
    element: <About></About>,
  },
  { 
    path: "/home", 
    element: <Home></Home>
  },
  {
    path: "*",
    // 重定向通配符,如果匹配不到就走这个
    element: <Navigate to="/home"></Navigate>,
  },
];

在组件里使用路由表来替代路由(路由本身就不是显示在页面上的,而是隐藏等待匹配的)

import React from "react";
import { Routes, useRoutes, Link, Route, Navigate } from "react-router-dom";
// 导入路由表
import routes from "./route";

export default function App() {
  // 解构出路由表,根据路由表生成对应的路由
  const element = useRoutes(routes);
  return (
    <div>
      <div>
        <div className="row">
          <h2>React Router Demo</h2>
        </div>
      </div>
      <div className="row">
        <Link to="/about">
          About
        </Link>
        <Link to="/home">
          Home
        </Link>
        <div>
          {/* 使用路由表,被解析成a标签 */}
          {element}
          
          {console.log(element)}
        </div>
      </div>
    </div>
  );
}

在这里插入图片描述

嵌套路由

所谓嵌套路由,就是在路由表里面加一层children属性,使url完成嵌套的操作,使其完成二级甚至n级路由的匹配

//根据路由表生成对应的路由规则
export default [
  {
    path: "/about",
    element: <About />,
  },
  {
    path: "/home",
    element: <Home />,
    children: [
      // 子路由
      {
        path: "new",
        element: <New />,
        children: [
          // 子路由当然还可以再套子路由
          {
            path: "message",
            element: <Message />,
          },
        ],
      },
    ],
  },
  {
    path: "/",
    element: <Navigate to="/home" />,
  },
];

嵌套路由定位 <Outlet>

<Outlet>就是子路由组件渲染的槽位,之前的一级路由,因为有组件里有Route标签,所以他就知道该去哪渲染。当父组件里没有写Route的位置,就无法确定渲染该组件的位置,此时就需要定位用的插槽标签 <Outlet> 。定位在 指定在子组件该呈现渲染的位置,如果不写,路由就不知道该渲染在哪

路由表定义,home下有子路由new组件,当匹配到home的子组件new之后,就会去父组件home里去找子组件的槽位 <Outlet>,并把子组件渲染到这个槽位上
路由表,三级路由:/home/new/message

  {
    path: "/home",
    element: <Home />,
    children: [
      // 子路由
      {
        path: "new",
        element: <New />,
        children: [{ path: "message", element: <Message /> }],
      },
    ],
  },

子路由标签,定义好槽位

import React from "react";
import { Outlet } from "react-router-dom";

export default function Home() {
  return (
    <h3>
      我是Home的内容
      <div>
      	// 这个槽位是匹配home的children子组件
        组件槽位:<Outlet></Outlet>
      </div>
    </h3>
  );
}

// New组件
export default function New() {
  return (
    <div>
      New
      <div>
      	// 这个槽位是匹配New的children子组件
        组件插槽<Outlet></Outlet>
      </div>
    </div>
  );
}

// Message组件
export default function Message() {
  return <div>Message</div>;
}

在这里插入图片描述

params参数传递(useParams())

之前的获取params参数,是通过const { id, title } = this.props.match.params;类似这种方式获取参数。但对于函数式组件来说,因为没有this对象了无法直接这么搞,所以就专门为了函数式组件来获取路由参数,搞了一个钩子 useParams()

this.props.match.params这种获取方式后面有对应的Hooks来模拟,不是说彻底就不要了,只是更推荐useParams()

这里数据需要在路由里占位
定义路由表的路径比如:detail/:id/:title/:title,这个是和定义的子组件绑定
路由表:

{
    path: "/home",
    element: <Home />,
    children: [
      // 子路由,定义好参数占位,这个参数是和Detail组件绑定的
      {
        path: "detail/:id/:title/:content",
        element: <Detail />,
      },
    ],
  },

Detail组件里:

import React from "react";
import { useParams } from "react-router-dom";

export default function Detail() {
  // useParams() param钩子,在对应组件里面,直接解构就能拿到url传递的参数
  // 因为我们之前,就在路由表里已经定义好了,所以可以直接匹配上
  const { id, title, content } = useParams();
  console.log(id, title, content);
  return <div>Detail</div>;
}

打一个url

http://localhost:3000/home/detail/123/zhangsan/abcdef

在这里插入图片描述

补充一个match的钩子

作用:返回当前匹配信息,对标5.x中的路由组件的match属性。
用得不多,优势在于性能好,这里了解即可

同样提供了钩子 useMatch(完整路径)
这个用的很少,就是复刻match属性
要在对应组件里匹配到完整路径(路径只用资源名即可):useMatch(/home/detail/:id/:title/:content)
即可以获取到类似this.props.match.params
在这里插入图片描述

search参数传递(useSearchParams())

search传递参数,是从这种url里面拿数据:
http://localhost:3000/home?id=008&title=哈哈&content=嘻嘻

和上面的param一样,之前的search传递参数,是依靠于queryString的这种第三方库,但是在Router6之后,就引入了官方的参数解析钩子useSearchParams()

这个钩子一般这么实现:
const [search, setSearch] = useSearchParams()

常用的都是用search对象,并且调用search身上的get方法来获取key

home组件内定义即可

import React from "react";
import { useSearchParams } from "react-router-dom";

export default function Home() {
  // setSearch一般不用,后面会介绍
  const [search, setSearch] = useSearchParams();
  // 通过useSearchParams定义好的search,来get("传入对应的key")
  const id = search.get("id");
  const name = search.get("title");
  const content = search.get("content");
  return (
    <div>
      <div>id:{id}</div>
      <div>name:{name}</div>
      <div>content:{content}</div>
    </div>
  );
}

在这里插入图片描述

setSearch的参数,没太大作用,就是原地更新一下url重新触发一下钩子渲染

import React from "react";
import { useSearchParams } from "react-router-dom";

export default function Home() {
  const [search, setSearch] = useSearchParams();
  // 通过useSearchParams定义好的search,来get("传入对应的key")
  const id = search.get("id");
  const name = search.get("title");
  const content = search.get("content");
  return (
    <div>
      <button onClick={() => setSearch("id=008&title=哈哈&content=嘻嘻")}>
        点我更新一下收到的search参数
      </button>
      <div>id:{id}</div>
      <div>name:{name}</div>
      <div>content:{content}</div>
    </div>
  );
}

在这里插入图片描述

路由的state参数(useLocation())

在router5中,我们支持路由Link的state参数是这个样子的
加粗样式

到了Router6就换了一种写法,分别影响Link对应组件获取参数的方式

对于Link来说,可以直接传递state属性了,也就是和to拆开写了:

 <Link to="/home" state={{ id: 123, age: 23, name: "zhangsan1" }}>
   Home
 </Link>

对于接收组件来说,因为不能用this.props.location来获取,所以只能通过useLocation()的钩子来获取location对象

// 钩子获取
const obj = useLocation();
console.log(obj);

例子代码:

// App组件
<Link to="/home" state={{ id: 123, age: 23, name: "zhangsan1" }}>
  Home
</Link>

// Home组件
const obj = useLocation();
console.log(obj);
--- 或者也可以连续解构拿出来,效果也一样,更直接了 --- 
const {state:{id,age,name}} = useLocation();
console.log(id,age,name);

在这里插入图片描述

编程式路由导航

在Router5中,编程式路由导航的核心是this.props.history.xxx
对应的history对象提供了很多api

history:
	go: ? go(n)
	goBack: ? goBack()
	goForward: ? goForward()
	push: ? push(path, state)
	replace: ? replace(path, state)

但是在router6中,因为没有this了,所以就没办法再用这个api去实现编程式路由导航,比如这种就实现不了

  replaceShow = (id, title) => {
    // 这里注意用箭头函数向外扩散找this对象,否则会造成死循环
    // 通过变更url传参
    return () => this.props.history.push(`/home/message/${id}/${title}`);
  };

此时就需要用全新的钩子useNavigate
之前我们学过的 <Navigate to="/home" replace />,必须渲染了才能生效进行跳转,也就是需要写在render里面。要不然不渲染就没法生效。

而编程式路由导航,往往是定义在函数里,也就是render以外的部分,所以必须借助useNavigate钩子来操作

import React from "react";

export default function Message() {
  function testMethod() {
    // 编程式路由导航都是在这里写
    // 因为不再render里,所以<Navigate to="/home" replace />这种标签渲染的跳转不会生效
  }
  return (
    <div>
      我是Message
      <button onClick={testMethod}>触发编程式路由导航</button>
    </div>
  );
}

直接定义一个变量接收钩子的返回参数即可,一般我们都叫做navigate
也就是const navigate = useNavigate() 这里的navigate对象就是之前的history对象
可以对其进行操作

import React from "react";
import { useNavigate } from "react-router-dom";

export default function Message() {
  const navigate = useNavigate();
  function testMethod() {
    navigate(
      //目标跳转路径,相对路径或者绝对路径都可以
      "detail",
      {
        // 是否替换栈顶历史记录
        replace: false,
        // 给目标组件传递的数据,这里key必须叫state,(只能是传state参数,组件里用useLocation()获取参数)
        state: {
          id: "123",
          name: "jack",
        },
      }
    );
  }
  return (
    <div>
      我是Message
      <button onClick={testMethod}>触发编程式路由导航</button>
    </div>
  );
}

// Detail组件,注意,要提前在路由表里注册好,要不然跳转了找不到组件
export default function Detail() {
  // useLocation() Location钩子,在对应组件里面,解构就能拿到
  // 连续解构,给state里的参数解出来
  const { state:{ id, name } } = useLocation();
  console.log(id, name);
  return <div>Detail组件</div>;
}

在这里插入图片描述

或者我们也可以用这个对象来操作浏览器前进和后退,和之前router5的操作差不多,传入不同的参数前进或后退

export default function Header() {
	const navigate = useNavigate()

	function back(){
		// 后退
		navigate(-1)
	}
	function forward(){
		// 前进
		navigate(1)
	}

	return (
		<div className="col-xs-offset-2 col-xs-8">
			<div className="page-header">
				<h2>React Router Demo</h2>
				<button onClick={back}>←后退</button>
				<button onClick={forward}>前进→</button>
			</div>
		</div>
	)
}

总结编程式路由导航

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

export default function Demo() {
  const navigate = useNavigate()
  const handle = () => {
    //第一种使用方式:指定具体的路径
    navigate('/login', {
      replace: false, // 是否替换history
      state: {a:1, b:2} // 是否替换state
    }) 
    //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
    navigate(-1)
  }
  
  return (
    <div>
      <button onClick={handle}>按钮</button>
    </div>
  )
}

useInRouterContext()

作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。

说白了就是有没有被<BrowserRouter>或者<HashRouter>包裹,如果被包裹了,那这个钩子就true,没有就flase

  const isInRouter = useInRouterContext();

这有啥用?一般封装组件的场景,就需要知道是否在路由环境下使用你的组件,这个时候就需要判断一下了

useNavigationType()

  1. 作用:返回当前的导航类型(用户是如何来到当前页面的)。
  2. 返回值:POPPUSHREPLACE
  3. 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。PUSHREPLACE这两种取决于路由Link是否开启了rplace属性。
  const navigateType = useNavigationType();

useOutlet()

  1. 作用:用来呈现当前组件中渲染的嵌套路由。就是判断<Outlet/>标签槽位上的目标组件有没有被渲染出来。

  2. 示例代码:

const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象

在这里插入图片描述

useResolvedPath()

作用:给定一个 URL值,解析其中的:path、search、hash值。

//  /后面是path,?后面是search参数,#后面是hash值
console.log(useResolvedPath("/user?id=001&name=jack#qwer")); 

在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_46906696/article/details/135145705
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。