使用 Router 可以构建多页的 Single-Page App
1 安装 react router :
npm intall react-router-dom
2 基本用法
2.1 App.js 导入Route 组件
import { Route } from "react-router-dom";
2.2 App.js 定义路径:
共两条路径:
http://localhost:3000/welcome
http://localhost:3000/products
不同的路径加载相对应的组件。
import { Route } from "react-router-dom";
import Welcome from "./components/Welcome";
import Products from "./components/Products";
function App() {
return (
<div>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/products">
<Products />
</Route>
</div>
);
}
export default App;
2.3 index.js 代码:
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
由于Welcome.js 和 Products.js 是与route 相关的组件,比起src/components ,把它们放在名称为src/pages 的文件夹中更恰当。
3 添加页面链接
3.1 标准链接方式,使用 <a> 实现页面导航。
此方法有一个缺点:每次点击链接,页面都会重新加载,App 重启,因此会丢失当前状态。
const MainHeader = () => {
return (
<header>
<nav>
<ul>
<li>
<a href="/welcome">Welcome</a>
</li>
<li>
<a href="/products">Products</a>
</li>
</ul>
</nav>
</header>
);
};
export default MainHeader;
function App() {
return (
<div>
<MainHeader />
<main>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/products">
<Products />
</Route>
</main>
</div>
);
}
3.2 使用 react-router-dom 的 Link 组件
将 <a> 改成 <Link> ,然后href 改成 to ,如下的代码将阻止浏览器默认行为,页面不会重新加载:
import { Link } from "react-router-dom";
const MainHeader = () => {
return (
<header>
<nav>
<ul>
<li>
<Link to="/welcome">Welcome</Link>
</li>
<li>
<Link to="/products">Products</Link>
</li>
</ul>
</nav>
</header>
);
};
export default MainHeader;
3.3 NavLink 和 activeClassName
使用 Link 组件虽然能避免页面重新加载,但是,用户无法直接看出当前点击了哪一个链接。 将 Link 改成 NavLink 就能使用 activeClassName 对当前链接添加 CSS 样式,对链接进行样式设定,
import { NavLink } from "react-router-dom";
import classes from "./MainHeader.module.css";
const MainHeader = () => {
return (
<header className={classes.header}>
<nav>
<ul>
<li>
<NavLink activeClassName={classes.active} to="/welcome">
Welcome
</NavLink>
</li>
<li>
<NavLink activeClassName={classes.active} to="/products">
Products
</NavLink>
</li>
</ul>
</nav>
</header>
);
};
export default MainHeader;
// MainHeader.module.css
.header {
width: 100%;
height: 5rem;
background-color: #044599;
padding: 0 10%;
}
.header nav {
height: 100%;
}
.header ul {
height: 100%;
list-style: none;
display: flex;
padding: 0;
margin: 0;
align-items: center;
justify-content: center;
}
.header li {
margin: 0 1rem;
width: 5rem;
}
.header a {
color: white;
text-decoration: none;
}
.header a:hover,
.header a:active,
.header a.active {
color: #95bcf0;
padding-bottom: 0.25rem;
border-bottom: 4px solid #95bcf0;
}
4 动态路由
4.1 注册带参数的动态路由
例如,要添加一系列 products 的详情页:
冒号必须加,
<Route path="/product-detail/:productId">
<ProductDetail />
</Route>
,:productId 是占位符,是 dynamic segment 此语法告诉 React Router, 为路径:
ourdomain.com/product-detail/ <any thing...>
加载 <ProductDetail /> 页面
4.2 使用 useParams 提取动态参数
钩子函数 useParams 返回一个key value对组成的对象,key 是动态路径的参数名。
注册路径:<Route path="/product-detail/:productId"> 输入路径 : localhost:3000/product-detail/product1 params.productId 的值为 product1
import { useParams } from "react-router-dom";
const ProductDetail = () => {
const params = useParams();
return (
<section>
<h1>Product Detail</h1>
<p>{params.productId}</p>
</section>
);
};
export default ProductDetail;
5 使用 switch 和 exact 配置路由
当前的问题,默认加载 全部 匹配输入路径的路由:
function App() {
return (
<div>
<MainHeader />
<main>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/products">
<Products />
</Route>
<Route path="/products/:productId">
<ProductDetail />
</Route>
</main>
</div>
);
}
例如:
http://localhost:3000/products/p2
此路径匹配两个路由: /products 和 /products/:productId 因此同时加载 两个 页面。 /products/:productId 以 /products 开头,所以匹配两个路径,与路由注册前后顺序无关。同时加载多个页面,有时,这是期望的行为,有时则不是,没有对错之分。
5.1 使用 Switch 组件实现唯一匹配
Switch 组件保证一个路径只加载一个路由,并且是 第1个 相匹配的路由: 按照以下的 Route 注册顺序, /products/:productId 不会被加载,因为是第2个匹配到的路由,只有第1个匹配的路由:/products 被加载。 (至少 React Router 5 的行为是这样)
import { Route, Switch } from "react-router-dom";
import Welcome from "./pages/Welcome";
import Products from "./pages/Products";
import MainHeader from "./components/MainHeader";
import ProductDetail from "./pages/ProductDetail";
function App() {
return (
<div>
<MainHeader />
<main>
<Switch>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/products">
<Products />
</Route>
<Route path="/products/:productId">
<ProductDetail />
</Route>
</Switch>
</main>
</div>
);
}
export default App;
5.2 exact 属性
为了解决上述 /products/:productId 不会被加载的问题,有两个 solution:
5.2.1 调整路由顺序
例如,调用两个匹配的路由的上下顺序: 因此 /products/p1 将匹配/products/:productId 而不是 /products
<Route path="/products/:productId">
<ProductDetail />
</Route>
<Route path="/products">
<Products />
</Route>
5.2.2 使用 exact 属性实现精确匹配
保持原始路由顺序不变,特定的路由添加 exact 属性,告诉 react router,当且仅当路径 完全匹配 时匹配路由。
下面的 code,/products/xyz 不再匹配 /products/:productId
function App() {
return (
<div>
<MainHeader />
<main>
<Switch>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/products" exact>
<Products />
</Route>
<Route path="/products/:productId">
<ProductDetail />
</Route>
</Switch>
</main>
</div>
);
}
6 嵌套路由
App.js 并非唯一可以设置路由的组件。路由也可以设置路由,所有的组件都可以添加路由:例如:
import { Route } from "react-router-dom";
const Welcome = () => {
return (
<section>
<h1>The welcome page</h1>
<Route path="/welcome/new-user">
<p>Welcome, new user!</p>
</Route>
</section>
);
};
export default Welcome;
7 使用 Redirect 组件实现重定向
下面的例子,如果用户输入/ 自动重定向到路由 /welcome , <Route path="/" exact> 的 exact 属性很重要,因为 / 匹配任意路由,没有此属性会无限循环。
import { Route, Switch, Redirect } from "react-router-dom";
import Welcome from "./pages/Welcome";
import Products from "./pages/Products";
import MainHeader from "./components/MainHeader";
import ProductDetail from "./pages/ProductDetail";
function App() {
return (
<div>
<MainHeader />
<main>
<Switch>
<Route path="/" exact>
<Redirect to="/welcome" />
</Route>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/products" exact>
<Products />
</Route>
<Route path="/products/:productId">
<ProductDetail />
</Route>
</Switch>
</main>
</div>
);
}
export default App;
|