React基础知识三 router路由全指南
现在最新版本是Router6和Router5有比较大的变化,Router5和Router4变化不大,本文以Router6的写法为主,也会对比和Router5的不同。比较全面。
安装路由
npm i react-router-dom
基本使用
有两种Router,BrowserRouter和HashRouter,选哪个都可以,这里以HashRouter为例子,BrowserRouter用法上没什么区别。
先给App组件套上一层Router,这样就可以使用路由功能了。
import { BrowserRouter, HashRouter } from "react-router-dom";
root.render(
<React.StrictMode>
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
);
或者
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
在App里面我们需要定义一个Routes组件,以及他的子组件Route,Route会将path映射成对应的组件。也就是动态加载我们的Home或者About组件。
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
function App() {
return (
<div className="App">
<div>Header</div>
<div>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
<div>Footer</div>
</div>
);
}
export default App;
Home和About就展示文本
import React from "react";
class Home extends React.PureComponent {
render() {
return <div>Home Page</div>;
}
}
export default Home;
在浏览器中输入路径,注意我们使用的是HashRouter,所以路径前面需要加#号。
http://localhost:3001/#/home
效果就是Home组件的内容确实被动态加载了。
使用Link组件实现点击切换
上面的代码可以使用浏览器输入路径动态的切换内容。但一般用户不会直接在浏览器输入地址。一般我们会给个导航栏让用户点击,这个功能用Link就可以很容易实现。
Link内容直接写显示的文本,以及一个to属性用于指定路径,和Route的path属性对应上就可以了。
import { Routes, Route, Link } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
function App() {
return (
<div className="App">
<div>
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
</div>
<div>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
<div>Footer</div>
</div>
);
}
export default App;
这样,就可以通过Link组件实现点击动态切换路由内容了。
404页面
当所有的路径都匹配不到的时候,就匹配到了*。
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="*" element={<NotFound />} />
Link的一些额外属性
- replace 是否重定向,默认false
- reloadDocument 是否出现加载文档,会导致填的数据丢失,默认false
- state 可以传值,在目标组件使用useLocation可以获取到值。
<Link to="/home" replace={false} reloadDocument={false} state={{name:"Tom"}}>
NavLink的使用(了解)
NavLink可以使链接样式有一些变化。但实际上我们是很少使用的,因为我们一般都自定义。作为了解就可以了。
当我们把Home组件的Link替换成NavLink的时候,当Home组件的NavLink被点击的时候,我们查看源代码,发现,a元素上面多了一个active的class。但NavLink被点击的时候,我们可以通过这个active class来做一些样式上的变化。
我们直接定义一个class,在组件里面引入,在点击的时候就会触发这个效果了。
.active {
color: red;
font-size: 20px;
}
支持下面的写法,函数参数是arguments,我们可以解构出isActive ,这是这个组件提供的。
<NavLink
to="/home"
style={({ isActive }) => ({ color: isActive ? "red" : "green" })}
>
首页
</NavLink>
也可以提供指定className的形式。
<NavLink
to="/about"
className={({ isActive }) => (isActive ? "linkActive" : "")}
>
关于
</NavLink>
这两个方式都可以简单的改变样式。
Navigate组件的使用
Navigate这个组件是用于重定向的。只要他被显示,那么组件就会被重定向。可是实现登录跳转。
我们添加一个Login组件。并做路由配置。
实现登录跳转
import { Routes, Route, Link, NavLink } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Login from "./pages/Login";
import "./pages/style.css";
function App() {
return (
<div className="App">
<div>
<NavLink
to="/home"
style={({ isActive }) => ({ color: isActive ? "red" : "green" })}
>
首页
</NavLink>
<NavLink
to="/about"
className={({ isActive }) => (isActive ? "linkActive" : "")}
>
关于
</NavLink>
<NavLink
to="/login"
className={({ isActive }) => (isActive ? "linkActive" : "")}
>
登录
</NavLink>
</div>
<div>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
</Routes>
</div>
<div>Footer</div>
</div>
);
}
export default App;
做一个条件判断,只要满足条件,我们就展示Navigate,然后组件就会跳转到指定的路由。
import React from "react";
import { Navigate } from "react-router-dom";
class Login extends React.PureComponent {
state = {
isLogin: false,
};
handleLogin() {
this.setState({
isLogin: true,
});
}
render() {
const { isLogin } = this.state;
return (
<div>
Login Page
{!isLogin ? (
<button onClick={(e) => this.handleLogin()}>登录</button>
) : (
<Navigate to="/home" />
)}
</div>
);
}
}
export default Login;
路径重定向
我们可以把/重定向到Home。
<Route path="/" element={<Navigate to="/home" />} />
路由嵌套
也就是二级路由,这个是很常见的。也不难,还是比较方便的。
需要给路由定义子路由。并且在路由是/home的时候,重定向到/home/recommends,这样可以避免,第一次进来二级内容是空白的情况。
App.js
<Routes>
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<Navigate to="/home/recommends" />} />
<Route path="/home" element={<Home />}>
<Route path="/home/recommends" element={<HomeRecommends />}></Route>
<Route path="/home/hots" element={<HomeHots />}></Route>
</Route>
</Routes>
在具体的页面设置Link组件,并且放一个Outlet组件作为占位符。
<div>
Home Page
<div>
<Link to="/home/recommends">推荐</Link>
<Link to="/home/hots">热门</Link>
<Outlet />
</div>
</div>
通过代码实现路由跳转
有时候,我们需要通过代码实现路由跳转。在router6里面,我们只能通过useNavigate这个hooks来实现代码路由跳转。用了hooks,我们只能使用函数组件。如果一定要在类组件里面实现代码路由跳转,我们只能自定义一个高阶函数withRouter(也是router6的写法)。
使用useNavigate hooks
虽然是通过代码实现路由跳转,但Route还是要手动定义的,这是跳转是通过代码实现的。
<Route path="/download" element={<Download />} />
<Route path="/shop" element={<Shop />} />
我们在原来的Link后面添加下面两个元素,我们自己实现点击事件
<button onClick={(e) => handleNavigate("/download")}>下载</button>
<span onClick={(e) => handleNavigate("/shop")}>商城</span>
使用useNavigate来实现跳转。这样就已经实现了通过代码实现路由跳转的功能了。
function App() {
//这行必须写在函数外面
const navigate = useNavigate();
function handleNavigate(path) {
navigate(path);
}
return ...div...
}
使用自定义实现高阶函数withRouter
这个自定义的高阶组件,内部也是使用了useNavigate,所以是router6才可以这么用。router5是自带一个withRouter函数的,只是这个函数在router6被移除了。我们自己实现的这个只是叫withRouter这个名字,内部实现是不一样的。
因为useNavigate只能在函数组件里面使用,我们怎么在类组件里面也能用呢?
就是使用高阶函数,中间套一个函数组件,并将navigate 放到props里面,经过这个骚操作,我们就可以在类组件的props里面获取到navigate了。
import { useNavigate } from "react-router-dom";
function withRouter(OriginComponent) {
return function (props) {
const navigate = useNavigate();
const router = { navigate };
// 套一个router对象,因为可能会封装别的操作
return <OriginComponent {...props} router={router} />;
};
}
export default withRouter;
这样,我们就可以通过this.props获取到navigate了。
import React from "react";
import withRouter from "../hoc/withRouter";
import { Outlet } from "react-router-dom";
class Shop extends React.PureComponent {
handleNavigate(path) {
const { navigate } = this.props.router;
navigate(path);
}
render() {
return (
<div>
Shop Page
<div>
<span onClick={(e) => this.handleNavigate("/shop/hots")}>热卖</span>
<span> </span>
<span onClick={(e) => this.handleNavigate("/shop/offsale")}>
折扣
</span>
</div>
<Outlet />
</div>
);
}
}
export default withRouter(Shop);
App.js定义我们的Route。
<Route path="/shop" element={<Navigate to="/shop/hots" />} />
<Route path="/shop" element={<Shop />}>
<Route path="/shop/hots" element={<ShopHots />} />
<Route path="/shop/offsale" element={<ShopOffsale />} />
</Route>
效果就是点击商城,再点击热卖或者折扣,也可以切换路由。
路由参数传递 动态路由 查询字符
参数传递和动态路由
下面的形式是动态路由的形式,我们可以根据传递的id来显示不同的页面。
<Route path="/shop/detail/:id" element={<ShopDetail />} />
和获取navigate一样,我们可以获取一个params,那么这个params怎么来的呢?我们需要在withRouter里面封装到props里面。
import React from "react";
import withRouter from "../hoc/withRouter";
class ShopDetail extends React.PureComponent {
render() {
const { params } = this.props.router;
console.log(params);
return <div>ShopDetail id:{params.id}</div>;
}
}
export default withRouter(ShopDetail);
这样,我们就成功实现把params 封装到props里面了。
import { useNavigate, useParams } from "react-router-dom";
function withRouter(OriginComponent) {
return function (props) {
const navigate = useNavigate();
const params = useParams();
const router = { navigate, params };
return <OriginComponent {...props} router={router} />;
};
}
export default withRouter;
我们的事件发起源是下面这个地方,通过navigate把含有id的路径传递给目标路由。
import React from "react";
import withRouter from "./../hoc/withRouter";
class ShopHots extends React.PureComponent {
state = {
goods: [
{ id: "001", name: "商品1" },
{ id: "002", name: "商品2" },
{ id: "003", name: "商品3" },
],
};
navigateToDetail(id) {
console.log(id);
const { navigate } = this.props.router;
navigate("/shop/detail/" + id);
}
render() {
const { goods } = this.state;
return (
<div>
商店热门
<div>
<ul>
{goods.map((item, index) => {
return (
<li onClick={(e) => this.navigateToDetail(item.id)} key={index}>
{item.name}
</li>
);
})}
</ul>
</div>
</div>
);
}
}
export default withRouter(ShopHots);
最后实现的效果如下:
我们点击具体的商品,跳转到商品详情页。
查询字符
我们给关于这个Link传点参数。
<NavLink
to="/about?user=tom&age=18"
className={({ isActive }) => (isActive ? "linkActive" : "")}
>
关于
</NavLink>
在withRouter里面通过useSearchParams把query放到props里面。
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from "react-router-dom";
function withRouter(OriginComponent) {
return function (props) {
const navigate = useNavigate();
const params = useParams();
// const location = useLocation();
// console.log("location:", location);
const [searchParams] = useSearchParams();
const query = Object.fromEntries(searchParams);
const router = { navigate, params, query };
return <OriginComponent {...props} router={router} />;
};
}
export default withRouter;
最后,在目标页面就可以通过props获取到query了。
import React from "react";
import withRouter from "../hoc/withRouter";
class About extends React.PureComponent {
render() {
const { query } = this.props.router;
return <div>About Page:{query.user + " " + query.age}</div>;
}
}
export default withRouter(About);
路由配置
前面写的路由是通过Route组件来实现的,router6提供了配置文件的形式,估计是参考vue的,本质上,react是把配置文件还是转成我们上面写的Route组件的形式。
现在,我们的Route是这些内容:
<div>
<Routes>
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<Navigate to="/home/recommends" />} />
<Route path="/home" element={<Home />}>
<Route path="/home/recommends" element={<HomeRecommends />}></Route>
<Route path="/home/hots" element={<HomeHots />}></Route>
</Route>
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/download" element={<Download />} />
<Route path="/shop" element={<Navigate to="/shop/hots" />} />
<Route path="/shop/detail/:id" element={<ShopDetail />} />
<Route path="/shop" element={<Shop />}>
<Route path="/shop/hots" element={<ShopHots />} />
<Route path="/shop/offsale" element={<ShopOffsale />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</div>
修改成下面的样子:
import { Navigate } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Login from "../pages/Login";
import NotFound from "../pages/NotFound";
import HomeHots from "../pages/HomeHots";
import HomeRecommends from "../pages/HomeRecommends";
import Download from "../pages/Download";
import Shop from "../pages/Shop";
import ShopHots from "../pages/ShopHots";
import ShopOffsale from "../pages/ShopOffsale";
import ShopDetail from "../pages/ShopDetail";
const routes = [
{
path: "/",
element: <Navigate to="/home" />,
},
{
path: "/home",
element: <Navigate to="/home/recommends" />,
},
{
path: "/home",
element: <Home />,
children: [
{
path: "/home/recommends",
element: <HomeRecommends />,
},
{
path: "/home/hots",
element: <HomeHots />,
},
],
},
{
path: "/about",
element: <About />,
},
{
path: "/login",
element: <Login />,
},
{
path: "/download",
element: <Download />,
},
{
path: "/shop",
element: <Navigate to="/shop/hots" />,
},
{
path: "/shop/detail/:id",
element: <ShopDetail />,
},
{
path: "/shop",
element: <Shop />,
children: [
{
path: "/shop/hots",
element: <ShopHots />,
},
{
path: "/shop/offsale",
element: <ShopOffsale />,
},
],
},
{
path: "*",
element: <NotFound />,
},
];
export default routes;
最后原来的位置提供useRoutes把routes传进去,react就帮我们把配置文件解析好转成Route的形式了。
<div>{useRoutes(routes)}</div>
懒加载分包和Suspense
在不使用懒加载的情况下,打包后的js内容全部打包到main这个js文件里面。
我们可以通过下面的语句,对页面进行懒加载。import是webpack提供的特性。
const About = React.lazy(() => import("../pages/About"));
const Home = React.lazy(() => import("../pages/Home"));
在使用懒加载后,多出的js文件就是懒加载分包的js文件。
通常情况下,在加载了懒加载之后,页面会直接崩溃的,但新版本react好像没有这个问题了,如果崩溃,需要添加Suspense,可以有loading的效果,当然fallback可以写空字符串,用户就不会察觉到有loading。
<Suspense fallback="loading...">
<HashRouter>
<App />
</HashRouter>
</Suspense>