以下是 V5 的语法,V6 的语法与之区别很大。
文章目录
使用 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 the library
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 重启,因此会丢失当前状态。
// MainHeader.js
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;
// App.js
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
并非唯一可以设置路由的组件。路由也可以设置路由,所有的组件都可以添加路由:例如:
// Welcome.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;