本文基于Reate-route 6.23.1版本制作
概述
对于多页面应用而言,一个 URL 对应的就是一个 HTML 页面,而对于单页面应用,一个 URL 对应的其实是一个组件的展示,可以通过 URL 来控制 UI 或者 HTML 的展示,这就是Reate-route。React Router 包含了三个库:
- react-router: 提供最基本的路由功能;
- react-router-dom: 在浏览器中使用;
- react-router-native: 在 react-native app开发时中使用,web开发时用不到;
React 路由也遵循:事件 => 状态变化 => 重新渲染这样的事件循环。大概核心内容如下:
安装
npm install react-router-dom --save
- 官网地址:https://reactrouter.com/en/main
核心架构
React-Route的大体设计如下图所示:
- 分为三大部分:管理者(Router)、生产者(Route)、消费者(Link等);
- Route创建URL和UI之间的mapping关系;
- Link等为交互动作,可引起应用当前URL的变化;
- Router负责监听当前应用的URL变化,并根据mapping关系渲染页面;
路由映射配置
路由器-Router
官方一共提供了6个,3个在客户端使用,3个用于服务端渲染。对于web程序建议只使用BrowserRouter,因为它是通过HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 和 URL 的同步的,兼容性会更好。
在实际应用时,建议使用createBrowserRouter
方式来创建,而不要用组件的方式。举例如下:
//index.js
import * as React from "react";
import * as ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./index.css";
import App from "./App";
const router = createBrowserRouter([
{
path: "/", //主页路径
element: <App />, //主页
childld:[],
},
//路由二
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
鉴于history也可以用这种方式来操作。
<a
href="/contact"
onClick={(event) => {
// stop the browser from changing the URL and requesting the new document
event.preventDefault();
// push an entry into the browser history stack and change the URL
window.history.pushState({}, undefined, "/contact");
window.location.pathname; // /getting-started/concepts/
window.location.hash; // #location
window.location.reload(); // force a refresh w/ the server
}}
/>
路由组-Routes
就是老版本的switch
功能,包裹所有的Route,每当位置发生变化时,都会查看其所有子路由,以找到最佳匹配,并渲染UI的该分支。
//标准定义格式可参考Outlet一节
<Routes>
<Route path="/" element={<Dashboard />}> {/*这种嵌套,一般会设置父路径为/user/*,这样的格式,注意后面是有个*的*/}
<Route path="messages" element={<DashboardMessages />}/>
<Route path="tasks" element={<DashboardTasks />} />
</Route>
<Route path="about" element={<AboutPage />} />
</Routes>
此处有时也可以加条件判断,比如:
<Routes>
<Route exact path='/' component={Home}/>
{ isUserLogin() && <Route exact path='/product' component={Product}/>,}
<Route path='/about' component={About}/>
</Routes>
建议用 ‘useRoutes()’ hook来实现统一管理,见最后,另外有些教程上说的路由嵌套就是在JSX组件中混合了Routes和Route标签。
路由-Route
Route底层实际上就是原有的静态组件最外层包装一个<Route/>
组件,由Router负责分析监听 URL 的变化,然后通过相应匹配的Route作出相应的UI改变。
const router = createBrowserRouter(
createRoutesFromElements(
<Route
element={<Team />}
path="teams/:teamId"
loader={async ({ params }) => {
return fetch(
`/fake/api/teams/${params.teamId}.json`
);
}}
action={async ({ request }) => {
return updateFakeTeam(await request.formData());
}}
errorElement={<ErrorBoundary />}
/>
)
);
定义如下:
interface RouteObject {
path?: string; //用于设置匹配到的路径
index?: boolean;//索引路径,即默认路径
children?: React.ReactNode;//函数返回要渲染的 React 元素
caseSensitive?: boolean;//指示路由匹配大小写或不匹配大小写
id?: string;//不建议使用
loader?: LoaderFunction;//路由加载器在路由渲染之前被调用,并通过useLoaderData为元素提供数据。
action?: ActionFunction;//当提交从表单、获取器或提交发送到路由时,会调用路由操作。
element?: React.ReactNode | null; //当路由与URL匹配时渲染的React元素/组件。
hydrateFallbackElement?: React.ReactNode | null;//不建议使用
errorElement?: React.ReactNode | null;//当路由在渲染、loader或action中抛出异常时,此React元素/组件将渲染
Component?: React.ComponentType | null;//功能同element,不建议使用
HydrateFallback?: React.ComponentType | null;//服务端渲染使用
ErrorBoundary?: React.ComponentType | null;//功能同errorElement,不建议使用
handle?: RouteObject["handle"];//任何特定于应用程序的数据,用于实现复杂功能
shouldRevalidate?: ShouldRevalidateFunction;//验证功能
lazy?: LazyRouteFunction<RouteObject>;//异步函数,每个lazy函数通常会返回动态导入的结果
}
路由参数
Route path属性说明
- 如果路径以
/:
开头:那么它就变成了“参数”。当路由与URL匹配时,将URL解析后作为params提供给其他路由器API。
<Route
// - /teams/hotspur
// - /teams/real
path="/teams/:teamId"
loader={({ params }) => {
console.log(params.teamId); // "hotspur"
}}
action={({ params }) => {}}
element={<Team />}
/>;
// and the element through `useParams`
function Team() {
let params = useParams();
console.log(params.teamId); // "hotspur"
}
- 如果路径以
?
结尾,就变成了可选部分,比如下列匹配"
// this path will match URLs like
// - /categories
// - /en/categories
// - /fr/categories
path="/:lang?/categories"
- 其它例子
// 多个参数的形式
<Route path="/c/:categoryId/p/:productId" />;
//普通定义,一般搜索时按这种方式来写,通过url.searchParams.get("name");方式来获取
<NavLink to='/detail?name=coder&age=18'>详情</NavLink>
//路由定义
import React, { PureComponent } from 'react';
import { Route, Routes, NavLink, Navigate } from 'react-router-dom';
<Route path='/detail/:id' element={<Detail />} />
//点击事件
import React, { PureComponent } from 'react';
import withRouter from '../hoc/withRouter';
export class Category extends PureComponent {
constructor(props) {
super(props);
this.state = {
arrList: [22222, 33333, 44444, 55555]
};
}
itemClick(id) {
const { router } = this.props;
// 跳转到详情页
router.navigate(`/detail/${id}`);
}
render() {
const { arrList } = this.state;
return (
<>
<div>Category</div>
<ul className='nav'>
{arrList.map((item, index) => {
return (
<li onClick={(e) => this.itemClick(item)} key={index}>
{item}
</li>
);
})}
</ul>
</>
);
}
}
export default withRouter(Category);
//嵌套路由
<Route path="teams" element={<Teams />}>
<Route path=":teamId" element={<Team />} />
<Route path="new" element={<NewTeamForm />} />
<Route index element={<LeagueStandings />} />
</Route>
//数据访问
let location = useLocation();
let urlParams = useParams();
let [urlSearchParams] = useSearchParams();
Route shouldRevalidate属性说明
数据验证,在使用,<fetcher.Form>,useSubmit,或fetcher.submit场景下生效。
<Route
path="meals-plans"
element={<MealPlans />}
loader={loadMealPlans}
shouldRevalidate={({ currentUrl }) => {
// only revalidate if the submission originates from
// the `/meal-plans/new` route.
return currentUrl.pathname === "/meal-plans/new";
}}
>
跳转组件
Link
在不同页面之间进行切换,最终会被翻译成一个<a/>
标签。
<div className='header'>
{/* 设置组件跳转 */}
<Link to='/home'>首页</Link>
<Link to='/about'>关于</Link>
</div>
对象定义
interface LinkProps{
to: To; //路径,指定一个url或组件
preventScrollReset?: boolean;//防止在单击链接时将滚动位置重置到窗口顶部
relative?: "route" | "path";
reloadDocument?: boolean;//跳过客户端路由,直接执行<a>默认行为,即跳转后是否刷新页面
replace?: boolean;//替换当前history中的堆栈信息
state?: any;//存储在历史状态内的新位置设置有状态值。随后可以通过useLocation()访问此值
unstable_viewTransition?: boolean;//为视图过渡应用特定样式
}
NavLink
NavLink 是特殊的 Link,可以为与当前 URL 匹配的 Link 元素增加样式属性。在匹配后NavLink就会添加上一个名字为active
的class。
<NavLink
to="/messages"
className={({ isActive, isPending }) =>
isPending ? "pending" : isActive ? "active" : ""
}
>
Messages
</NavLink>;
Navigate
Navigate用于重定向,当这个组件出现时,就会执行跳转到对应的to路径中,即原来的Redirect。这个组件一般用于在代码中编程使用。
//用于显示默认显示的页面
<Route path='/' element={<Navigate to='/home' />} />
//条件判断
{!isLogin ? <button onClick={(e) => this.login()}>登录</button> : <Navigate to='/home' />}
也可以编码实现:redirect
import { redirect } from "react-router-dom";
const loader = async () => {
const user = await getUser();
if (!user) {
return redirect("/login");
}
return null;
};
Outlet
用于在父路由元素中作为子路由的占位元素。
//定义Outlet
function Dashboard() { //做为父路由htmh的element属性值
return (
<div>
<h1>Dashboard</h1>
{/* 当URL=/messages时,则显示<DashboardMessages>;
当URL=/tasks时,则显示<DashboardTasks>;
当URL=/时,则显示Dashboard
*/}
<h2>
<Link to="/">Home</Link>
<Link to="/messages">Message</Link>
<Link to="/tasks">Tasks</Link>
</h2>
<Outlet /> {/*占位符*/}
</div>
);
}
//定义路由
function App() {
return (
<Routes>
<Route path="/" element={<Dashboard />}> {/*父路由*/}
<Route index element={<Home />} /> {/*默认路径,也可用Navigate来替换*/}
<Route path="messages" element={<DashboardMessages />}/>
<Route path="tasks" element={<DashboardTasks />} />
<Route path="*" element={<NoMatch />} /> {/*配置错误页面*/}
</Route>
</Routes>
);
}
//定义具体样式组件
function Home(){};
function DashboardMessages() {
return (
<div>
<h2>Dashboard</h2>
</div>
);
}
function DashboardTasks(){};
function NoMatch(){};
Form
注意这个Form第一个字母是大写的,这个在写代码时很容易弄混,其定义如下:
function Form(props: FormProps): React.ReactElement;
interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
method?: "get" | "post" | "put" | "patch" | "delete";
encType?:
| "application/x-www-form-urlencoded"
| "multipart/form-data"
| "text/plain";
action?: string;
onSubmit?: React.FormEventHandler<HTMLFormElement>;
fetcherKey?: string;
navigate?: boolean;
preventScrollReset?: boolean;
relative?: "route" | "path";
reloadDocument?: boolean;
replace?: boolean;
state?: any; //state={{ some: "value" }
unstable_viewTransition?: boolean;
}
例子如下:
- 定义表单
import { Form } from "react-router-dom";
function FilterForm() {
return (
<Form method="get" action="/slc/hotels">
<select name="sort">
<option value="price">Price</option>
<option value="stars">Stars</option>
<option value="distance">Distance</option>
</select>
<fieldset>
<legend>Star Rating</legend>
<label>
<input type="radio" name="stars" value="5" />{" "}
★★★★★
</label>
<label>
<input type="radio" name="stars" value="4" /> ★★★★
</label>
<label>
<input type="radio" name="stars" value="3" /> ★★★
</label>
<label>
<input type="radio" name="stars" value="2" /> ★★
</label>
<label>
<input type="radio" name="stars" value="1" /> ★
</label>
</fieldset>
<fieldset>
<legend>Amenities</legend>
<label>
<input
type="checkbox"
name="amenities"
value="pool"
/>{" "}
Pool
</label>
<label>
<input
type="checkbox"
name="amenities"
value="exercise"
/>{" "}
Exercise Room
</label>
</fieldset>
<button type="submit">Search</button>
</Form>
);
}
- 处理表单 :/slc/hotels?sort=price&stars=4&amenities=pool&amenities=exercise
<Route
path="/:city/hotels"
loader={async ({ request }) => {
let url = new URL(request.url);
let sort = url.searchParams.get("sort");
let stars = url.searchParams.get("stars");
let amenities = url.searchParams.getAll("amenities");
return fakeGetHotels({ sort, stars, amenities });
}}
/>
Hooks
更多Hooks可参考https://reactrouter.com/en/main/hooks/use-action-data
useNavigate() 手动路由跳转
import { Route, Routes,useNavigate } from 'react-router-dom';
export function App() {
// 1.定义Hook
const navigate = useNavigate();
return (
<div className=' app'>
<div className='header'>
{/* 2. 使用,点击跳转,注意我的这个链接没有对应的路由,此时一般会显示一个空白页面 */}
<button onClick={(e) => navigate('/category')}>分类</button>
<button onClick={(e) => navigate('/profily')}>我的</button>
</div>
<hr />
<div className='content'>
{/* 3. 定义路由 */}
<Routes>
<Route path='/category' element={<Category />} />
</Routes>
</div>
<hr />
<div className='footer'>footer</div>
</div>
);
}
function Category() {
return(
<div>Category</div>
)
}
export default App;
useRoutes() 路由配置集中管理
代替<Routes>
组件,实现路由配置集中管理。
import * as React from "react";
import { useRoutes } from "react-router-dom";
function App() {
let element = useRoutes([
{
path: "/",
element: <Dashboard />,
children: [
{
path: "messages",
element: <DashboardMessages />,
},
{ path: "tasks", element: <DashboardTasks /> },
],
},
{ path: "team", element: <AboutPage /> },
]);
return element;
}
Ajax访问
除了使用mwpf的fetch函数,也可以通过 cnpm i axios -D
组件来进行Ajax访问。
import axios from 'axios'
// get
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
// get带参数
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
// post请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
JSON处理
new Response(JSON.stringify(someValue), {
headers: {
"Content-Type": "application/json; utf-8",
},
});
路径生成
generatePath("/users/:id", { id: "42" }); // "/users/42"
generatePath("/files/:type/*", {
type: "img",
"*": "cat.jpg",
}); // "/files/img/cat.jpg"