目錄
- 基本特性
- ranked routes matching
- active links
- NavLink
- useMatch
- relative links
- 1. 相對路徑的使用
- 2. 嵌套路由的增強行為
- 3. 優勢和注意事項
- 4. . 和 ..
- 5. 總結
- data loading
- loading or changing data and redirect
- pending navigation ui
- skeleton ui with suspense
- data mutations with `<Route action>`
- busy indicators with route actions
- data fetchers
基本特性
- client side routing
- nested routes
- dynamic segments
比較好理解,這里不贅述。
ranked routes matching
https://reactrouter.com/en/main/start/overview#ranked-route-matching
When matching URLs to routes, React Router will rank the routes according to the number of segments, static segments, dynamic segments, splats, etc. and pick the most specific match.
這句話描述了 React Router 在匹配 URL 和路由時的策略,即根據路由的具體性來優先選擇最合適的匹配項。讓我們逐步解析這句話的含義:
-
URL 和路由的匹配:
- 當用戶訪問某個 URL 時,React Router 需要確定哪個路由規則最適合處理該 URL。例如,對于 URL
/users/123
,React Router 需要決定是匹配/users/:id
還是其他定義的路由。
- 當用戶訪問某個 URL 時,React Router 需要確定哪個路由規則最適合處理該 URL。例如,對于 URL
-
路由匹配的考量因素:優先級由高到低
-
路由的段數(Segments):URL 和路由可以分成多個段(segments),例如
/users/123
有兩個段,/users/:id
也有兩個段。React Router 會比較 URL 和每個路由的段數,越多的段數一般意味著路由更具體。 -
靜態段(Static Segments):靜態段是指在路由中直接指定的固定路徑,例如
/users
是一個靜態段。React Router 會考慮靜態段的數量來確定路由的具體性。 -
動態段(Dynamic Segments):動態段是指在路由中使用參數化路徑,例如
/users/:id
中的:id
是一個動態段。動態段的存在可能使得路由更靈活但也更具體。 -
通配符(Splat):通配符(如
*
)表示匹配多個路徑段,通常用于處理不確定數量的路徑部分。
-
-
最具體的匹配:
- React Router 會通過比較以上因素來確定哪個路由定義是最具體的匹配。具體的路由定義意味著它能夠最準確地匹配當前的 URL,而不會與其他可能的路由定義沖突。
-
示例:
<Route path="/teams/:teamId" />
<Route path="/teams/new" />
對于 http://example.com/teams/new.
會優先匹配第二個 Route。因為靜態段數為 2,更具體。
理解 React Router 的路由匹配策略,特別是根據路由的具體性來優先選擇最合適的匹配項,有助于開發者更有效地設計和管理復雜的路由結構。通過正確的路由定義和優先級排序,可以確保應用程序在導航和頁面渲染時行為符合預期,并能夠靈活地應對各種場景和URL路徑。
active links
NavLink
https://reactrouter.com/en/main/components/nav-link
<NavLinkstyle={({ isActive, isPending }) => {return {color: isActive ? "red" : "inherit",};}}className={({ isActive, isPending }) => {return isActive ? "active" : isPending ? "pending" : "";}}
/>
useMatch
https://reactrouter.com/en/main/hooks/use-match
function SomeComp() {const match = useMatch("/messages");return <li className={Boolean(match) ? "active" : ""} />;
}
relative links
理解 React Router 中 <Link>
和 <NavLink>
組件相對路徑的使用需要考慮它們與 HTML 中 <a>
標簽的行為差異,尤其是在嵌套路由場景下的增強行為。
1. 相對路徑的使用
-
HTML
<a>
標簽:在 HTML 中,使用<a>
標簽時,相對路徑通常相對于當前頁面的完整 URL。這意味著,相對路徑會根據當前頁面的路徑來構建最終的目標 URL。<a href="about">About</a>
- 如果當前 URL 是
http://example.com/home
,那么點擊上述鏈接將導航到http://example.com/about
。
- 如果當前 URL 是
-
React Router 中的
<Link>
和<NavLink>
:在 React Router 中,<Link>
和<NavLink>
組件可以接受相對路徑,但它們的行為略有不同。import { Link, NavLink } from 'react-router-dom';<Link to="about">About</Link> <NavLink to="about">About</NavLink>
- 這里的
to="about"
是相對路徑,相對于當前路由的路徑來構建目標 URL。例如,如果當前路由是/home
,那么這兩個鏈接將會導航到/home/about
。
- 這里的
2. 嵌套路由的增強行為
-
嵌套路由:當應用程序中存在嵌套路由時,React Router 的
<Link>
和<NavLink>
組件表現出更智能的行為,確保相對路徑的正確解析。<Route path="/home"><Link to="about">About</Link><NavLink to="about">About</NavLink> </Route>
- 在上述例子中,假設當前路由是
/home
,那么<Link>
和<NavLink>
組件會基于當前路由的路徑/home
構建相對路徑,導航到/home/about
。
- 在上述例子中,假設當前路由是
3. 優勢和注意事項
-
靈活性和便利性:相對路徑的使用使得在應用中鏈接管理更加靈活和簡單,尤其是在處理嵌套路由時。
-
注意路徑解析:確保理解相對路徑在不同嵌套層級下的解析規則。React Router 的行為通常是基于當前活動的路由來解析相對路徑,而不是簡單地相對于根路徑。
4. . 和 …
<Route path="/home"><Link to=".">About</Link><NavLink to=".">About</NavLink>
</Route>
- 在上述例子中,假設當前路由是
/home
,那么<Link>
和<NavLink>
組件會基于當前路由的路徑/home
構建相對路徑,導航到/home
。
<Route path="home" element={<Home />}><Route path="project/:projectId" element={<Project />}><Route path=":taskId" element={<Task />} /></Route>
</Route>
Project 中會渲染:
<Link to="abc"><Link to="."><Link to=".."></Link><Link to=".." relative="path">
- 在上述例子中,假設當前路由是
/home/project/123
,那么<Link>
會基于當前路由的路徑構建相對路徑,分別導航到/home/project/123/abc
、/home/project/abc
、/home
、/home/project
。
注意后面兩個的差異:
By default, the … in relative links traverse the route hierarchy, not the URL segments. Adding relative=“path” in the next example allows you to traverse the path segments instead.
5. 總結
理解 React Router 中 <Link>
和 <NavLink>
組件相對路徑的行為,特別是在嵌套路由情況下的增強行為,有助于開發者更有效地管理和導航應用程序中的鏈接。相對路徑的使用使得在不同層級和場景下的導航操作更加靈活和便捷,但需要注意理解和控制路徑的解析和構建規則。
data loading
https://reactrouter.com/en/main/start/overview#data-loading
Combined with nested routes, all of the data for multiple layouts at a specific URL can be loaded in parallel.
<Routepath="/"loader={async ({ request }) => {// loaders can be async functionsconst res = await fetch("/api/user.json", {signal: request.signal,});const user = await res.json();return user;}}element={<Root />}
><Routepath=":teamId"// loaders understand Fetch Responses and will automatically// unwrap the res.json(), so you can simply return a fetchloader={({ params }) => {return fetch(`/api/teams/${params.teamId}`);}}element={<Team />}><Routepath=":gameId"loader={({ params }) => {// of course you can use any data storereturn fakeSdk.getTeam(params.gameId);}}element={<Game />}/></Route>
</Route>
Data is made available to your components through useLoaderData.
function Root() {const user = useLoaderData();// data from <Route path="/">
}function Team() {const team = useLoaderData();// data from <Route path=":teamId">
}function Game() {const game = useLoaderData();// data from <Route path=":gameId">
}
When the user visits or clicks links to https://example.com/real-salt-lake/45face3, all three route loaders will be called and loaded in parallel, before the UI for that URL renders.
loading or changing data and redirect
https://reactrouter.com/en/main/route/loader#throwing-in-loaders
<Routepath="dashboard"loader={async () => {const user = await fake.getUser();if (!user) {// if you know you can't render the route, you can// throw a redirect to stop executing code here,// sending the user to a new routethrow redirect("/login");}// otherwise continueconst stats = await fake.getDashboardStats();return { user, stats };}}
/>
pending navigation ui
https://reactrouter.com/en/main/start/overview#pending-navigation-ui
When users navigate around the app, the data for the next page is loaded before the page is rendered. It’s important to provide user feedback during this time so the app doesn’t feel like it’s unresponsive.
function Root() {const navigation = useNavigation();return (<div>{navigation.state === "loading" && <GlobalSpinner />}<FakeSidebar /><Outlet /><FakeFooter /></div>);
}
skeleton ui with suspense
https://reactrouter.com/en/main/start/overview#skeleton-ui-with-suspense
Instead of waiting for the data for the next page, you can defer data so the UI flips over to the next screen with placeholder UI immediately while the data loads.
defer enables suspense for the un-awaited promises
<Routepath="issue/:issueId"element={<Issue />}loader={async ({ params }) => {// these are promises, but *not* awaitedconst comments = fake.getIssueComments(params.issueId);const history = fake.getIssueHistory(params.issueId);// the issue, however, *is* awaitedconst issue = await fake.getIssue(params.issueId);// defer enables suspense for the un-awaited promisesreturn defer({ issue, comments, history });}}
/>;function Issue() {const { issue, history, comments } = useLoaderData();return (<div><IssueDescription issue={issue} />{/* Suspense provides the placeholder fallback */}<Suspense fallback={<IssueHistorySkeleton />}>{/* Await manages the deferred data (promise) */}<Await resolve={history}>{/* this calls back when the data is resolved */}{(resolvedHistory) => (<IssueHistory history={resolvedHistory} />)}</Await></Suspense><Suspense fallback={<IssueCommentsSkeleton />}><Await resolve={comments}>{/* ... or you can use hooks to access the data */}<IssueComments /></Await></Suspense></div>);
}function IssueComments() {const comments = useAsyncValue();return <div>{/* ... */}</div>;
}
涉及如下 API 結合使用:
- defer
- Await
- useAsyncValue
data mutations with <Route action>
https://reactrouter.com/en/main/start/overview#data-mutations
HTML forms are navigation events, just like links. React Router supports HTML form workflows with client side routing.
When a form is submitted, the normal browser navigation event is prevented and a Request, with a body containing the FormData of the submission, is created. This request is sent to the <Route action>
that matches the form’s <Form action>
.
Form elements’s name prop are submitted to the action:
<Form action="/project/new"><label>Project title<br /><input type="text" name="title" /></label><label>Target Finish Date<br /><input type="date" name="due" /></label>
</Form>
<Routepath="project/new"action={async ({ request }) => {const formData = await request.formData();const newProject = await createProject({title: formData.get("title"),due: formData.get("due"),});return redirect(`/projects/${newProject.id}`);}}
/>
在 HTML 中,<form>
元素的 action
屬性定義了當用戶提交表單時將數據發送到的服務器端的 URL。
具體來說:
-
action
屬性的作用:- 當用戶提交表單時,瀏覽器會將表單中的數據發送到指定的 URL。
- 這個 URL 可以是相對路徑或絕對路徑。
- 如果
action
屬性未指定,表單會被提交到當前頁面的 URL(即自身)。
-
使用示例:
<form action="/project/new" method="post"><!-- 表單內容 --><input type="text" name="project_name" /><button type="submit">提交</button> </form>
- 在這個例子中,
action
屬性的值是"/project/new"
。當用戶點擊提交按鈕時,表單數據將被發送到當前服務器的/project/new
路徑。
- 在這個例子中,
-
重要說明:
- 如果
action
屬性指向一個相對路徑,表單數據會被提交到當前頁面的基礎 URL 加上action
的值。 - 如果
action
屬性是絕對路徑(例如http://example.com/project/new
),數據將被發送到指定的絕對路徑。
- 如果
-
HTTP 方法 (
method
屬性):- 另一個與
action
相關的重要屬性是method
,它指定了使用何種 HTTP 方法將表單數據發送到服務器。 - 常見的方法是
GET
和POST
。GET
方法將數據附加到 URL 上(可見),而POST
方法將數據包含在請求體中(不可見)。
- 另一個與
總結來說,action
屬性定義了表單數據提交的目標 URL。這對于將用戶輸入數據發送到后端處理或其他指定的處理程序非常重要。
busy indicators with route actions
https://reactrouter.com/en/main/start/overview#busy-indicators
When forms are being submitted to route actions, you have access to the navigation state to display busy indicators, disable fieldsets, etc.
function NewProjectForm() {const navigation = useNavigation();const busy = navigation.state === "submitting";return (<Form action="/project/new"><fieldset disabled={busy}><label>Project title<br /><input type="text" name="title" /></label><label>Target Finish Date<br /><input type="date" name="due" /></label></fieldset><button type="submit" disabled={busy}>{busy ? "Creating..." : "Create"}</button></Form>);
}
data fetchers
HTML Forms are the model for mutations but they have one major limitation: you can have only one at a time because a form submission is a navigation.
Most web apps need to allow for multiple mutations to be happening at the same time, like a list of records where each can be independently deleted, marked complete, liked, etc.
Fetchers allow you to interact with the route actions and loaders without causing a navigation in the browser, but still getting all the conventional benefits like error handling, revalidation, interruption handling, and race condition handling.
Imagine a list of tasks:
function Tasks() {const tasks = useLoaderData();return tasks.map((task) => (<div><p>{task.name}</p><ToggleCompleteButton task={task} /></div>));
}
Each task can be marked complete independently of the rest, with its own pending state and without causing a navigation with a fetcher:
function ToggleCompleteButton({ task }) {const fetcher = useFetcher();return (<fetcher.Form method="post" action="/toggle-complete"><fieldset disabled={fetcher.state !== "idle"}><input type="hidden" name="id" value={task.id} /><inputtype="hidden"name="status"value={task.complete ? "incomplete" : "complete"}/><button type="submit">{task.status === "complete"? "Mark Incomplete": "Mark Complete"}</button></fieldset></fetcher.Form>);
}