路由(React Router )
一、理解前端路由和后端路由
(一)什么是路由?
路由是根据不同的 url 地址展示不同的内容或页面;
(二)前端路由
- 最重要的就是页面不会刷新。 前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,每跳转到不同的URL都是使用前端的锚点路由.
- 随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载,不会向服务器发请求。
(二)后端路由
- 每次请求都会刷新页面。
- 浏览器在地址栏中切换不同的url时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件传给前端显示, 返回不同的页面。
- 后端路由对网速有一定的要求,并且前后端不分离
二、React Router 的使用
(一)概念
React Router 是官方提供的一个与 React 配套使用的前端路由库,它利用 HTML 5 的 history API,来实现组件的切换。
(二)步骤
-
使用脚手架工具来快速搭建一个 React 应用。
create-react-app 项目名
-
安装 router
npm install react-router-dom
-
导入:在入口文件
index.js
中导入import {BrowserRouter, HashRouter} from 'react-router-dom' ReactDOM.render( ( <BrowserRouter> <App/> </BrowserRouter> /*<HashRouter> <App /> </HashRouter>*/ ), document.getElementById('root') )
(三) BrowserRouter 和 HashRouter
-
BrowserRouter(推荐)
类似于 vue 中的历史模式,路径前边不会有 #。 用的是基于 HTML 5 的 History API,浏览器提供相应的接口来修改浏览器的历史记录。
-
HashRouter
路径前有 #。 通过改变地址后面的 hash 来改变浏览器的历史记录。
(四)一个简单的路由示例
-
App.jsx
文件- Route: 切换的路由组件,路由配置
- Link: 链接
- Switch: 类似于 vue 的
router-view
,显示视图。内部的配置即是路由的路径和跳转组件。 - Redirect: 重定向,当首次启动时,展示的组件。
// App.jsx 新创建的时候是js文件,后来重命名的 import React, { Component } from 'react' import { Route, Link, Switch, Redirect } from 'react-router-dom' // 导入路由组件 import About from '../views/About.jsx' // 引入 views 的组件(views 是自己新建的) import Home from '../views/Home.jsx' export default class App extends Component { render() { return ( <div> {/* 页头 */} <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> </div> </div> </div> <div className="row"> {/* 左边导航 */} <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/*导航路由链接*/} <Link className="list-group-item" to='/home'>Home</Link> <Link className="list-group-item" to='/about'>About</Link> </div> </div> {/* 右边内容区域 */} <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/*可切换的路由组件*/} <Switch> <Route path='/home' component={Home}/> <Route path='/about' component={About}/> {/* 重定向页面:可以理解为页面一打开就显示的页面 */} <Redirect to='/home'/> </Switch> </div> </div> </div> </div> </div> ) } }
三、嵌套路由
(一)注意
- 嵌套路由书写路由的时候要将父组件路由也写上,即一级路径。
(二)示例
// views/Home.js
import React from 'react'
import {Link, Switch, Route, Redirect} from 'react-router-dom'
import HomeChild from './homeAbout/HomeChild.jsx' // 导入 views 目录 ——> homeAbout 目录 ——> HomeChild.jsx 组件
export default function Home() {
return (
<div>
<div>
<ul className="nav nav-tabs">
<li>
<Link to='/home/homeChild'>HomeChild</Link>
</li>
</ul>
{/*可切换的路由组件*/}
<Switch>
<Route path='/home/homeChild' component={HomeChild} />
<Redirect to='/home/homeChild'/>
</Switch>
</div>
</div>
)
}
四、路由组件传参
(一)获取当前路由
-
const path = props.match.path;
(二)路由配置时传递参数
-
// 父组件 // 配置路由:跳转路由时带上参数 id <Switch> <Route path={`${path}/:id`} component={组件名}></Route> </Switch>
(三)获取参数
通过 props.match
来拿到传递过来的 id
-
// 子组件 const id = props.match.params.id
五、路由完整示例
-
目录结构
-
App.js(根组件):有两个导航组件 Home.jsx 和 About.jsx
// /src/App.jsx import React, { Component } from 'react' import { Route, Link, Switch, Redirect } from 'react-router-dom' import About from '../views/About.jsx' import Home from '../views/Home.jsx' export default class App extends Component { render() { return ( <div> {/* 页头 */} <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> </div> </div> </div> <div className="row"> {/* 左边导航 */} <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/*导航路由链接*/} <Link className="list-group-item" to='/home'>Home</Link> <Link className="list-group-item" to='/about'>About</Link> </div> </div> {/* 右边内容区域 */} <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/*可切换的路由组件*/} <Switch> <Route path='/home' component={Home}/> <Route path='/about' component={About}/> {/* 重定向页面:可以理解为页面一打开就显示的页面 */} <Redirect to='/home'/> </Switch> </div> </div> </div> </div> </div> ) } }
-
父组件(导航组件)
Home.jsx
// src/views/Home.jsx import React from 'react' export default function Home() { return ( <div> <p>Home</p> </div> ) }
-
父组件(导航组件)
About.jsx
// src/views/About.jsx import React from 'react' import { Route, Link, Switch, Redirect } from 'react-router-dom' import AboutChild1 from './aboutChild/AboutChild1.jsx' // 引入 About 组件的两个子组件 import AboutChild2 from './aboutChild/AboutChild2.jsx' export default function About() { return ( <div> <ul className="nav nav-tabs"> <li> <Link to="/about/aboutChild1">aboutChild1</Link> </li> <li> <Link to="/about/aboutChild2">aboutChild2</Link> </li> </ul> <div> <Switch> <Route path="/about/aboutChild1" component={AboutChild1}></Route> <Route path="/about/aboutChild2" component={AboutChild2}></Route> <Redirect to="/about/aboutChild1"/> </Switch> </div> </div> ) }
-
父组件
About
的子组件AboutChild1.jsx
:主要演示一下嵌套路由// / src/views/aboutChild/AboutChild1.jsx import React, { useState } from 'react' export default function AboutChild1() { let [stuArr,setStuArr] = useState(["嵌套路由001","嵌套路由002","嵌套路由003"]) // 自己声明一个数据 // 在 JSX 外面渲染。遍历数组,显示数组 let lis = stuArr.map((item,index) => { return <li key={index}>{item}</li> }) // JSX 展示视图 return ( <div> <ul> {lis} </ul> </div> ) }
-
父组件
About
的另一个子组件AboutChild2.jsx
:主要演示嵌套路由传参// / src/views/aboutChild/AboutChild2.jsx import React, { useState, useEffect } from 'react' import { Route, Link } from 'react-router-dom' import AboutChild2_1 from './aboutChild2_1/AboutChild2_1.jsx' // 引入路由传参下的文件下的文件 export default function AboutChild2(props) { let [messagesArr, setMessages] = useState([]) /* 函数式组件是通过 useEffect 方法去操作一些除渲染视图之外的副作用, 类定义组件是通过声明周期钩子函数 */ useEffect(() => { // 使用 setTimeout 模拟发送 Ajax 发请求拿数据,2秒后拿到数据显示 setTimeout(() => { // 模拟一个数据库数据 let stuData = [ { id: 1, title: "AboutChild2_001_动态参数1" }, { id: 2, title: "AboutChild2_002_动态参数2" }, { id: 3, title: "AboutChild2_003_动态参数3" } ] // 将数据设置到 messages 中 setMessages(stuData); }, 2000); // [] 阻止无限发请求 }, []) // 获取当前路由 let path = props.match.path; console.log(path); return ( <div> {/* 直接在 JSX 中渲染 */} { messagesArr.map(item => { return ( <ul key={item.id}> {/* 跳转路由的时候将 id 参数传过去 */} <Link to={`${path}/${item.id}`}><li>{item.title}</li></Link> </ul> ) }) } {/* 配置路由 */} <Route path={`${path}/:id`} component={AboutChild2_1}></Route> </div> ) }
-
AboutChild2.jsx
的子组件AboutChild2_1.jsx
// / src/views/aboutChild/aboutChild2_1/AboutChild2_1.jsx import React from 'react' // 创建一些详情数据 const messageDetails = [ { id: 1, title: 'Message001', content: '宋慧乔和宋仲基离婚' }, { id: 2, title: 'Message002', content: '范冰冰和李晨分手' }, { id: 3, title: 'Message003', content: '杜江否认出轨' }, ] export default function AboutChild2_1(props) { // 同样通过 props.match 来拿到传递过来的 id let id = props.match.params.id; // 通过 id 找到对应的内容(find 方法查找,返回一个对象) const item = messageDetails.find(item => item.id === id * 1) return ( <div> <ul> <li>{item.id}</li> <li>{item.title}</li> <li>{item.content}</li> </ul> </div> ) }
编程式导航
一、什么是编程式导航?
- 所谓编程式导航,即通过 js 代码来实现组件之间的跳转。
- 目前为止,我们使用的都是链接的方式来实现路由的跳转,也可以使用 js 代码来实现。
二、编程式导航语法(接收一个 props)
-
前进
props.history.goForward()
-
后退
props.history.goBack()
-
前进具体步数
props.history.go() // 括号里是具体数字 // 如: props.history.go(2) // 前进2步
-
push 跳转
// /home/message/detail/ 路由路径 // push跳转 + 携带 params参数 this.props.history.push(/home/message/detail/${id}/${title}) // push跳转 + 携带 search参数 this.props.history.push(/home/message/detail?id=${id}&title=${title}) // replace 跳转 + 携带state参数 this.props.history.push(/home/message/detail,{id,title})
-
replace 跳转
// /home/message/detail/ 路由路径 // replace 跳转 + 携带 params 参数 this.props.history.replace(/home/message/detail/${id}/${title}) // replace 跳转 + 携带query 参数 this.props.history.replace(/home/message/detail?id=${id}&title=${title}) // replace 跳转 + 携带state 参数 this.props.history.replace(/home/message/detail,{id,title})
三、push 和 replace 的区别
- push:跳转之后,点击“返回”可以返回上一级。
- replace:不能返回上一级,而是将上一路由替换,直接返回上上级。
四、示例(函数式组件演示)
-
改写 AboutChild2.jsx 的代码,如下:
import React, { useState, useEffect } from 'react' import { Route, Link } from 'react-router-dom' import AboutChild2_1 from './aboutChild2_1/AboutChild2_1.jsx' // 引入路由传参下的文件下的文件 export default function AboutChild2(props) { let [messagesArr, setMessages] = useState([]) /* 函数式组件是通过 useEffect 方法去操作一些除渲染视图之外的副作用, 类定义组件是通过声明周期钩子函数 */ useEffect(() => { // 使用 setTimeout 模拟发送 Ajax 发请求拿数据,2秒后拿到数据显示 setTimeout(() => { // 模拟一个数据库数据 let stuData = [ { id: 1, title: "AboutChild2_001_动态参数1" }, { id: 2, title: "AboutChild2_002_动态参数2" }, { id: 3, title: "AboutChild2_003_动态参数3" } ] // 将数据设置到 messages 中 setMessages(stuData); }, 2000); // [] 阻止无限发请求 }, []) // 获取当前路由 /about/aboutChild2 let path = props.match.path; /* 按钮事件 */ // 后退 function backHandle() { props.history.goBack() } // 前进 function forwardHandle() { props.history.goForward() } // push 跳转 function pushHandle(id){ props.history.push(`${path}/${id}`) } // replace 跳转 function replaceHandle(id){ props.history.replace(`${path}/${id}`) } return ( <div> {/* 直接在 JSX 中渲染 */} { messagesArr.map(item => { return ( <ul key={item.id}> {/* 跳转路由的时候将 id 参数传过去 */} <Link to={`${path}/${item.id}`}><li>{item.title}</li></Link> <button onClick={() => pushHandle(item.id)}>push</button> <button onClick={() => replaceHandle(item.id)}>replace</button> </ul> ) }) } {/* 配置路由 */} <Route path={`${path}/:id`} component={AboutChild2_1}></Route> <div> <button onClick={backHandle}>后退</button> <button onClick={forwardHandle}>前进</button> </div> </div> ) }