npmjs 仓库位置
Setup 1 2 3 4 5 6 npm create vite@latest name-of-your-project -- --template reactcd <your new project directory> npm install react-router-dom npm install localforage match-sorter sort-by npm run dev
有:
1 2 3 4 VITE v3.0.7 ready in 175 ms ➜ Local : http://127.0 .0.1 :5173 / ➜ Network: use --host to expose
(提示了 VITE 开发的端口信息)
入口文件 ./src/main.jsx
是入口文件.
添加一个路由 需要先创建一个 Browser Router (React Router 提供的一种组件类型).
之后在 Browser Router 中配置程序的第一个路由 (相当于默认打开时访问的位置). 简单来说就两步:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import * as React from "react" ;import * as ReactDOM from "react-dom/client" ;import { createBrowserRouter, RouterProvider , } from "react-router-dom" ;import "./index.css" ;const router = createBrowserRouter ([ { path : "/" , element : <div > Hello world!</div > , }, ]);ReactDOM .createRoot (document .getElementById ("root" )).render ( <React.StrictMode > <RouterProvider router ={router} /> </React.StrictMode > );
createBrowserRouter()
接受一个对象数组作为参数, 对象的 path
成员对应路由路径, element
对应渲染的页面元素 (替换为自己的根元素).
RouterProvider
组件, 用于将整个路由配置 (也就是一个 Browser Router) 传递给 React 应用程序.
错误处理 通过设置 createBrowserRouter()
创建对象的 errorElement
成员. (先用 useRouteError()
获取错误信息):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import { useRouteError } from "react-router-dom" ;export default function ErrorPage ( ) { const error = useRouteError (); console .error (error); return ( <div id ="error-page" > <h1 > Oops!</h1 > <p > Sorry, an unexpected error has occurred.</p > <p > <i > {error.statusText || error.message}</i > </p > </div > ); }import ErrorPage from "./error-page" ;const router = createBrowserRouter ([ { path : "/" , element : <Root /> , errorElement : <ErrorPage /> , }, ]);ReactDOM .createRoot (document .getElementById ("root" )).render ( <React.StrictMode > <RouterProvider router ={router} /> </React.StrictMode > );
1 2 3 4 5 6 7 8 9 10 11 12 13 import Contact from "./routes/contact" ;const router = createBrowserRouter ([ { path : "/" , element : <Root /> , errorElement : <ErrorPage /> , }, { path : "contacts/:contactId" , element : <Contact /> , }, ]);
这里的 :contactId
是一个占位符 (:
称 dynamic segment, 是必需的, 而 contactId
是自己命名的), 若访问路由 contacts/xxxxx
, 则 xxxxx
会被捕获并传递给 <Contact />
组件.
比如访问 /contacts/123
, 那么在 <Contact>
组件中, 调用 useParams()
, 会返回 { contactId: '123' }
.
嵌套路由 1 2 3 4 5 6 7 8 9 10 11 12 13 const router = createBrowserRouter ([ { path : "/" , element : <Root /> , errorElement : <ErrorPage /> , children : [ { path : "contacts/:contactId" , element : <Contact /> , }, ], }, ]);
此时, 需要在 <Root>
组件中, 通过 <Outlet>
组件 (react-router-dom
提供) 来指定 children 的 element 需要渲染在哪个地方:
1 2 3 4 5 6 7 8 9 10 11 12 import { Outlet } from "react-router-dom" ;export default function Root ( ) { return ( <> {/* all the other elements */} <div id ="detail" > <Outlet /> </div > </> ); }
Client Side Routing Client 指打开页面的用户, 另一侧是 Server, 提供页面.
Client Side Routing 指由用户端浏览器已经加载的 JavaScript 来管理路由, 而不是向服务器请求一个新页面. 这样可以提供更流畅的用户体验, 因为应用程序可以在不重新加载整个页面的情况下更新页面内容.
在这里通过 <Link>
组件替换掉原来的 <a href>
来实现. 如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { Outlet, Link } from "react-router-dom" ;export default function Root () { return ( <> <div id ="sidebar" > {/* other elements */} <nav> <ul> <li> <Link to={`contacts/1`}>Your Name</Link> </li> <li> <Link to={`contacts/2`}>Your Friend</Link> </li> </ul> </nav> {/* other elements */} </div> </> ); }
路径与组件的关联 URL 段一般设计和相关组件有关, 比如:
/
与 <Root>
相关
contacts/:id
与 <Contact>
相关
数据加载 可以利用 loader
在渲染组件之前预加载数据. 它在路由定义时指定, 并且在特定路由被访问时调用. 所加载的数据通常与该路由相关, 并且可以通过 useLoaderData
钩子在组件中访问.
首先, 定义一个 loader 函数. 这个函数可以是异步的, 用于获取所需的数据, 例如从 API 请求数据, 也可以在使用 loader 的组件中通过 useLoaderData
钩子获取 loader 返回的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export async function loader ( ) { const data = await fetch ('/api/data' ).then (response => response.json ()); return data; }export default function Root ( ) { const data = useLoaderData (); return ( <div > <h1 > Root Component</h1 > <pre > {JSON.stringify(data, null, 2)}</pre > </div > ); }
之后配置路由并指定 loader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { createBrowserRouter } from "react-router-dom" ;import Root , { loader as rootLoader } from "./routes/root" ;import ErrorPage from "./components/ErrorPage" ;import Contact from "./components/Contact" ;const router = createBrowserRouter ([ { path : "/" , element : <Root /> , errorElement : <ErrorPage /> , loader : rootLoader, children : [ { path : "contacts/:contactId" , element : <Contact /> , }, ], }, ]);export default router;
利用 HTML 表单进行导航 链接导航 传统的是链接导航, 即使用 <a>
标签创建超链接, 用户可以点击链接进行页面跳转, 如:
1 <a href ="/about" > About Us</a >
在 React 中为:
1 2 3 import { Link } from "react-router-dom" ;<Link to ="/about" > About Us</Link >
表单导航 而 HTML 表单导航为: 通过 HTML 表单元素 (<form>
) 及其相关属性和方法来实现页面之间的导航和数据提交.
一个典型的 HTML 表单包括以下几个基本部分:
<form>
标签: 定义表单的开始和结束
输入控件: 如文本框, 复选框, 单选按钮等, 用于用户输入数据
提交按钮: 用于提交表单数据
如:
1 2 3 4 5 <form action ="/submit-form" method ="post" > <label for ="name" > Name:</label > <input type ="text" id ="name" name ="name" > <button type ="submit" > Submit</button > </form >
这里有两个表单属性:
action: 指定表单数据提交的目标 URL
method: 指定表单数据提交的方法, 通常是 GET 或 POST
其工作原理为:
用户输入数据: 用户在表单中输入数据
提交表单: 用户点击提交按钮 (<button type="submit">
) 或触发提交事件
数据序列化: 浏览器将表单数据序列化为键值对
发送请求: 浏览器根据 action 和 method 属性, 将表单数据发送到指定服务器
处理响应: 服务器处理请求并返回响应, 浏览器根据响应执行相应的操作, 如页面重定向或显示结果
其与传统的链接导航主要区别在于可以指定请求方式如 GET
, POST
.
React 中为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { Form } from "react-router-dom" ;function ContactForm ( ) { return ( <Form method ="post" action ="/create-contact" > <label > Name: <input type ="text" name ="name" /> </label > <button type ="submit" > Create Contact</button > </Form > ); }export default ContactForm ;
在 React Router 中, 该表单会提交给客户端路由 (这里的 action 所指定的路由), 而不是服务器.
而路由通过设置 action
成员来确定用哪个函数来处理, 如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import { Form } from "react-router-dom" ;function MyForm ( ) { return ( <Form method ="post" > <button type ="submit" > New</button > </Form > ); }export default MyForm ;import { RouterProvider , createBrowserRouter } from "react-router-dom" ;import MyForm from "./MyForm" ;import HomePage from "./HomePage" ;import ErrorPage from "./ErrorPage" ;async function handleFormSubmit ({ request } ) { const formData = await request.formData (); console .log ("Form Data:" , Object .fromEntries (formData)); return null ; }const router = createBrowserRouter ([ { path : "/" , element : <HomePage /> , errorElement : <ErrorPage /> , children : [ { path : "current-page" , element : <MyForm /> , action : handleFormSubmit, }, ], }, ]);function App ( ) { return <RouterProvider router ={router} /> ; }export default App ;
在 Loader 中使用 URL Params 对于:
1 2 3 4 5 6 [ { path : "contacts/:contactId" , element : <Contact /> , }, ];
这里 contactId
捕获的数据会以第一个变量传递给 loader
指定的函数, 如:
1 2 3 4 export async function loader ({ params } ) { const contact = await getContact (params.contactId ); return { contact }; }
以此来访问.
利用表单更新数据 如:
1 2 3 4 5 6 7 <input placeholder="First" aria-label="First name" type="text" name="first" defaultValue={contact.first } />
若想访问表单提交过来的数据:
1 2 3 4 5 6 export async function action ({ request, params } ) { const formData = await request.formData (); const firstName = formData.get ("first" ); const lastName = formData.get ("last" ); }
先用 fromData()
得到数据主体, 之后用 get()
获取指定字段.
若想用常用的字段访问方式, 则用 fromEntries()
获取数据体:
1 2 3 const updates = Object .fromEntries (formData); updates.first ; updates.last ;
示例:
1 2 3 4 5 6 7 8 9 10 11 import { redirect, } from "react-router-dom" ;import { updateContact } from "../contacts" ;export async function action ({ request, params } ) { const formData = await request.formData (); const updates = Object .fromEntries (formData); await updateContact (params.contactId , updates); return redirect (`/contacts/${params.contactId} ` ); }
注意这里的 redirect()
, 返回一个 redirect response, 及让 client router 进行跳转处理.
对于 action
处指定的函数而言, 默认传入两个参数:
NavLink NavLink 是 React Router 中的一个组件, 用于创建导航链接, 并且可以根据当前 URL 的匹配情况为链接添加样式. 与普通的 Link 组件不同, NavLink 组件提供了 active 状态的管理.
1 import NavLink from "react-router-dom" ;
主要属性有:
to
: 指定导航链接的目标路径
exact
: 仅在路径完全匹配时才应用活动状态
activeClassName
: 指定在匹配时应用的 CSS 类名,默认为 active
activeStyle
: 指定在匹配时应用的内联样式
isActive
: 一个函数,返回布尔值,用于自定义活动状态的逻辑
示例:
1 2 3 4 5 6 7 8 9 10 11 12 <NavLink to={`contacts/${contact.id} ` } className={({ isActive, isPending } ) => isActive ? "active" : isPending ? "pending" : "" } > {} </NavLink >
当页面处于 NavLink
中的 URL 时, 即为 active.
导航状态处理 假设我们有一个博客应用, 用户点击文章标题时会导航到文章详情页. 在导航过程中,我们希望显示一个加载指示器. 我们可以使用 useNavigation
来实现这个功能.
useNavigation
钩子返回的导航状态对象包含了几个与当前导航状态相关的属性:
state, 表示当前的导航状态:
idle: 当前没有进行中的导航
loading: 正在进行导航, 通常是因为一个异步操作 (如数据获取) 正在进行
submitting: 表单正在提交
location: 当前的 Location 对象, 包含了关于当前 URL 的信息, 如 pathname, search, hash 等
navigationType: 表示当前导航的类型, 可以是以下值之一:
PUSH: 用户通过点击链接或调用 navigate 方法进行的导航。
POP: 用户通过浏览器的前进或后退按钮进行的导航。
REPLACE: 用户通过调用 navigate 方法的 replace 选项进行的导航
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import { useNavigation, } from "react-router-dom" ;export default function Root ( ) { const { contacts } = useLoaderData (); const navigation = useNavigation (); return ( <> <div id ="sidebar" > {/* existing code */}</div > <div id ="detail" className ={ navigation.state === "loading" ? "loading " : "" } > <Outlet /> </div > </> ); }
这里通过判断 state 来确定要使用的样式.
异常处理 使用 errorElement
, 如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export async function action ({ params } ) { throw new Error ("oh dang!" ); await deleteContact (params.contactId ); return redirect ("/" ); } ... [ { path : "contacts/:contactId/destroy" , action : destroyAction, errorElement : <div > Oops! There was an error.</div > , }, ]; ...
添加 Index Routes 当访问 /
时, 此时 children
处的 path 无法 match, 因此不会有任何显示, 可以通过添加一个 index route 来解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 export default function Index ( ) { return ( <p id ="zero-state" > This is a demo for React Router. <br /> Check out{" "} <a href ="https://reactrouter.com" > the docs at reactrouter.com </a > . </p > ); }import Index from "./routes/index" ;const router = createBrowserRouter ([ { path : "/" , element : <Root /> , errorElement : <ErrorPage /> , loader : rootLoader, action : rootAction, children : [ { index : true , element : <Index /> }, ], }, ]);
页面回退 利用 useNavigation()
可以实现回退到 browser’s history 的上一个 entry:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import { Form , useLoaderData, redirect, useNavigate, } from "react-router-dom" ;export default function EditContact ( ) { const { contact } = useLoaderData (); const navigate = useNavigate (); return ( <Form method ="post" id ="contact-form" > {/* existing code */} <p > <button type ="submit" > Save</button > <button type ="button" onClick ={() => { navigate(-1); }} > Cancel </button > </p > </Form > ); }
处理 URL Search Params 以及 GET Submissions 如:
1 2 3 4 5 6 7 8 9 10 11 <Form id="search-form" role="search" > <input id ="q" aria-label ="Search contacts" placeholder ="Search" type ="search" name ="q" /> <div id ="search-spinner" aria-hidden hidden ={true} /> <div className ="sr-only" aria-live ="polite" > </div > </Form >
可以发送 http://127.0.0.1:5173/?q=xxx
的请求.
处理如:
1 2 3 4 5 6 export async function loader ({ request } ) { const url = new URL (request.url ); const q = url.searchParams .get ("q" ); const contacts = await getContacts (q); return { contacts }; }
注意这里不是 POST 请求, 而是 GET 请求, 因此 React Router 不会触发 action, 代码也是添加到 loader 中.