V6的改变
- 原来的component、render、children属性合并成element属性;
- < Routes >替换 < Switch >;
- 新增 < Outlet > 给子路由占位;
- 支持子路由中的路径为相对于父组件的相对路径(Link和Route都改),v6以下只能用绝对路径;
- 删除 < Redirect >,使用 < Navigate />实现重定向;
- 删除query方式传参;
V6的用法
嵌套路由 + 默认路由
- v6支持 < Route > 直接嵌套
//index入口
<Routes>
<Route path="/" element={<Layout />}> //一级路由
<Route index element={<About />} />//一级路由的默认路由(用index)
<Route path="about" element={<About />} />//注意路径都是相对路径(相对于父路由)
<Route path="home" element={<Home />}>
<Route index element={<Child1 />} />//二级路由的默认路由
<Route path="child1" element={<Child1 />} />
<Route path="child2" element={<Child2 />} />
</Route>
</Route>
</Routes>
//一级路由Layout(二级路由也是一样)
import React from "react";
import {
BrowserRouter as Router,
Link,
Route,
NavLink,
Outlet,
} from "react-router-dom";
const Layout = () => {
return (
<div>
<Link to="/about">About</Link>
<Link to="/home">Home</Link>
<Outlet/> //使用Outlet给子路由占位(Layout中嵌套的路由渲染在此处)
</div>
);
};
export default Layout;
上面的用法相当于:
//index入口
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<About />} />
<Route path="about" element={<About />} />
<Route path="home/*" element={<Home />} />
</Route>
</Routes>
//二级路由Home(和一级路由一样)
import React from 'react'
import { BrowserRouter as Router, Link, Outlet, Route, NavLink, Routes } from 'react-router-dom'
import Child1 from './Child1';
import Child2 from './Child2';
export default function Home(props) {
return (
<div>
<h2>我是Home</h2>
<Link to='child1'>msg1</Link>
<Link to='child2'>msg2</Link>
<Routes> //这个部分相当于上面的<Outlet />
<Route index element={<Child1 />} />
<Route path='/child1' element={<Child1 />} />
<Route path='/child2' element={<Child2 />} />
</Routes>
</div>
)
}
默认路由
- 参考上面使用index指定默认路由
<Routes>
<Route index element={<Child1 />} />
<Route path='/child1' element={<Child1 />} />
<Route path='/child2' element={<Child2 />} />
</Routes>
- 与之前版本的用法相同,增加一个 < Route > ,需要精准匹配,路径为父路由的根路径
//外层路由
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<About />} />
<Route path="about" element={<About />} />
<Route path="home/*" element={<Home />} />//从父组件往后匹配
</Route>
</Routes>
//父组件内
<Routes>
<Route exact path={"/"} element={<Child1 />} /> //path可以是'/'或者''
<Route path='/child1' element={<Child1 />} />
<Route path='/child2' element={<Child2 />} />
</Routes>
动态传参
依旧有params、state两种传参方式,但是删去了query方式,并且state传参方式有所改变;
- 通过params传参(和之前版本相同)
//传参
<Link to='/about/123'>About</Link>
<Route path='/about/:id' element={<About />} />
//About组件中获取参数(需要用useParams获取)
import React from 'react'
import { BrowserRouter as Router, useLocation, Route, NavLink, useParams } from 'react-router-dom'
export default function About(props) {
let params = useParams();
console.log('params', params);
return (
<div>我是About</div>
)
}
- 通过state传参(直接给Link传state和to属性)原来的那种方式不可行了
//传参
import React from 'react'
import { BrowserRouter as Router, Link, Routes, Route} from 'react-router-dom'
import About from './About';
import Home from './Home';
import Child1 from './Child1';
import Child2 from './Child2';
export default function index() {
let data = { s: '我是state传参' }
return (
<div>
<Link to='/about' state={data}>About</Link> //state传参
<Link to='/home'>Home</Link>
<Routes>
<Route path='/about' element={<About />} />
<Route path='/home/*' element={<Home />} />
</Routes>
</div>
)
}
//获取
import React from 'react'
import { BrowserRouter as Router, useLocation } from 'react-router-dom'
export default function About(props) {
let { state } = useLocation();
console.log('state', state);
return (
<div>我是About</div>
)
}
注意:如果有通过params传参,它会将参数拼接到url后面,因此所有需要用到这个路由的地方都必须用完整的path(包含参数)
<Link to='/about/123'>About</Link>
<Route path='/about/:id' element={<About />} /> //下面需要对应
//需要跳转的页面
import { Button } from 'antd';
import React from 'react'
import { BrowserRouter as Router, useNavigate, Link, Outlet, Route, Routes } from 'react-router-dom'
import Child1 from './Child1';
import Child2 from './Child2';
export default function Home(props) {
let navigate = useNavigate();
return (
<div>
<h2>我是Home</h2>
<Link to='child1'>msg1</Link>
<Link to='child2'>msg2</Link>
<Outlet />
<Button onClick={() => navigate('/about/:id', { state: { name: 'xiaowang', age: 18 } })}>跳转到About</Button> //必须是完整path -----/about/:id
</div>
)
}
React-router Hook
- useLocation(与之前版本相同,返回当前url的动态的location对象)
可以获取state方式传的参数(包括通过useNavigate传参),都是从location对象的state中获取。
- useNavigate(v6新增)(代替useHistory)
可以用它实现跳转路由并传参,接收一个参数path或者path+参数对象两个参数;
返回一个函数用来实现编程式导航(编程式导航就是写js,类似history.push(),声明式导航是a标签这种直接渲染在页面上的写法)。
(1)直接传入数字(1,-1),模拟的是浏览器的前进回退,相当于history.go()。
let navigate=useNavigate();
navigate(1);//前进一步 相当于history.go(1)
navigate(-1);//后退一步 相当于history.go(-1)
(2)传入path和state
//useNavigate实现跳转
import { Button } from 'antd';
import React from 'react'
import { BrowserRouter as Router,useLocation,useNavigate, Link, Outlet, Route, NavLink, Routes } from 'react-router-dom'
import Child1 from './Child1';
import Child2 from './Child2';
export default function Home(props) {
let navigate=useNavigate();
console.log('navigate',navigate);
return (
<div>
<h2>我是Home</h2>
<Link to='child1'>msg1</Link>
<Link to='child2'>msg2</Link>
<Outlet />
<Button onClick={()=>navigate('/about')}>跳转到About</Button> //接收一个path实现跳转
<Button onClick={() => navigate('/about', { state: { name: 'xiaowang', age: 18 } })}>跳转到About</Button>//跳转并传参,path+state参数
</div>
)
}
//useLocation获取参数
import React from 'react'
import { BrowserRouter as Router, useLocation, Route, NavLink } from 'react-router-dom'
export default function About() {
let {state} = useLocation(); //useNavigate传参需要通过useLocation获取参数
console.log('state', state);
return (
<div>我是About</div>
)
}
- useParams(v6新增)
可以用它获取通过params方式传的参数
let params = useParams();
console.log('params', params);
- useOutlet(v6新增)
返回当前嵌套路由组件下已经渲染的子路由,没有或者没有挂载就返回null
import React from "react";
import { Outlet, useOutlet} from "react-router-dom";
const Home = () => {
console.log('useOutlet',useOutlet());
return (
<div>
<Outlet />
</div>
);
};
export default Home;
返回Home子路由Children1或者Children2(选中的那一个)
5.useRoutes(v6新增)
接收一个路由配置数据,生成对应的路由表(路由元素),可以在动态路由中使用。(可以看下面动态路由的示例)
6.useSearchParams(v6新增)(用于修改并获取修改后的参数的方法)
与usestate用法相同,里面暴露了一组数据[search,setSearch],search是一个包含参数内容的对象(就是url后面?&连接的那部分内容)(不是state传参的state对象,也不是params对象),需要用get方法获取上面的属性;setSearch是用于改变search对象的方法。
- 注意:初始使用state传参,setSearch会使得state为null
import { Button } from "antd";
import React, { useEffect } from "react";
import {
BrowserRouter as Router,
useSearchParams,
Route,
NavLink,
useParams,
} from "react-router-dom";
export default function Search() {
let [search, setSearch] = useSearchParams();
let params=useParams();//获取通过params方式传递的参数
let {params}=useMatch('/search/:id');//上面获取params也可以用useMatch,需要传path
console.log(params)//{id: '123'}
useEffect(() => {
console.log("search", search);
console.log('setSearch',setSearch);
}, [search]);
return (
<div>
<h1>Search</h1>
<h3>我是初始参数id:{params.id}</h3>
<h3>我是添加的参数name:{search.get('name')}</h3>
<h3>我是添加的参数age:{search.get('age')}</h3>
<Button onClick={() => setSearch({ name: "xiaowang", age: 18 })}>
改变传参
</Button>
</div>
);
}
打印的search和setSearch:
使用setSearch之前的参数要用useParams或者useMatch获取;
- useMatch(与之前版本的useRouteMatch相似,内部属性有所变化)(可以获取到match对象,里面包含的传过来的params)用法可以参照上面的例子🌰
let match=useMatch('/search/:id');
let {params} =useMatch('/search/:id');//直接解构获取params
console.log(match);
- useInRouterContext(v6新增)
用于判断当前组件是否处于Router的上下文之中(也就是有没有被< BrowserRouter >包裹),对应的返回true/false
//mac index文件
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import {
BrowserRouter as Router,
Route,
NavLink,
useInRouterContext,
} from "react-router-dom";
import Mac from "./mac";
const NotRouter = () => {
let flag = useInRouterContext();
console.log('flag',flag);//flag false
return <div>测试useInRouterContext:{!flag?'我是false':'我是true'}</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Router>
<Mac />//被<Router>包裹
</Router>
<NotRouter />//没有被<Router>包裹
</React.StrictMode>
);
//Mac组件
console.log('useInRouterContext',useInRouterContext()); //useInRouterContext true
- useNavigationType
返回当前的导航类型(是如何跳转到这个path的),返回pop(浏览器直接打开这个路径/刷新页面)/push/replace
//instance1
import React from "react";
import { useNavigationType } from "react-router-dom";
import { Button } from 'antd';
export default function About(props) {
console.log('useNavigationType',useNavigationType());//Link进入打印useNavigationType PUSH;默认都是PUSH;在路由刷新打印useNavigationType POP
return (
<div>
<h2>我是About</h2>
</div>
);
}
//instance2
import { Button } from "antd";
import React, { useEffect } from "react";
import {
BrowserRouter as Router,
useSearchParams,
Route,
NavLink,
useParams,
useMatch,
useLocation,
useNavigationType,
} from "react-router-dom";
export default function Search() {
let [search, setSearch] = useSearchParams();
let { state } = useLocation();
console.log("search", useNavigationType());//直接Link打开路由界面打印 search PUSH;Navigate重定向打印search REPLACE;
return (
<div>
<h1>Search</h1>
<h3>我是初始参数id:{state?.id}</h3>
<h3>我是添加的参数name:{search.get("name")}</h3>
<h3>我是添加的参数age:{search.get("age")}</h3>
<Button onClick={() => setSearch({...search, name: "xiaowang", age: 18 })}>
改变传参
</Button>
</div>
);
}
- useResolvedPath
给定一个url(随便一个url),解析其中的path/search/hash值
console.log('useResolvedPath',useResolvedPath('/home?name=xiaowang&age=18#detail'));
动态路由
- 懒加载+useRoutes实现动态路由,点击跳转demo的gitee仓库
(1)页面展示
(2)依赖版本
(3)代码部分
项目入口文件
//mac index文件
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import {
BrowserRouter as Router,
Route,
NavLink,
useInRouterContext,
} from "react-router-dom";
import Mac from "./mac";
const NotRouter = () => {
let flag = useInRouterContext();
console.log("flag", flag);
return (
<h3 style={{ padding: "5px" }}>
NotRouter--测试useInRouterContext:{!flag ? "我是false" : "我是true"}
</h3>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Router>//直接把< Router >放在最外层
<Mac />
</Router>
<NotRouter />
</React.StrictMode>
);
路由index入口文件
import React, { useEffect, Suspense } from "react";
import {
BrowserRouter as Router,
useRoutes,
useNavigate,
Link,
} from "react-router-dom";
import { Layout } from "antd";
import config from "./config";
import Menus from "./Menus";
const { Sider } = Layout;
const RouteEntry = () => {
let routes = useRoutes(config);
return routes;
};
export default function Mac() {
let navigate = useNavigate();
//刷新页面后重置路由
useEffect(() => {
//performance.navigation.type == 1监听刷新事件 这个已经被弃用了,所以换成performance.getEntriesByType("navigation")[0].type=='reload'
console.log(performance.getEntriesByType("navigation")[0].type);
if (performance.getEntriesByType("navigation")[0].type == 'reload') {
console.info("This page is reloaded");
navigate("/about"); //注意:⚠️因为这行代码,所以刷新以后About组件中useNavigationType打印PUSH而不是POP
} else {
console.info("This page is not reloaded");
}
}, []);
return (
<div>
<Layout>
<Sider width={200} className="site-layout-background">
<Menus />
</Sider>
<Layout style={{ padding: "24px" }}>
<Suspense fallback={<div>加载中...</div>}>
<RouteEntry />
</Suspense>
</Layout>
</Layout>
</div>
);
}
config配置文件
import React, { lazy } from "react";
import {
SmileTwoTone,
ProfileTwoTone,
ContactsTwoTone,
GithubOutlined,
TwitterOutlined,
} from "@ant-design/icons";
const handleLazy = (name) => {
let Comp = lazy(() => import(`./${name}`));
return <Comp />;//element需要传入<Element />这种形式,与component不同
};
const config = [
{
key: "about",
label: "首页",
index: 1, //默认展示 没有path属性,添加index属性
icon: <SmileTwoTone />,
element: handleLazy("About"),
exact: 1, //1代表true,用1或者0代替true/false更好(因为React对boolean类型的attribute的识别方式问题)
},
{
key: "about",
label: "首页",
icon: <SmileTwoTone />,
path: "/about",
element: handleLazy("About"),
exact: 1,
},
{
key: "home",
label: "嵌套路由",
icon: <ProfileTwoTone />,
path: "/home",
element: handleLazy("Home"),
children: [
{
key: "child1",
label: "son1",
index: 1, //默认展示 没有path属性,添加index属性
element: handleLazy("Children1"),
exact: 1,
},
{
key: "child1",
label: "son1",
icon: <GithubOutlined />,
path: "/home/child1",
element: handleLazy("Children1"),
exact: 1,
},
{
key: "child2",
icon: <TwitterOutlined />,
label: "son2",
path: "/home/child2",
element: handleLazy("Children2"),
exact: 1,
},
],
},
{
key: "search",
label: "Search",
icon: <ContactsTwoTone />,
path: "/search",
state: { id: 123 },
element: handleLazy("Search"),
exact: 1,
},
{
key: "navigate",
label: "重定向",
navigate:1, //加这个属性重定向
icon: <ContactsTwoTone />,
state: { winter: 'yes' },
path:'/search',
},
];
export default config;
Menus菜单(Link组件)
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Menu } from "antd";
import config from "./config";
import PubSub from "pubsub-js";
const Menus = () => {
const [selectedKeys, setSelectedKeys] = useState([]);
const [openKeys, setOpenKeys] = useState([]);
const handleLabel = (value) => {
if(!value.path)return;
return value.children
? {
...value,
children: value.children.map((child) => handleLabel(child)),
}
: {
...value,
label: <Link to={value.path} state={value.state}>{value.label}</Link>, //这里增加了state参数
};
};
useEffect(() => {
let openkeys=config.map(l=>{
if(l.children)return l.key
})
setOpenKeys(openkeys);
setSelectedKeys(config[0].key)
}, [config])
useEffect(() => {
PubSub.subscribe("backtohome", (_, data) => {
setSelectedKeys([data]);
});
}, []);
return (
<Menu
mode="inline"
openKeys={openKeys} //默认展开嵌套路由
selectedKeys={selectedKeys}
onClick={(v) => setSelectedKeys([v.key])}
style={{
height: "100%",
borderRight: 0,
}}
items={config.map((i) => handleLabel(i))}
></Menu>
);
};
export default Menus;
首页About(刷新后默认展示)
import React from "react";
import {useNavigationType} from "react-router-dom";
export default function About(props) {
console.log("useNavigationType", useNavigationType());//刷新后打印useNavigationType PUSH,通过Link进入也打印useNavigationType PUSH
return (
<div>
<h2>我是About</h2>
</div>
);
}
嵌套路由Home
import React from "react";
import { Outlet } from "react-router-dom";
const Home = () => {
return (
<div>
<Outlet /> //只需要占位
</div>
);
};
export default Home;
Children1(嵌套子路由文件)
import { Button } from 'antd';
import React from 'react'
import { BrowserRouter as Router,useLocation,useNavigate, Link, Outlet, Route, NavLink, Routes } from 'react-router-dom'
import PubSub from "pubsub-js";
const Children1 = (porps) => {
let navigate=useNavigate();
const handleClick = () => {
PubSub.publish('backtohome','about');
navigate('/about', { state: { name: 'xiaowang', age: 18 } });
};
return (
<div>
<ul>
<li>我是children1</li>
<li>我是children1</li>
<li>我是children1</li>
<li>我是children1</li>
<li>我是children1</li>
</ul>
<Button onClick={handleClick}>跳转到About</Button>
</div>
);
};
export default Children1;
常用组件
新增Navigate(重定向,代替< Redirect >)
Navigate有to和replace两个属性,replace属性用于改变跳转方式(push 或 replace,默认是push也就是默认false)。
路由的跳转有两种模式,push和replace,push模式会将这个url压入路由history栈顶; 而replace模式会将栈顶的url替换 (也就是是否可以回退的区别)。
<Navigate to="/about" replace={true}/> //replace模式 替换url
Link/NavLink/Outlet(新增)/BrowserRouter/HashRouter就不再赘述了