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><buttononClick={() => {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/>
-
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>
Router6新增特性
NavLink样式更新
-
作用: 与
<Link>
组件类似,且可实现导航的“高亮”效果。 -
示例代码:
// 注意: NavLink默认类名是active,下面是指定自定义的class//自定义样式
<NavLinkto="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, // 是否替换historystate: {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()
- 作用:返回当前的导航类型(用户是如何来到当前页面的)。
- 返回值:
POP
、PUSH
、REPLACE
。 - 备注:
POP
是指在浏览器中直接打开了这个路由组件(刷新页面)。PUSH
、REPLACE
这两种取决于路由Link是否开启了rplace属性。
const navigateType = useNavigationType();
useOutlet()
-
作用:用来呈现当前组件中渲染的嵌套路由。就是判断
<Outlet/>
标签槽位上的目标组件有没有被渲染出来。 -
示例代码:
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"));