React-Router
React-Router
前端路由的核心是什么呢?
改变 URL,但是页面不进行整体的刷新
是什么
react-router
主要分成了几个不同的包:
react-router
: 实现了路由的核心功能react-router-dom
: 基于 react-router,加入了在浏览器运行环境下的一些功能react-router-native
:基于 react-router,加入了 react-native 运行环境下的一些功能react-router-config
: 用于配置静态路由的工具库
常用 API
BrowserRouter
(history
模式)、HashRouter
(hash
模式)Route
Link
、NavLink
switch
(V5)/Routes
(V6)redirect
(V5)/Navigate
(V6)useHistory
(V5)/useNavigate
(V6)
BrowserRouter
和HashRouter
Router
中包含了对路径改变的监听,并且会将相应的路径传递给子组件
BrowserRouter
是history
模式,HashRouter
是hash
模式(#)
使用两者作为最顶层组件包裹其他组件
root.render(
// <BrowserRouter>
<HashRouter>
<App/>
</HashRouter>
)
Route
用于路径的匹配,然后进行组件的渲染,对应的属性如下:
path
属性:用于设置匹配到的路径component
(V5) /element
(V6)属性:设置匹配到路径后,渲染的组件render
(V5) 属性:设置匹配到路径后,渲染的内容exact
(V5) 属性:开启精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
// V5
<Route path="/about" component={About} exact/>
<Route path="/about" render={() => <About />} />
// V6
<Route path="/about" element={<About />} />
Link
和NavLink
通常路径的跳转是使用Link
组件,最终会被渲染成a
元素,其中属性to
代替a
标题的href
属性
NavLink
是在Link
基础之上增加了一些样式属性,例如组件被选中时,发生样式变化,则可以设置NavLink
的一下属性:
activeStyle
:活跃时(匹配时)的样式(V6 中被移除)activeClassName
:活跃时添加的class
(V6 中被移除)
// V5
<NavLink to="/home" activeStyle={{color: "red"}} exact>首页</NavLink>
// V6
<NavLink to="/home" style={({isActive}) => ({color: isActive ? "red": ""})}>首页</NavLink>
<Link to="/home">首页</Link>
switch
和Routes
- v5: 使用
<Switch>
组件来包裹多个路由,确保只有一个路由被渲染。 - v6: 使用
<Routes>
组件来包裹多个路由,并且引入了<Outlet>
来表示子路由的渲染位置。
// V5
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/:userid" component={User} />
<Route component={NoMatch} />
</Switch>
// V6
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path='/home' element={<Home/>}>
<Route path='/home' element={<Navigate to="/home/recommend"/>}/>
<Route path='/home/recommend' element={<HomeRecommend/>}/>
</Route>
<Route path='/about' element={<About/>}/>
<Route path='/detail/:id' element={<Detail/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
redirect
和Navigate
用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to
路径中。这里演示 V6 的Navigate
。
render() {
const { isLogin } = this.state
return (
<div>
{/* 登录时自动跳转到home */}
{!isLogin ? <button onClick={e => this.login()}>登录</button>: <Navigate to="/home"/>}
</div>
)
}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path='/home' element={<Home/>}>
</Routes>
useHistory
和useNavigate
通过 JS 代码进行跳转的两个 hook 函数,只能在函数式组件中使用。
useHistory
hook 用于获取导航历史对象,可以用来在 React 组件中执行导航操作(V5)
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
const handleButtonClick = () => {
// 使用history对象进行导航
history.push('/new-page');
};
return (
<button onClick={handleButtonClick}>Go to New Page</button>
);
}
useNavigate
hook 是 React Router v6 中引入的新 hook,用于执行导航操作。与useHistory
不同,它返回一个函数,直接调用该函数即可执行导航。
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate();
const handleButtonClick = () => {
// 直接调用navigate函数进行导航
navigate('/new-page');
// 重定向
// navigate("/welcome", {replace: true});
};
return (
<button onClick={handleButtonClick}>Go to New Page</button>
);
}
基本使用
安装:npm install react-router-dom
导入 Router
import { HashRouter } from "react-router-dom"
将 Router 包裹根组件
root.render(
<StrictMode>
<HashRouter>
<App/>
</HashRouter>
</StrictMode>
)
类组件配置映射关系
Routes
:包裹所有的Route
,在其中匹配一个路由(Router5.x 使用的是Switch
组件替代Routes
)
export class App extends PureComponent {
render() {
return (
<div className='app'>
<div className='header'>
<span>header</span>
<div className='nav'>
{/* NavLink可以动态的加样式
style:传入函数,函数接受一个对象,包含isActive属性
className:传入函数,函数接受一个对象,包含isActive属性 */}
<NavLink to="/home" style={({isActive}) => ({color: isActive ? "red": ""})}>首页</NavLink>
<NavLink to="/about" className={({isActive}) => isActive?"link-active":""}>关于</NavLink>
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
</div>
</div>
<div className='content'>
{/* 配置路由映射表,映射关系: path => Component(Router5.x) */}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
< Route path='/home' element={<Home/>}>
{/* 路由嵌套 */}
<Route path='/home' element={<Navigate to="/home/recommend"/>}/>
<Route path='/home/recommend' element={<HomeRecommend/>}/>
{/* Not Found页面配置 */}
<Route path='*' element={<NotFound/>}/>
</ Route >
</Routes>
</div>
</div>
)
}
}
路由嵌套的使用及占位显示
<Outlet>
组件用于在父路由元素中作为子路由的占位元素。
render() {
return (
<div>
<h1>Home Page</h1>
<div className='home-nav'>
<Link to="/home/recommend">推荐</Link>
<Link to="/home/ranking">排行榜</Link>
</div>
{/* 占位的组件 */}
< Outlet />
</div>
)
}
JS 代码的路由跳转
在函数式组件中使用hooks
函数的useNavigate
来实现 js 代码的路由跳转功能。
export function App(props) {
// hooks函数必须放顶层使用,不能方法嵌套函数里面
const navigate = useNavigate()
function navigateTo(path) {
// 报错
// const navigate = useNavigate()
navigate(path)
}
return (
<div className='app'>
<div className='header'>
<div className='nav'>
<button onClick={e => navigateTo("/category")}>分类</button>
<span onClick={e => navigateTo("/order")}>订单</span>
</div>
</div>
<div className='content'>
{/* 映射关系: path => Component */}
<Routes>
<Route path='/category' element={<Category/>}/>
<Route path='/order' element={<Order/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
</div>
</div>
)
}
export default App
在类组件中利用高阶组件使用hooks
函数的useNavigate
来实现 js 代码的路由跳转功能
// 使用高阶组件封装hooks
import { useNavigate } from "react-router-dom"
// 高阶组件: 函数
function withRouter(WrapperComponent) {
return function(props) {
const navigate = useNavigate()
const router = { navigate }
return <WrapperComponent {...props} router={router}/>
}
}
export default withRouter
import { withRouter } from "../hoc"
export class Home extends PureComponent {
navigateTo(path) {
const { navigate } = this.props.router
navigate(path)
}
render() {
return (
<div>
<h1>Home Page</h1>
<div className='home-nav'>
<button onClick={e => this.navigateTo("/home/songmenu")}>歌单</button>
</div>
{/* 占位的组件 */}
<Outlet/>
</div>
)
}
}
export default withRouter(Home)
动态路由参数传递
调用hooks
来进行路由参数的获取
useParams
useLocation
useSearchParams
params 类型
eg. /router/123
params
参数
传递<Route path='/detail/:id' element={<Detail/>}/>
以下为结合useNavigate
传递参数
export class HomeSongMenu extends PureComponent {
constructor(props) {
super(props)
this.state = {
songMenus: [
{ id: 111, name: "华语流行" },
{ id: 112, name: "古典音乐" },
{ id: 113, name: "民谣歌曲" },
]
}
}
NavigateToDetail(id) {
const { navigate } = this.props.router
navigate("/detail/" + id)
}
render() {
const { songMenus } = this.state
return (
<div>
<h1>Home Song Menu</h1>
<ul>
{
songMenus.map(item => {
return <li key={item.id} onClick={e => this.NavigateToDetail(item.id)}>{item.name}</li>
})
}
</ul>
</div>
)
}
}
export default withRouter(HomeSongMenu)
params
参数
获取使用useParams()
hooks 函数获取params
参数
const params = useParams()
query 类型
eg. /router?id=123
query
参数
传递<Link to="/user?name=why&age=18">用户</Link>
query
参数
获取// 第一种方式
const location = useLocation()
console.log("location.search")
// 第二种方式(useSearchParams返回的是一个数组)
const [searchParams] = useSearchParams() // 通过searchParams.get('key')获取属性值
// 根据Entries创建一个对象
const query = Object.fromEntries(searchParams)
以上都是在函数式组件中使用**hooks
**来传递路由参数的,若想在类组件中传递路由参数,需要把**hooks
**封装到高阶组件中。
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"
// 高阶组件: 函数
function withRouter(WrapperComponent) {
return function(props) {
// 1.导航
const navigate = useNavigate()
// 2.获取动态路由的参数(params): /detail/:id
const params = useParams()
// 3.查询字符串的参数(query): /user?name=why&age=18
// 第一种方式
const location = useLocation()
// 第二种方式(useSearchParams返回的是一个数组)
const [searchParams] = useSearchParams() // 通过searchParams.get('key')获取属性值
// 根据Entries创建一个对象
const query = Object.fromEntries(searchParams)
const router = { navigate, params, location, query }
return <WrapperComponent {...props} router={router}/>
}
}
export default withRouter
路由的配置文件
// 懒加载
const Login = React.lazy(() => import("../pages/Login"))
// ...
const routes = [
{
path: "/",
element: <Navigate to="/home"/>
},
{
path: "/home",
element: <Home/>,
children: [
{
path: "/home",
element: <Navigate to="/home/recommend"/>
},
{
path: "/home/recommend",
element: <HomeRecommend/>
}
]
},
{
path: "/detail/:id",
element: <Detail/>
},
{
path: "/user",
element: <User/>
},
{
path: "*",
element: <NotFound/>
}
]
export default routes
在早期的时候,Router 并且没有提供相关的 API,我们需要借助于react-router-config
完成,在 Router6.x 中,为我们提供了useRoutes
API 可以完成相关的配置
import { useRoutes } from 'react-router-dom'
<div>{useRoutes(routes)}</div>
组件懒加载
build
时可以分块打包文件,如果对某些组件进行了异步加载(懒加载),那么需要使用Suspense
进行包裹
const Login = React.lazy(() => import("../pages/Login"))
root.render(
<StrictMode>
<HashRouter>
<Suspense fallback={<h3>Loading...</h3>}>
<App/>
</Suspense>
</HashRouter>
</StrictMode>
)