安装
# 从 5 开始就放弃了原有的 react-router 库,统一命名为 react-router-dom
npm install react-router-dom
使用
全局路由模式
HashRouter
URL 使用hash(#)
来创建路由- www.example.com/#/
BrowserRouter
采用真实的 URL 资源
入口文件 index.js 中引入路由
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
+import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
+ <BrowserRouter>
<App />
+ </BrowserRouter>
</React.StrictMode>
);
常用路由组件和hooks
组件名 | 作用 | 说明 |
---|---|---|
一组路由 | 代替原来 Switch,所有子路由都用基础的 Router children 来表示 | |
基础路由 | Router 可嵌套,解决了原有 v5 中严格模式 | |
导航组件 | 在实际页面中跳转使用 | |
自适应渲染组件 | 根据实际路由 url 去自动选择组件 |
hooks名 | 作用 | 说明 |
---|---|---|
useParams | 返回当前参数 | 根据路径读取参数 |
useNavigate | 返回当前路由 | 代替原有 v5 中的 useHistory |
useOutlet | 返回根据路由生成的 element | |
useLocation | 返回当前 location 对象 | |
useRoutes | 同 Routers 组件一样,只不过是在 js 中使用 | |
useSearchParams | 用来匹配 URL 中 ? 后面的搜索参数 |
组件直接使用
- Route 必须包裹在 Routes 里面使用(配置路由)
- Link 相当于 a 标签,跳转对应页面
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
<Link to="/">home</Link>
<Link to="/about">about</Link>
嵌套路由
- Route 组件中包裹着多个 Route
<Routes>
<Route path="user" element={<Users />}>
<Route path=":id" element={<UserDetail />} />
<Route path="create" element={<NewUser />} />
</Route>
</Routes>
- index 指定默认路由(多个子路由情况下)
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<About />} />
<Route path="user" element={<User />} />
<Route path="about" element={<About />} />
</Route>
</Routes>
- outlet 预留坑位渲染嵌套路由
路由通配符
- * 只能在 / 后面使用,不可在实际路径中间
/home
/home/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*
NotFound
类路由,可以用 * 替代
<Routes>
<Route path="/" element={<Home />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
</Routes>
获取参数 useParams
和 useSearchParams
useParams()
直接返回 param 对象useSearchParams()
返回一个包含 searchParams 对象、setSearchParams 方法
的对象- 可使用
searchParams.get(key)
获得参数 setSearchParams({key: value})
来改变路由
- 可使用
useNavigate
//js写法
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
//组件写法
function App() {
return <Navigate to="/home" replace state={state} />;
}
//替代原有的go goBack和goForward
<button onClick={() => navigate(-2)}>
Go 2 pages back
</button>
<button onClick={() => navigate(-1)}>
Go back
</button>
<button onClick={() => navigate(1)}>
Go forward
</button>
<button onClick={() => navigate(2)}>
Go 2 pages forward
</button>
集中路由渲染
需知:如要在父路由中显示嵌套路由中的子路由,需要引入
react-router-dom
库中的Outlet
组件
- 入口文件 index.js 中引入
BrowserRouter
,启用全局路由
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
- 新建
src/router/index.js
,配置项目路由 - 引入 react 中的 lazy 方法,实现嵌套路由中的子组件的懒加载,减少首屏需要加载的数据量,避免首屏加载缓慢
- 配合使用 Suspense 组件包裹在 lazy 方法返回值的 node,使用 fallback 属性,实现懒加载完毕后的消息提醒
// index.js
import { lazy, Suspense } from 'react';
const CityList = lazy(() => import("../pages/CityList"));
const AppLayout = lazy(() => import('../pages/AppLayout'));
const Index = lazy(() => import("../pages/Index"));
const News = lazy(() => import('../pages/News'));
const lazyLoad = (children) => {
return <Suspense fallback={<>Loading</>}>
{children}
</Suspense>;
};
const routes = [
{
path: '/',
element: <AppLayout />,
children: [
{
path: '/index',
element: lazyLoad(<Index />)
},
{
path: '/news',
element: lazyLoad(<News />)
}
]
},
{
path: '/cityList',
element: <CityList />
},
];
export { routes };
- 根组件 App.js 中引入
编写好的 routes
// App.js
import './App.css';
import { useRoutes } from 'react-router-dom';
import { routes } from './router/index';
function App() {
return useRoutes(routes);
}
export default App;
懒加载(异步加载)
- 优化应用的启动速度,首屏加载的速度有明显提升(首屏不需要的组件,直到使用的那一刻才会加载)
- 导致了闪屏(加载对应页面需要等待一段时间,网络质量越差,该现象越明显)
使用了react库中的 lazy 配合 Suspense(本质是 promise)
根组件 App.js 中使用 Suspense 组件包裹
import { lazy, Suspense } from 'react';
const AppLayout = lazy(() => import('../pages/AppLayout'));
const Home = lazy(() => import('../pages/Home'));
const CityList = lazy(() => import('../pages/CityList'));
const News = lazy(() => import('../pages/News'));
const routes = [
{
path: '/',
element: <AppLayout />,
children: [
{
index: true,
element: <Home />
},
{
path: 'news',
element: <News />
}
]
},
{
path: '/citylist',
element: <CityList />
}
];
export { routes };
import './App.css';
// UI组件库: antd-mobile
import { Button } from 'antd-mobile';
import { routes } from './router/index';
import { useRoutes } from 'react-router-dom';
import { Suspense } from 'react';
function App() {
let element = useRoutes(routes);
return (
<Suspense fallback={<>loading</>}>
{element}
</Suspense>
);
}
export default App;
避免闪屏
- 父路由使用直接加载的方式载入
- 子路由使用懒加载的方式来载入
/* src/router/index.js */
import { lazy, Suspense } from 'react';
import AppLayout from '../pages/AppLayout';
const Home = lazy(() => import('../pages/Home'));
const CityList = lazy(() => import('../pages/CityList'));
const News = lazy(() => import('../pages/News'));
const lazyLoad = (children) => {
return <Suspense fallback={<>Loading...</>}>
{children}
</Suspense>;
};
const routes = [
{
path: '/',
element: <AppLayout />,
children: [
{
index: true,
element: lazyLoad(<Home />)
},
{
path: 'news',
element: lazyLoad(<News />)
},
{
path: 'citylist',
element: lazyLoad(<CityList />)
},
]
},
];
export { routes };
/* /src/App.js */
import './App.css';
// UI组件库: antd-mobile
import { Button } from 'antd-mobile';
import { routes } from './router/index';
import { useRoutes } from 'react-router-dom';
function App() {
let element = useRoutes(routes);
return element;
}
export default App;
路由监听(守卫)
useEffect
该 Hook 类似生命周期函数componentWillUnmount
、componentDidMount
和componentDidUpdate
(可看做是三者的结合)- useEffect 支持你在
函数组件
(该概念区别于类组件
)中执行副作用操作- 数据获取
- 设置订阅
- 手动更改 React 组件中的 DOM
- 参数一:处理逻辑
- 参数二:依赖的数据源
['data']
- 该 Hook 解决了
类组件中经常将不相关的逻辑放在同一个生命周期函数中,将相关的逻辑分离在多个不同的生命周期函数中
的这个问题 - 通过使用多个
useEffect
来分离这些需要在各个生命周期函数中处理的逻辑
- useEffect 支持你在
import './App.css';
// UI组件库: antd-mobile
import { Button } from 'antd-mobile';
import { routes } from './router/index';
import { useRoutes, useLocation } from 'react-router-dom';
import { Suspense, useEffect } from 'react';
function App() {
const location = useLocation();
useEffect(() => {
console.log(location.pathname, 'enter: 路由前置守卫');
return () => {
console.log(location.pathname, 'leave: 路由后置守卫');
};
}, [location.pathname]);
let element = useRoutes(routes);
return element;
}
export default App;
- useState 该 Hook ,通过数组解构可在
函数组件
中实现类似setState()
的效果const [count, setCount] = useState('banana')
- 通过
setCount(new value)
来实现状态更新
- 将
路由监听操作
封装成一个函数传递给 App.js 根组件
/* /src/router/index.js */
import { lazy, Suspense } from 'react';
import { matchRoutes } from 'react-router-dom';
import AppLayout from '../pages/AppLayout';
import NotFound from '../pages/NotFound';
const Home = lazy(() => import('../pages/Home'));
const CityList = lazy(() => import('../pages/CityList'));
const News = lazy(() => import('../pages/News'));
const lazyLoad = (children) => {
return <Suspense fallback={<>Loading...</>}>
{children}
</Suspense>;
};
const routes = [
{
path: '/',
element: <AppLayout />,
children: [
{
index: true,
element: lazyLoad(<Home />)
},
{
path: 'news',
element: lazyLoad(<News />)
},
{
path: 'citylist',
element: lazyLoad(<CityList />)
},
]
},
{
path: '/404',
element: <NotFound />
}
];
const routerListener = (path, navigator) => {
return () => {
const isMatch = matchRoutes(routes, path);
if (!isMatch) {
navigator('/404');
}
console.log(path, 'enter: routerEnterEvent');
return () => {
console.log(path, 'leave: routerLeaveEvent');
};
};
};
export { routes, routerListener };
/* App.js */
import './App.css';
// UI组件库: antd-mobile
import { Button } from 'antd-mobile';
import { routes, routerListener } from './router/index';
import { useRoutes, useLocation, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
function App() {
const location = useLocation();
const navigator = useNavigate();
let [pathname, setPathname] = useState(location.pathname);
useEffect(
routerListener(pathname, navigator),
[pathname]
);
let element = useRoutes(routes);
return element;
}
export default App;