当我们着手构建 React Router v6 时,从 @reach/router
用户的角度来看,我们有以下目标
@reach/router
更小)@reach/router
的最佳部分(嵌套路由,以及通过排名路径匹配和 navigate
简化的 API)如果我们要制作一个 @reach/router
v2,它看起来几乎与 React Router v6 完全一样。因此,@reach/router
的下一个版本就是 React Router v6。换句话说,不会有 @reach/router
v2,因为它将与 React Router v6 相同。
实际上,@reach/router
1.3 和 React Router v6 之间的许多 API 都是相同的。
navigate
具有相同的签名Link
具有相同的签名大多数更改只是重命名。如果您碰巧编写了代码修改器,请与我们分享,我们会将其添加到本指南中!
在本指南中,我们将向您展示如何升级路由代码的每个部分。我们将逐步进行,以便您可以进行一些更改,发布,然后在方便时返回进行迁移。我们还将讨论一些关于“为什么”进行更改的内容,以及看似简单的重命名实际上背后有更大的原因。
我们强烈建议您在迁移到 React Router v6 之前对代码进行以下更新。这些更改不必在整个应用程序中一次性完成,您只需更新一行代码,提交并发布即可。这样做将大大减少您在进行 React Router v6 中的重大更改时所付出的努力。
@reach/router
v1.3<LocationProvider/>
以下更改需要在整个应用程序中一次性完成。
<Router>
元素更新为 <Routes>
<RouteElement default/>
更改为 <RouteElement path="*" />
<Redirect />
<Link getProps />
useMatch
,参数在 match.params
上ServerLocation
更改为 StaticRouter
React Router v6 大量使用 React 钩子,因此您需要在尝试升级到 React Router v6 之前使用 React 16.8 或更高版本。
升级到 React 16.8 后,您应该部署应用程序。然后,您可以稍后回来继续您之前的工作。
@reach/router
v1.3.3您应该能够简单地安装 v1.3.3,然后部署您的应用程序。
npm install @reach/router@latest
您可以一次更新一个路由组件,提交并部署。您不需要一次更新整个应用程序。
在 @reach/router
v1.3 中,我们添加了钩子来访问路由数据,为 React Router v6 做准备。如果您先这样做,那么在升级到 React Router v6 时,您将需要做的工作更少。
// @reach/router v1.2
<Router>
<User path="users/:userId/grades/:assignmentId" />
</Router>;
function User(props) {
let {
// route params were accessed from props
userId,
assignmentId,
// as well as location and navigate
location,
navigate,
} = props;
// ...
}
// @reach/router v1.3 and React Router v6
import {
useParams,
useLocation,
useNavigate,
} from "@reach/router";
function User() {
// everything comes from a specific hook now
let { userId, assignmentId } = useParams();
let location = useLocation();
let navigate = useNavigate();
// ...
}
所有这些数据都已存在于上下文中,但从那里访问它们对于应用程序代码来说很尴尬,因此我们将它们转储到您的道具中。钩子使从上下文中访问数据变得简单,因此我们不再需要用路由信息污染您的道具。
不污染道具也有助于 TypeScript,并且还可以防止您在查看组件时想知道道具来自哪里。如果您使用的是来自路由器的数据,现在它已经完全清楚了。
此外,随着页面的增长,您自然会将其分解为多个组件,并最终将数据“道具钻取”到树的底部。现在,您可以在树中的任何位置访问路由数据。这不仅更方便,而且使创建以路由器为中心的可组合抽象成为可能。如果自定义钩子需要位置,它现在可以简单地使用 useLocation()
等来请求它。
虽然 @reach/router
不需要在应用程序树的顶部使用位置提供程序,但 React Router v6 需要,因此现在就准备好它吧。
// before
ReactDOM.render(<App />, el);
// after
import { LocationProvider } from "@reach/router";
ReactDOM.render(
<LocationProvider>
<App />
</LocationProvider>,
el
);
@reach/router
使用全局默认历史记录实例,该实例在模块中具有副作用,这会阻止模块的树状摇动,无论您是否使用全局实例。此外,React Router 提供了其他历史记录类型(如哈希历史记录),而 @reach/router
没有,因此它始终需要一个顶级位置提供程序(在 React Router 中,这些是 <BrowserRouter/>
及其朋友)。
此外,各种模块(如 Router
、Link
和 useLocation
)在 <LocationProvider/>
之外渲染,它们会设置自己的 URL 监听器。这通常不是问题,但每一小部分都很重要。在顶部放置一个 <LocationProvider />
允许应用程序拥有一个 URL 监听器。
下一组更新需要一次性完成。幸运的是,大多数只是简单的重命名。
您可以使用一个技巧,在迁移时同时使用两个路由器,但您绝对不应该以这种状态发布您的应用程序,因为它们是不可互操作的。您从一个路由器中的链接将无法在另一个路由器中使用。但是,能够进行更改并刷新页面以查看您是否正确执行了这一步,这很好。
npm install react-router@6 react-router-dom@6
LocationProvider
更新为 BrowserRouter
// @reach/router
import { LocationProvider } from "@reach/router";
ReactDOM.render(
<LocationProvider>
<App />
</LocationProvider>,
el
);
// React Router v6
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
el
);
Router
更新为 Routes
您可能有多个,但通常只有一个在应用程序的顶部附近。如果您有多个,请对每个都执行此操作。
// @reach/router
import { Router } from "@reach/router";
<Router>
<Home path="/" />
{/* ... */}
</Router>;
// React Router v6
import { Routes, Route } from "react-router-dom";
<Routes>
<Route path="/" element={<Home />} />
{/* ... */}
</Routes>;
default
路由属性default
属性告诉 @reach/router
如果没有其他路由匹配,则使用该路由。在 React Router v6 中,您可以使用通配符路径来解释此行为。
// @reach/router
<Router>
<Home path="/" />
<NotFound default />
</Router>
// React Router v6
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
<Redirect/>
、redirectTo
、isRedirect
哇...系好安全带,准备好了。请将您的西红柿留着做自制的马格丽特披萨,而不是扔给我们。
我们已经删除了从 React Router 中重定向的功能。这意味着没有 <Redirect/>
、redirectTo
或 isRedirect
,也没有替代 API。请继续阅读 😅
不要将重定向与用户与您的应用程序交互时的导航混淆。响应用户交互进行导航仍然受支持。当我们谈论重定向时,我们指的是在匹配时进行重定向
<Router>
<Home path="/" />
<Users path="/events" />
<Redirect from="/dashboard" to="/events" />
</Router>
@reach/router
中的重定向工作方式有点像实验。它“抛出”重定向,并使用 componentDidCatch
捕获它。这很酷,因为它会导致整个渲染树停止,然后使用新位置重新开始。几年前我们首次发布此项目时,与 React 团队的讨论导致我们尝试了这种方法。
在遇到问题(例如,应用程序级别的 componentDidCatch
需要重新抛出重定向)后,我们决定在 React Router v6 中不再这样做。
但我们更进一步,认为重定向甚至不是 React Router 的工作。您的动态 Web 服务器或静态文件服务器应该处理此问题,并发送适当的响应状态代码,例如 301 或 302。
在 React Router 中匹配时具有重定向的能力,充其量需要您在两个地方配置重定向(您的服务器和您的路由),最糟糕的是鼓励人们只在 React Router 中进行重定向——这根本不会发送状态代码。
我们经常使用 Firebase 托管,因此以下是如何更新我们其中一个应用程序的示例
// @reach/router
<Router>
<Home path="/" />
<Users path="/events" />
<Redirect from="/dashboard" to="/events" />
</Router>
// React Router v6
// firebase.json config file
{
// ...
"hosting": {
"redirects": [
{
"source": "/dashboard",
"destination": "/events",
"type": 301
}
]
}
}
无论我们是在使用无服务器函数进行服务器渲染,还是仅将其用作静态文件服务器,这都适用。所有 Web 托管服务都提供配置此功能的方法。
如果您的应用程序仍然存在 <Link to="/events" />
,并且用户点击它,由于您使用的是客户端路由器,因此服务器不会参与。您需要更加勤勉地更新您的链接 😬。
或者,如果您想允许过时的链接,并且您意识到您需要在客户端和服务器上配置您的重定向,请继续复制粘贴我们即将发布但随后删除的 Redirect
组件。
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
function Redirect({ to }) {
let navigate = useNavigate();
useEffect(() => {
navigate(to);
});
return null;
}
// usage
<Routes>
<Route path="/" element={<Home />} />
<Route path="/events" element={<Users />} />
<Route
path="/dashboard"
element={<Redirect to="/events" />}
/>
</Routes>;
我们认为,通过根本不提供任何重定向 API,人们更有可能正确地配置它们。多年来,我们一直在无意中鼓励不良做法,现在我们想停止 🙈。
<Link getProps />
此道具获取器对于将链接样式化为“活动”很有用。决定链接是否处于活动状态有点主观。有时您希望它在 URL 完全匹配时处于活动状态,有时您希望它在部分匹配时处于活动状态,甚至还有更多涉及搜索参数和位置状态的边缘情况。
// @reach/router
function SomeCustomLink() {
return (
<Link
to="/some/where/cool"
getProps={(obj) => {
let {
isCurrent,
isPartiallyCurrent,
href,
location,
} = obj;
// do what you will
}}
/>
);
}
// React Router
import { useLocation, useMatch } from "react-router-dom";
function SomeCustomLink() {
let to = "/some/where/cool";
let match = useMatch(to);
let { isExact } = useMatch(to);
let location = useLocation();
return <Link to={to} />;
}
让我们看一些不太通用的例子。
// A custom nav link that is active when the URL matches the link's href exactly
// @reach/router
function ExactNavLink(props) {
const isActive = ({ isCurrent }) => {
return isCurrent ? { className: "active" } : {};
};
return <Link getProps={isActive} {...props} />;
}
// React Router v6
function ExactNavLink(props) {
return (
<Link
// If you only need the active state for styling without
// overriding the default isActive state, we provide it as
// a named argument in a function that can be passed to
// either `className` or `style` props
className={({ isActive }) =>
isActive ? "active" : ""
}
{...props}
/>
);
}
// A link that is active when itself or deeper routes are current
// @reach/router
function PartialNavLink(props) {
const isPartiallyActive = ({ isPartiallyCurrent }) => {
return isPartiallyCurrent
? { className: "active" }
: {};
};
return <Link getProps={isPartiallyActive} {...props} />;
}
// React Router v6
function PartialNavLink(props) {
// add the wild card to match deeper URLs
let match = useMatch(props.to + "/*");
return (
<Link className={match ? "active" : ""} {...props} />
);
}
“道具获取器”很笨拙,几乎总是可以用钩子替换。这也允许您使用其他钩子,例如 useLocation
,并执行更多自定义操作,例如使用搜索字符串使链接处于活动状态
function RecentPostsLink(props) {
let match = useMatch("/posts");
let location = useLocation();
let isActive =
match && location.search === "?view=recent";
return (
<Link className={isActive ? "active" : ""}>Recent</Link>
);
}
useMatch
useMatch
的签名在 React Router v6 中略有不同。
// @reach/router
let {
uri,
path,
// params are merged into the object with uri and path
eventId,
} = useMatch("/events/:eventId");
// React Router v6
let {
url,
path,
// params get their own key on the match
params: { eventId },
} = useMatch("/events/:eventId");
还要注意从 uri -> url
的更改。
感觉将参数与 URL 和路径分开更干净。
此外,没有人知道 URL 和 URI 之间的区别,因此我们不想开始一堆关于它的迂腐争论。React Router 一直称其为 URL,并且它拥有更多生产应用程序,因此我们使用 URL 而不是 URI。
<Match />
React Router v6 中没有 <Match/>
组件。它使用渲染道具来组合行为,但我们现在有了钩子。
如果您喜欢它,或者只是不想更新您的代码,很容易回溯
function Match({ path, children }) {
let match = useMatch(path);
let location = useLocation();
let navigate = useNavigate();
return children({ match, location, navigate });
}
现在我们有了钩子,渲染道具有点恶心(ew!)
<ServerLocation />
这里只是简单的重命名
// @reach/router
import { ServerLocation } from "@reach/router";
createServer((req, res) => {
let markup = ReactDOMServer.renderToString(
<ServerLocation url={req.url}>
<App />
</ServerLocation>
);
req.send(markup);
});
// React Router v6
// note the import path from react-router-dom/server!
import { StaticRouter } from "react-router-dom/server";
createServer((req, res) => {
let markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url}>
<App />
</StaticRouter>
);
req.send(markup);
});
如果您觉得本指南有所帮助,请告诉我们
打开拉取请求:请添加您需要的任何我们遗漏的迁移。
一般反馈:Twitter 上的 @remix_run,或发送电子邮件至 [email protected]。
谢谢!