推荐一个 视频 https://www.youtube.com/watch?v=zEQiNFAwDGo
V6主要变动概览
重命名为 。 的新特性变更。 - 嵌套路由变得更简单。
- 用useNavigate代替useHistory。
- 新钩子useRoutes代替react-router-config。
- 大小减少:从20kb到8kb
使用方法参见 React-Router v6
Switch命名为Routes
// v5
<Switch>
<Route exact path="/"><Home /></Route>
<Route path="/profile"><Profile /></Route>
</Switch>
// v6
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile/*" element={<Profile />} />
</Routes>
Route渲染方式改变
component/render的渲染方式更换成element形式
注意:
- element中是JSX形式
```javascript
//v6
<a name="ZmPXR"></a>
## 嵌套路由更便捷
- <Route children> 已更改为接受子路由。
- 比<Route exact>和<Route strict>更简单的匹配规则。
- <Route path> 路径层次更清晰。
<a name="ga7gK"></a>
### 路径匹配更精准人性
```javascript
<Route path="/about/:id" element={<About/>}/>
<Route path="/about/books" element={<Dashboard/>}/>
当路径是/about/123会匹配到About
当路径是/about/books会匹配到Dashboard
v5中需要exacth和strict来进行精准匹配
v6中的所有路径匹配都将忽略URL上的尾部”/“。实际上,
在v5版本之前的路径,存在路由歧义
- 当前路径:”/users”,则将跳转。
- 当前路径:”/users/“,则将跳转。
React Router v6修复了这种歧义,取消了尾部”/“:
其形式更像命令行cd的用法
// 当前路径为 /app/dashboard
<Link to="stats"> // <a href="/app/dashboard/stats">
<Link to="../stats"> // <a href="/app/stats">
<Link to="../../stats"> // <a href="/stats">
<Link to="../../../stats"> // <a href="/stats">
// 命令行当前路径为 /app/dashboard
cd stats // pwd is /app/dashboard/stats
cd ../stats // pwd is /app/stats
cd ../../stats // pwd is /stats
cd ../../../stats // pwd is /stats
嵌套更方便-脱离useRouteMatch
V5路由回顾中,我们对useRouteMatch用法做过介绍。
V5版本中,需要useRouteMatch来进行匹配,进而根据匹配的url来进行下一步的嵌套渲染。
//v5
import {
BrowserRouter,
Switch,
Route,
Link,
useRouteMatch,
} from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" render={()=><div>Home组件</div>} />
<Route path="/profile" component={Profile} />
</Switch>
</BrowserRouter>
);
}
function Profile() {
let { path, url } = useRouteMatch();
console.log('Profile useRouteMatch',useRouteMatch('/profile/:id'));
return (
<div>
<nav>
<Link to={`${url}/me`}>My Profile</Link>
</nav>
<Switch>
<Route path={`${path}/me`} render={()=> (<div>第一个子组件</div>)} />
<Route path={`${path}/:id`} render={()=>(<div>第二个子组件</div>)}/>
</Switch>
</div>
);
}
export default App
V6中,你可以删除字符串匹配逻辑。不需要任何useRouteMatch()!
原因是 V6中Link时的处理跟V5不一样 参见 路径匹配更精准人性
注意下 profile/*
function App() {
return (
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
// 注意这里路径是profile/*
<Route path="profile/*" element={<Profile />}></Route>
</Routes>
</Router>
);
}
function Home() {
return <>Home V6</>;
}
function Profile() {
// 这里不需要useRouteMatch
// let { path, url } = useRouteMatch();
// console.log("Profile useRouteMatch", useRouteMatch());
return (
<div>
<nav>
<Link to={`me`}>My Profile</Link>
</nav>
<Routes>
<Route path={`me`} element={<Profile1 />} />
<Route path={`:id`} element={<Profile2 />} />
</Routes>
</div>
);
}
function Profile1() {
return <>第一个子组件</>;
}
function Profile2() {
return <>第二个子组件</>;
}
新的API:Outlet
还是上面的例子,outlet相当于props.children,会将嵌套路由中的子组件渲染出来
可太6了
function App() {
return (
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
{/**这里路径 可以是profile 也可以是profile/* */}
<Route path="profile" element={<Profile />}>
<Route path={`me`} element={<Profile1 />} />
<Route path={`:id`} element={<Profile2 />} />
</Route>
</Routes>
</Router>
);
}
function Home() {
return <>Home V6</>;
}
function Profile() {
return (
<div>
<nav>
<Link to={`me`}>My Profile V6</Link>
</nav>
{/* <Routes>
<Route path={`me`} element={<Profile1 />} />
<Route path={`:id`} element={<Profile2 />} />
</Routes> */}
{/**这里相当于props.children 将Profile中的children渲染出来 */}
<Outlet />
</div>
);
}
function Profile1() {
return <>第一个子组件V6</>;
}
function Profile2() {
return <>第二个子组件V6</>;
}
export default App;
useRoutes代替react-router-config
需要注意两点:
- 定义router的中,element为JSX
- useRoutes必须在Router包裹中,不然报错:useRoutes() may be used only in the context of a
component
定义router.js
import Home from "./pages/home";
import About from "./pages/about";
import Dashboard from "./pages/dashborad";
import Detail from "./pages/detail";
const routes = [
{
path: "/",
element: <Home/>,
},
{
path: "about",
element: <About />,
//此处添加嵌套路由
children: [
{
path: "detail",
element: <Detail />,
},
],
},
{
path: "dashboard",
element: <Dashboard />,
},
// 重定向
{ path: "/login", redirectTo: "/" },
// 404找不到
{ path: "*", element: <NotFound /> },
];
//将路由表数组导出
export default routes;
const App = () => {
let routes = useRoutes(router);
return routes;
};
console.log('APP',App);
const AppWrapper = () => {
return (
<Router>
<App />
</Router>
);
};
export default AppWrapper;
useNavigate代替useHistory
// v5
import { useHistory } from 'react-router-dom';
function MyButton() {
let history = useHistory();
function handleClick() {
history.push('/home');
};
return <button onClick={handleClick}>Submit</button>;
};
history.push()将替换为navigation()
// v6
import { useNavigate } from 'react-router-dom';
function MyButton() {
let navigate = useNavigate();
function handleClick() {
navigate('/home');
};
return <button onClick={handleClick}>Submit</button>;
};
这里注意下,history的用法也被替换
// v5
history.push('/home');
history.replace('/home');
// v6
navigate('/home');
navigate('/home', {replace: true});
大小减少:从20kb到8kb
代码体检减少到8kb,主要核心代码react-router-dom大概一千多行,react-router中400多行
useBlocker
import {
useCallback
} from 'react';
import {
useBlocker,
useLocation,
useNavigate
} from 'react-router';
const locationProperties = ['pathname', 'search', 'state'];
function isSameLocation(location1, location2) {
return locationProperties.every((property) => location1[property] === location2[property]);
}
export default function NavigationGuard() {
const location = useLocation();
const navigate = useNavigate();
const blocker = useCallback(
({
action,
location: nextLocation,
retry
}) => {
switch (action) {
case 'PUSH':
case 'REPLACE': {
retry();
return;
}
case 'POP': {
if (isSameLocation(nextLocation, location)) {
retry();
return;
}
const answer = confirm('Are you sure you want to leave this page?');
if (answer) {
navigate('/');
}
return;
}
}
},
[dispatch, location, navigate],
);
useBlocker(blocker);
return null;
}
References
https://reactrouter.com/docs/en/v6/getting-started/tutorial
https://juejin.cn/post/6844904096059621389#heading-8