React路由学习目标:
- 能够说出react路由的作用
- 能够掌握react-router-dom的使用
- 能够使用编程式导航跳转路由
- 能够知道react路由的匹配模式
1. 路由介绍
现代的前端应用大多数是SPA(单页面应用),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
- 前端路由功能:让用户从一个视图(页面)导航到另一个视图(页面)。
- 前端路由是一套映射规则,在react中,是
url路径与组件的对应关系
。 - 使用react路由简单来说,就是配置路径和组件
BroswerRouter与HashRouter的区别:
- BroswerRouter使用 HTML5 history API 的< Router >( pushState,replaceState 和 popstate 事件),让您的 UI 同步与 URL。
- HashRouter使用 url 中的 hash 部分来导航的路由器。(window.location.hash)。
注意:hash history 不支持 location.key 或 location.state。在以前的版本中,我们尝试对state进行匀称处理,但存在一些无法解决的极端情况。任何需要此state的代码或插件都将无法工作。由于此技术仅旨在支持旧版浏览器,因此我们建议您正确配置您的服务器并使用< BrowserRouter >
2. 路由的使用
- 安装:yurn add react-router-dom
- 导入路由三个核心组件:Router/Route/Link
import {BrowerRouter as Router,Routes,Route,Link} from 'react-router-dom'
- 使用Router组件包裹整个应用
const App = () => {
(
<Router>
<div id="app"></div>
</Router>
)
}
最基本示例:
import React from 'react'
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'
export default function App() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
</ul>
<Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/users" element={<Users />}></Route>
<Route path="/" element={<Home />}></Route>
</Routes>
</div>
</Router>
)
}
function Home() {
return <h2>Home</h2>
}
function About() {
return <h2>About</h2>
}
function Users() {
return <h2>Users</h2>
}
嵌套路由示例:
import React from 'react'
import {
BrowserRouter as Router,
Routes,
Route,
Link,
useParams,
} from 'react-router-dom'
export default function App() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
<li>
<Link to="/topic">嵌套路由</Link>
</li>
</ul>
<Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/users" element={<Users />}></Route>
<Route path="/topic/*" element={<Topics />}></Route>
<Route path="/" element={<Home />}></Route>
</Routes>
</div>
</Router>
)
}
function Home() {
return <h2>Home</h2>
}
function About() {
return <h2>About</h2>
}
function Users() {
return <h2>Users</h2>
}
function Topics() {
return (
<div>
<h3>Topics</h3>
<ul>
<li>
<Link to={'components'}>Components</Link>
<!-- http://localhost:3000/topic/components -->
</li>
<li>
<Link to={'props-v-state'}>Props v. State</Link>
<!-- http://localhost:3000/topic/props-v-state -->
</li>
</ul>
<Routes>
<Route path={':topicId'} element={<Topic />}></Route>
<Route path={''} element={<h3>Please select a topic.</h3>}></Route>
</Routes>
</div>
)
}
function Topic() {
let params = useParams()
return <h4>requested topic ID:{params.topicId}</h4>
}
注意:嵌套路由后面要使用通配符“*”
,且子路由不用再加上上一层路由,比如:< Link to={‘components’}>Components< /Link >,错误示例to={'/topic/components'}
。
更简便的嵌套路由:
import React from 'react'
import {
BrowserRouter as Router,
Routes,
Route,
Link,
Outlet,
useParams,
} from 'react-router-dom'
export default function App() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
<li>
<Link to="/topic">嵌套路由</Link>
</li>
</ul>
<Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/users" element={<Users />}></Route>
<Route path="/topic" element={<Topics />}>
<Route path=":topicId" element={<Topic />}></Route>
</Route>
<Route path="/" element={<Home />}></Route>
</Routes>
</div>
</Router>
)
}
function Home() {
return <h2>Home</h2>
}
function About() {
return <h2>About</h2>
}
function Users() {
return <h2>Users</h2>
}
function Topics() {
return (
<div>
<h3>Topics</h3>
<ul>
<li>
<Link to={'components'}>Components</Link>
</li>
<li>
<Link to={'props-v-state'}>Props v. State</Link>
</li>
</ul>
<Outlet></Outlet>
</div>
)
}
function Topic() {
let params = useParams()
return <h4>requested topic ID:{params.topicId}</h4>
}
两者不同点:
嵌套的子路由直接写在上一层的Route中,而原本的Route的位置使用Outlet组件做占位符
<Route path="/topic" element={<Topics />}>
<Route path=":topicId" element={<Topic />}></Route>
</Route>
- Router组件:包裹整个应用,一个React应用只需
使用一次
- 两种常用Router:HashRouter和BrowerRouter
- HashRouter:使用url的哈希值实现(http://localhost:3000/#/first)
- (推荐)BroswerRouter:使用H5的history的API实现
- Link组件:用于指定导航链接(a标签)
- 所有的Route需被Routes组件包裹着
3. 路由的执行过程
- 点击Link组件,修改了浏览器地址栏中的url
- react路由监听到地址栏url的变化
- react路由内部遍历所有Route组件,使用路由规则(path)与地址栏的location.pathname进行匹配
- 当路由规则(path)能够匹配地址栏中的pathname时,就在Route组件所在的位置展示该Route组件的内容
4. 变化对比
- < Switch >重命名为< Routes >。
- < Route >的新特性变更。
- 嵌套路由变得更简单。
- 用useNavigate代替useHistory。
- 新钩子useRoutes代替react-router-config。
第一点:
// v5
<Switch>
<Route exact path="/"><Home /></Route>
<Route path="/profile"><Profile /></Route>
</Switch>
// v6
import { HashRouter, Route, Routes } from "react-router-dom";
<div className="App">
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/test" element={<Test />} />
</Routes>
</HashRouter>
</div>
第二点:component/render被element替代
import Profile from './Profile';
// v5
<Route path=":userId" component={Profile} />
<Route
path=":userId"
render={routeProps => (
<Profile routeProps={routeProps} animate={true} />
)}
/>
// v6
<Route path=":userId" element={<Profile />} />
<Route path=":userId" element={<Profile animate={true} />} />
第四点:用 useNavigate 代替 useHistory
// v5
import { useHistory } from 'react-router-dom';
function MyButton() {
let history = useHistory();
function handleClick() {
history.push('/home');
};
return <button onClick={handleClick}>Submit</button>;
};
// v6
import { useNavigate } from 'react-router-dom';
function MyButton() {
let navigate = useNavigate();
function handleClick() {
navigate('/home');
};
return <button onClick={handleClick}>Submit</button>;
};
编程式导航:
// v5
history.push('/home');
history.replace('/home');
// v6
navigate('/home');
navigate('/home', {replace: true});
第五点:新钩子 useRoutes 代替 react-router-config
function App() {
let element = useRoutes([
{ path: '/', element: <Home /> },
{ path: 'dashboard', element: <Dashboard /> },
{ path: 'invoices',
element: <Invoices />,
children: [
{ path: ':id', element: <Invoice /> },
{ path: 'sent', element: <SentInvoices /> }
]
},
// 重定向
{ path: 'home', redirectTo: '/' },
// 404找不到
{ path: '*', element: <NotFound /> }
]);
return element;
}