部分标签产生了变化,之前的标签都有了替(主要集中在Route匹配上),所以这里先回顾一下Router5,同时引出Router6的一些新特性
其次,React官方在推出Router6之后,就明确推荐函数式组件了
注意:<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> 组件来渲染其子路由。
注意,是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>
之前作为路由的兜底路径匹配(输入一个不在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>;
}
这是第三个破坏性变更:废弃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/>
v6版本中移出了先前的<Switch>
,引入了新的替代者:<Routes>
。
<Routes>
和 <Route>
要配合使用,且必须要用<Routes>
包裹<Route>
。
<Route>
相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。
<Route caseSensitive>
属性用于指定:匹配时是否区分大小写(默认为 false)。
当URL发生变化时,<Routes>
都会查看其所有子 <Route>
元素以找到最佳匹配并呈现组件 。
<Route>
也可以嵌套使用,且可配合useRoutes()
配置 “路由表” ,但需要通过 <Outlet>
组件来渲染其子路由。
示例代码:
这里使用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>
作用: 与<Link>
组件类似,且可实现导航的“高亮”效果。
示例代码:
// 注意: 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>
简单来说,就是把路由定义在外部文件,这个文件里定义了路由的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参数,是通过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
作用:返回当前匹配信息,对标5.x中的路由组件的match
属性。
用得不多,优势在于性能好,这里了解即可
同样提供了钩子 useMatch(完整路径)
这个用的很少,就是复刻match属性
要在对应组件里匹配到完整路径(路径只用资源名即可):useMatch(/home/detail/:id/:title/:content)
即可以获取到类似this.props.match.params
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>
);
}
在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>
)
}
作用:如果组件在 <Router>
的上下文中呈现,则 useInRouterContext
钩子返回 true,否则返回 false。
说白了就是有没有被<BrowserRouter>
或者<HashRouter>
包裹,如果被包裹了,那这个钩子就true,没有就flase
const isInRouter = useInRouterContext();
这有啥用?一般封装组件的场景,就需要知道是否在路由环境下使用你的组件,这个时候就需要判断一下了
POP
、PUSH
、REPLACE
。POP
是指在浏览器中直接打开了这个路由组件(刷新页面)。PUSH
、REPLACE
这两种取决于路由Link是否开启了rplace属性。 const navigateType = useNavigationType();
作用:用来呈现当前组件中渲染的嵌套路由。就是判断<Outlet/>
标签槽位上的目标组件有没有被渲染出来。
示例代码:
const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象
作用:给定一个 URL值,解析其中的:path、search、hash值。
// /后面是path,?后面是search参数,#后面是hash值
console.log(useResolvedPath("/user?id=001&name=jack#qwer"));