1、router的安装
在react中,router的使用需要依赖react-router-dom
npm i react-router-dom
2、router的使用
路由有分两种模式
- hash 模式: HashRouter
- history 模式: BrowserRouter
入门案例
使用router时,在根目录需要用HashRouter或者是BrowserRouter包一层
import App from './App';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
root.render(
<HashRouter>
<App />
</HashRouter>,
);
使用例子
import { FC, ReactElement } from 'react';
import { Routes, Route, NavLink } from 'react-router-dom';
const Home: FC = (): ReactElement => {
return <div>this is Home</div>;
};
const About: FC = (): ReactElement => {
return <div>this is About</div>;
};
const App: FC = (): ReactElement => {
return (
<div>
<div className="header">
<h1>this is App</h1>
<div>
{/*
* Link的其他属性
* to: 表示需要跳转到的目标路径
* state: any 表示在history中可以传入的state
* replace: boolean 表示是否替换,非push
* reloadDocument: boolean 表示是否需要重载页面
*/}
{/* <Link to="/home">首页</Link>
<Link to="/about">关于</Link>
<Link to="/test">测试</Link> */}
{/* 通常会使用NavLink, 因为当被选中时,会在对应的地方添加active的类, 也可以使用style,如下 */}
<NavLink
to="/home"
style={({ isActive }) => ({ color: isActive ? 'red' : '' })}
>
首页
</NavLink>
{/* 也可以自定义className */}
<NavLink
to="/about"
className={({ isActive }) => (isActive ? 'clsName' : '')}
>
关于
</NavLink>
<NavLink to="/test">测试</NavLink>
</div>
</div>
<div className="content">
<Routes>
{/* 重定向到首页 */}
<Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
<Route path="/home" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
<Route path="/test" element={<div>this is test</div>}></Route>
{/* 匹配到没有设置的路由 */}
<Route path="*" element={<div>NotFound</div>}></Route>
</Routes>
</div>
</div>
);
};
export default App;
router的早期版本的写法如下
import React from 'react';
import ReactDom from 'react-dom'
import {HashRouter, Link, Route, Redirect} from "react-router-dom"
import Home from './components/Home'
import User from './components/User'
/**
* HashRouter表示使用的是HashRouter即Hash模式, history模式使用的是BrowserRouter
* Route相当于vueRouter里的routerView, 注意这里用的是route并非router
* Link相当于vue里的routerLink,相当于点击跳转的标签
* Redirect重定向组件,path表示匹配的路径,to表示重定向需要跳转的路径
*
*/
const App = () => {
return <div>
<h1>this is App</h1>
<Link to='/home'>首页</Link>
<Link to='/user'>用户</Link>
{/*相当于当路径匹配到地址/home的时候,那么就展示home这个组件,匹配到/user的时候就展示 User组件*/}
{/*注意:当组件被route使用后,则会在该组件的props注入一些路由的属性,如果location, history等等*/}
{/*<Redirect path='/' to='/home'></Redirect>重定向的方法*/}
<Route path='/' exact render={() => <div>this is main</div>}/>{/*注意:这里的exact是表示精确匹配*/}
<Route path='/home' component={Home} />
{/*<Route path='/user' component={User}/>*/}
{/*<Route path='/user' render={() => <div>this is user, render</div>}/>*/}
<Route path='/user' render={() => <User/>}/>
{/*Route在使用的过程中,如果需要有逻辑的判断,可以用另外一种写法*/}
</div>
}
//注意:在使用路由的时候,根组件需要用HashRouter或者BrowserRouter进行包裹,这样子组件内就可以愉快的使用路由了
ReactDom.render(<HashRouter><App/></HashRouter>, window.root)
// ReactDom.render(<BrowserRouter><App/></BrowserRouter>, window.root)
注意: 在react的机制中,如果路由匹配上多个,那么就会所有匹配上的全部显示出来,这点区别于vue,当vue匹配上一个路由后则不会往下匹配,这个时候react就需要switch组件来做处理
import React from 'react'; import ReactDom from 'react-dom' import {HashRouter, Link, Route, Switch} from "react-router-dom" import Home from './components/Home' import User from './components/User' const App = () => { return <div> <h1>this is App</h1> <Link to='/home'>首页</Link> <Link to='/user'>用户</Link> <Switch> <Route path='/' render={() => <div>this is main</div>}/> <Route path='/home' component={Home} /> <Route path='/home' render={() => <div>hello home</div>} /> <Route path='/user' render={() => <User/>}/> </Switch> </div> } ReactDom.render(<HashRouter><App/></HashRouter>, window.root)
注意:Switch组件中间只能放Route组件,不能放其他组件,配置上Switch组件后匹配上一个路由后,就不会再匹配其他路由了
import React from 'react'; import ReactDom from 'react-dom' import {HashRouter, Link, Route, Switch, Redirect} from "react-router-dom" import Home from './components/Home' import User from './components/User' const App = () => { return <div> <h1>this is App</h1> <Link to='/home'>首页</Link> <Link to='/user'>用户</Link> <Switch> {/*如果这种重定向放在switch会把所有路由拦截,因为/可以匹配所有,所以要加上exact*/} <Redirect path='/' exact to='/home'/> <Route path='/home' component={Home} /> <Route path='/user' render={() => <User/>}/> {/*匹配未能匹配到的路由则用以下方法,但是注意这个页面通常是配置在路由的最后,也可以不配置path*/} <Route path='/*' render={() => <div>NOT FOUND</div>} /> </Switch> </div> } ReactDom.render(<HashRouter><App/></HashRouter>, window.root)
NavLink组件的使用
为了实现标签的高亮显示, 这个时候可以使用NavLink来替代Link实现高亮显示,在路径匹配上后,也就是激活后,所对应的标签会被默认的加上active类,但是可以使用activeStyle或activeClassName进行定义样式或类名
<NavLink to='/home'>首页</NavLink> <NavLink to='/user' activeClassName='yfbill'>用户</NavLink>
3、嵌套子路由的实现
子路由的实现
import { FC, ReactElement } from 'react';
import { Routes, Route, Link, Navigate, Outlet } from 'react-router-dom';
const Home: FC = (): ReactElement => {
return (
<div>
<h1>this is Home</h1>
<div>
<Link to="/home/first">first</Link>
<Link to="/home/second">second</Link>
</div>
<div>
{/* 这个是占位组件,表示当前的子组件的内容展示的位置 */}
<Outlet />
</div>
</div>
);
};
const About: FC = (): ReactElement => {
return <div>this is About</div>;
};
const App: FC = (): ReactElement => {
return (
<div>
<div className="header">
<h1>this is App</h1>
<div>
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
</div>
</div>
<div className="content">
<Routes>
{/* 重定向到首页 */}
<Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
<Route path="/home" element={<Home />}>
<Route
path="/home/first"
element={<div>this is home first</div>}
></Route>
<Route
path="/home/second"
element={<div>this is home second</div>}
></Route>
</Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</div>
</div>
);
};
export default App;
早期版本的子路由的实现,承接上面的例子
import {NavLink, Route, Switch} from 'react-router-dom' import Redirect from "react-router-dom/es/Redirect"; const User = () => { return <div> <h3>this is user</h3> <NavLink to='/user/list'>用户列表</NavLink> <NavLink to='/user/edit'>用户编辑</NavLink> <Switch> <Redirect path='/user' to='/user/list' exact />{/*表示当用户访问/user的时候,自动跳转到/user/list页面*/} <Route path='/user/list' render={() => <div>这个是用户列表页面</div>}/> <Route path='/user/edit' render={() => <div>这个是用户编辑页面</div>}/> </Switch> </div> } export default User
4、手动路由的跳转
在常规开发过程中,经常会使用一个行为跳转到指定的路由中,那么可以使用以下的方法进行手动的跳转
import { FC, ReactElement } from 'react';
import { Routes, Route, Navigate, useNavigate } from 'react-router-dom';
const Home: FC = (): ReactElement => {
return <div>this is Home</div>;
};
const About: FC = (): ReactElement => {
return <div>this is About</div>;
};
const App: FC = (): ReactElement => {
const navigate = useNavigate();
return (
<div>
<div className="header">
<h1>this is App</h1>
<div>
<button onClick={() => navigate('/home')}>首页</button>
<button onClick={() => navigate('/about')}>关于</button>
</div>
</div>
<div className="content">
<Routes>
{/* 重定向到首页 */}
<Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
<Route path="/home" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</div>
</div>
);
};
export default App;
但是useNavigate是一个hook在类组件中不能使用,这个时候就需要封装高阶组件进行操作(以下封装了useNavigate, useParams, useSearchParams)等方法
import { FC, ReactElement, PureComponent, ComponentType } from 'react';
import {
Routes,
Route,
Navigate,
useNavigate,
useParams,
useLocation,
NavigateFunction,
Params,
useSearchParams,
} from 'react-router-dom';
interface IRouter {
router: {
navigate: NavigateFunction;
params: Params<string>;
query: Record<string, string>;
};
}
const withRouter = <T,>(
WrapComponent: ComponentType<T & IRouter>,
): ComponentType<T> => {
return (props: T): ReactElement => {
const navigate = useNavigate();
// 获取路由上的params, 如/home/:id,那么就可以获取id的值
const params = useParams<Params<string>>();
// 获取路由上的query,如/home?name=aaa&age=12, 但是这个方法是没有解构query里的参数, 可以使用该方法获取当前的路径等信息
const locations = useLocation();
console.log(locations);
// 获取路由上的query,如/home?name=aaa&age=12, 获取name与age的值
const [searchPrams] = useSearchParams();
const query = Object.fromEntries(searchPrams);
const router = { navigate, params, query };
return <WrapComponent {...props} router={router} />;
};
};
const Home = withRouter(
class HomeInner extends PureComponent<IRouter> {
private routerTo(path: string): void {
const { navigate } = this.props.router;
navigate(path);
}
public render(): ReactElement {
return (
<div>
<h3>this is home</h3>
<button onClick={() => this.routerTo('/about')}>跳转到about</button>
</div>
);
}
},
);
const About: FC = (): ReactElement => {
return <div>this is About</div>;
};
const App: FC = (): ReactElement => {
const navigate = useNavigate();
return (
<div>
<div className="header">
<h1>this is App</h1>
<div>
<button onClick={() => navigate('/home')}>首页</button>
<button onClick={() => navigate('/about')}>关于</button>
</div>
</div>
<div className="content">
<Routes>
{/* 重定向到首页 */}
<Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
<Route path="/home" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</div>
</div>
);
};
export default App;
5、组件中监听路由的变化
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
console.log('Path changed:', location.pathname);
// 在这里可以执行路径变化时的操作
}, [location]);
return (
<div>
{/* 页面内容 */}
</div>
);
}
export default App;
6、实现最新版本的路由可配置化
配置如下, 注意:这个文件应该为index.tsx
import React from 'react';
import { RouteObject, Navigate } from 'react-router-dom';
import Home from '@app/views/home';
import About from '@app/views/about';
const routes: RouteObject[] = [
{
path: '/',
element: <Navigate to="/home" />,
},
{
path: '/home',
element: <Home />,
children: [
{ path: '/home/first', element: <div>this is home first</div> },
{ path: '/home/second', element: <div>this is home second</div> },
],
},
{
path: '/about',
element: <About />,
},
];
export default routes;
那么在调用routes的地方代码如下
import { FC, ReactElement, ComponentType, useEffect } from 'react';
import { Link, useRoutes } from 'react-router-dom';
import routes from './routers';
// 自定义的附加属性类型
interface CustomProps {
customData?: {
title: string;
};
}
const App: FC = (): ReactElement => {
return (
<div>
<div className="header">
<h1>this is App</h1>
<div>
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
<Link to="/home/first">first</Link>
<Link to="/home/second">second</Link>
</div>
</div>
<div className="content">{useRoutes(routes)}</div>
</div>
);
};
export default App;
如果需要使用路由的懒加载,那么使用如下
import React from 'react';
import { RouteObject, Navigate } from 'react-router-dom';
import Home from '@app/views/home';
const About = React.lazy(() => import('@app/views/about'));
const routes: RouteObject[] = [
{
path: '/',
element: <Navigate to="/home" />,
},
{
path: '/home',
element: <Home />,
children: [
{ path: '/home/first', element: <div>this is home first</div> },
{ path: '/home/second', element: <div>this is home second</div> },
],
},
{
path: '/about',
element: <About />,
},
];
export default routes;
这个时候就需要在外围index.tx入口处使用组件 Suspense组件,如下
import App from './App';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import { Suspense } from 'react';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
root.render(
<HashRouter>
<Suspense fallback={<h2>正在加载中。。。。</h2>}>
<App />
</Suspense>
</HashRouter>,
);
这个时候组件第一次加载时就会显示正在加载中。。。
7、手动实现5.x版本的路由的可配置化
在router5进行使用的时候,可以使用依赖 react-router-config进行配置,也可以使用以下方法进行封装
router ├── config │ └── mainMenu.ts //具体的路由配置 ├── IRouter.ts //路由配置的声明 ├── pageComps.tsx //页面组件管理文件 ├── RouterComponent.tsx //主体的路由生成器 └── RouterWrapper.tsx //动态路由转发
RouterComponent文件
import {Switch, Route, Redirect} from 'react-router-dom' import {IRouter} from "./IRouter"; import AllComponents from './pageComps' import React, {ReactElement} from "react"; import RouterWrapper from './RouterWrapper'; const routerComponent: (routerList: Array<IRouter>) => React.FC = (routerList: Array<IRouter>) => { const createRouters = (router: IRouter): Array<React.ReactElement> => { let routerArr: Array<React.ReactElement> = [] if(router.redirect) { //如果有进行重新定位,那么主使用redirect routerArr.push(<Redirect key={router.path} exact path={router.path} to={router.redirect}/>) } else { routerArr.push( router.component && AllComponents[router.component]? <Route path={router.path} key={router.path} exact render={props => { //根据登录情况,实现动态判断登录 return <RouterWrapper {...{...props, Comp: router.component && AllComponents[router.component], router}}/> }} />: <Redirect key={router.path} path={router.path} to='/404' />) //如果找不到页面, 那么跳转到not found页面 } if(router.children) { //如果有子元素,那么用递归实现子路由 routerArr = routerArr.concat(...router.children.map((val: IRouter) => createRouters(val))) } return routerArr } const transformRouters = () => { return routerList.reduce((total: Array<ReactElement>, per: IRouter) => { let items: Array<React.ReactElement> = createRouters(per) return total.concat(items) }, []) } //注意:如果在最根目录使用该方法,那么需要在要根目录使用HashRouter或BrowserRouter进行包裹, 否则会报错 return () => { return <Switch> {transformRouters()} </Switch> } } export default routerComponent
RouterWrapper文件
import React, {PropsWithChildren} from "react"; import DocumentTitle from "../components/DocumentTitle"; //自己封装的改变document.title的组件 import queryString from 'query-string' const RouterWrapper: React.FC<any> = (props: PropsWithChildren<any>) => { let { Comp, router, ...others } = props; const queryReg = /\?\S*/g; const matchQuery = (reg: RegExp) => { const queryParams = others.location.search.match(reg); return queryParams ? queryParams[0] : '{}'; }; others.match.query = queryString.parse(matchQuery(queryReg)); //扩展query方法 return <DocumentTitle title={router.meta && router.meta.title}> <Comp {...others}/> </DocumentTitle> } export default RouterWrapper
DocumentTitle组件
import {PropsWithChildren, useEffect} from "react"; import config from '../config' export interface IDocumentTitle { title?: string } const DocumentTitle: React.FC<IDocumentTitle> = (props: PropsWithChildren<IDocumentTitle>) => { let {title} = props useEffect(() => { document.title = config.defaultTitle + (title? `-${title}`: '') }, [title]) return <>{props.children}</> } export default DocumentTitle
PageComps文件
import Login from '../pages/Login' import Home from '../pages/home/Home' import InfoList from "../pages/info/InfoList"; import InfoEdit from "../pages/info/InfoEdit"; import UserEdit from "../pages/user/UserEdit"; import UserList from "../pages/user/UserList"; import NotFound from "../pages/NotFound"; const routerComp: {[key: string]: React.FC|React.ComponentType} = { Login, Home, InfoEdit, InfoList, UserList, UserEdit, NotFound } export default routerComp
IRouter文件
export interface IMeta { title: string, icon?: string } export interface IRouter{ path: string, component?: string, redirect?: string, hidden?: boolean, alwaysShow?: boolean, children?: Array<IRouter>, meta?: IMeta }
mainMenu文件
import {IRouter} from "../IRouter"; const mainMenu: Array<IRouter> = [ { path: '/login', component: 'Login', meta: { title: '登录页' } }, { path: '/', component: 'Home', meta: { title: '首页' } }, { path: '/info', redirect: '/info/list', children: [ { path: '/info/list', component: 'InfoList', meta: { title: '信息列表' } }, { path: '/info/edit', component: 'InfoEdit', meta: { title: '信息列表编辑' } } ] }, { path: '/user', redirect: '/user/list', children: [ { path: '/user/list', component: 'UserList', meta: { title: '用户列表' } }, { path: '/user/edit', component: 'UserEdit', meta: { title: '用户列表编辑' } } ] }, { path: '/*', component: 'NotFound', meta: { title: '页面未找到' } } ] export default mainMenu
在app中调用
import React from 'react'; import routerComponent from "./router/RouterComponent"; import mainMenu from "./router/config/mainMenu"; import {HashRouter} from 'react-router-dom' import './App.css'; const App: React.FC = () => { let RouterComp = routerComponent(mainMenu) return <HashRouter><RouterComp/></HashRouter> } export default App;
8、手动封装组件动态加载方法
组件的动态加载,可以使用react-loadable,这里实现手动编写原理
import {NavLink, Route, Switch, Redirect} from 'react-router-dom' import {Component} from "react"; const Loading = () => { //顶替的组件 return <div>现在正在加载。。。</div> } const Loadable = (config) => { //方法主体 const {loader, Loading} = config; return class LoadableComp extends Component { state = { flag: false, Comp: null } componentDidMount() { loader().then(res => { console.log('ok') this.setState({ Comp: res.default, flag: true }) }) } render() { let {Comp, flag} = this.state; return <>{flag? <Comp {...this.props}/>: <Loading/>}</> } } } const UserList = Loadable({ loader: () => import('./UserList'), //需要预加载的组件 Loading //在预加载是顶替的组件 }) const User = () => { return <div> <h3>this is user</h3> <NavLink to='/user/list'>用户列表</NavLink> <NavLink to='/user/edit'>用户编辑</NavLink> <Switch> <Redirect path='/user' to='/user/list' exact />{/*表示当用户访问/user的时候,自动跳转到/user/list页面*/} <Route path='/user/list' component={UserList}/> <Route path='/user/edit' render={() => <div>这个是用户编辑页面</div>}/> </Switch> </div> } export default User
注意: 在react中使用高阶函数,并且返回一个函数式组件,注意方法要使用小写的,否则会被识别为函数式组件,容易报错