當我們著手打造 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
具有相同的簽名大多數變更只有一些重新命名。如果您碰巧寫了一個 codemod,請與我們分享,我們會將它新增到本指南中!
在本指南中,我們將向您展示如何升級路徑代碼中的每個部分。我們將逐步進行,以便您可以在方便的時候進行一些變更、配送,然後再回來進行遷移。我們還會稍微討論一下「為什麼」進行這些變更,看起來像是一個簡單的重新命名,其實背後有更大的原因。
我們強烈建議您在遷移到 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 時可存取路由資料的 Hook。如果你先執行此動作,在升級到 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();
// ...
}
這些資料都已經存在於 context 中,但從應用程式程式碼中存取這些資料很麻煩,因此我們將這些資料建置到 props 中。Hook 簡化了從 context 存取資料的方式,因此我們不再需要透過路由資訊來污染 props。
不污染 props 對 TypeScript 也有點幫助,還可以避免你在檢視元件時想知道道具來自哪裡。如果你正在使用來自路由器的資料,現在完全清楚了。
此外,當頁面變大時,你會自然地將其拆分為多個元件,並最終將資料「道具串接」到樹狀結構的最底層。現在,你可以在樹狀結構中的任何位置存取路由資料。它不僅更方便,而且可以建立以路由器為中心的可組合抽象化。如果自訂 Hook 需要位置,它現在可以透過 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 的工作。你的動態網路伺服器或靜態檔案伺服器應該處理這項工作,並傳送適當的回應狀態碼(例如 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
}
]
}
}
無論我們是使用無伺服器函式進行伺服器渲染,還是只將其用作靜態檔案伺服器,這種方法都適用。所有網路託管服務都會提供設定此功能的方式。
如果你的應用程式中仍然存在 <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 />
這個 prop 取得器可用於將連結設定為「active」樣式。決定連結是否為 active 有點主觀。有時你希望它在 URL 完全符合時才為 active,有時你希望它在部分符合時為 active,甚至還有涉及搜尋參數和位置狀態的更多邊緣案例。
// @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} />
);
}
「prop 取得器」很笨重,而且幾乎都可以用鉤子取代。這也允許你使用其他鉤子,例如 useLocation
,並執行更多自訂動作,例如讓連結使用搜尋字串為 active 狀態
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/>
組件。它使用 render prop 來撰寫行為,但我們現在有鉤子了。
如果您喜歡,或只是不想更新程式碼,回朔並不難
function Match({ path, children }) {
let match = useMatch(path);
let location = useLocation();
let navigate = useNavigate();
return children({ match, location, navigate });
}
現在我們有掛勾了,呈現屬性變得很噁心(噁!)。
<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);
});
請讓我們知道這個指南是否有幫助
建立 Pull Request:請添加我們遺漏且您需要的任何遷移。
一般意見反應:推特 @remix_run,或傳送電子郵件至 hello@remix.run。
謝謝!