React 脚手架
React 在 VSCode 中需要安装的插件。
"ES7 React/Redux/GraphQL/React-Native snippets" 是一个非常流行的 VS Code 插件,它提供了一系列的代码片段,可以帮助你更快地编写 React、Redux、GraphQL 和 React Native 代码。常用的快捷键:
clg:控制台打印。jsxconsole.log();imr:导入 React。jsximport React from 'react';imrd:导入 ReactDOM。jsximport ReactDOM from 'react-dom';imrc:导入 React 和 Component。jsximport React, { Component } from 'react';impt:导入 PropTypes。jsximport PropTypes from 'prop-types';rcc:创建一个 React class 组件。jsximport React, { Component } from 'react'; class FileName extends Component { render() { return <div></div>; } } export default FileName;rfc:创建一个 React 函数组件。jsximport React from 'react'; function FileName() { return <div></div>; } export default FileName;rrd:创建一个 React Redux 组件。jsximport React, { Component } from 'react'; import { connect } from 'react-redux'; class FileName extends Component { render() { return <div></div>; } } const mapStateToProps = (state) => ({}); export default connect(mapStateToProps)(FileName);
第一章:认识脚手架
一、什么是 React 脚手架?
在我们的现实生活中,脚手架最常用的使用场景是在工地,它是为了保证施工顺利的、方便的进行而搭建的,在工地上搭建的脚手架可以帮助工人们高校的去完成工作,同时在大楼建设完成后,拆除脚手架并不会有任何的影响。
在我们的 React 项目中,脚手架的作用与之有异曲同工之妙。
React 脚手架其实是一个工具帮我们快速的生成项目的工程化结构,每个项目的结构其实大致都是相同的,所以 React 给我提前的搭建好了,这也是脚手架强大之处之一,也是用 React 创建 SPA 应用(单页应用程序,只有一个 HTML 页面,不利于 SEO)的最佳方式。
总结:脚手架可以帮助我们快速的搭建一个项目结构。
二、为什么要用脚手架?
在之前学习 webpack 的过程中,每次都需要配置 webpack.config.js 文件,用于配置我们项目的相关 loader、plugin,这些操作比较复杂,但是它的重复性很高,而且在项目打包时又很有必要,那 React 脚手架就帮助我们做了这些,它不需要我们人为的去编写webpack 配置文件,它将这些配置文件全部都已经提前的配置好了。
三、怎么用 React 脚手架?
1. 使用步骤
首先确保安装了 npm 和 Node。
然后打开 cmd 命令行工具,全局安装 create-react-app
npm i create-react-app -g然后可以新建一个文件夹用于存放项目。在新建的文件夹下执行:
create-react-app hello-react再在生成好的 hello-react 文件夹中执行:
npm start2. 脚手架项目结构
hello-react
├─ node_modules
├─ public // 公共资源
│ ├─ favicon.ico // 浏览器顶部的icon图标
│ ├─ index.html // 应用的 index.html入口 【重要】
│ ├─ logo192.png // 在 manifest 中使用的logo图
│ ├─ logo512.png // 同上
│ ├─ manifest.json // 应用加壳的配置文件
│ └─ robots.txt // 爬虫给协议文件
├─ src // 源码文件夹
│ ├─ App.css // App组件的样式
│ ├─ App.js // App组件 【重要】
│ ├─ App.test.js // 用于给APP做测试
│ ├─ index.css // 样式
│ ├─ index.js // 入口文件 【重要】
│ ├─ logo.svg // logo图
│ ├─ reportWebVitals.js // 页面性能分析文件
│ └─ setupTests.js // 组件单元测试文件
├─ .gitignore // 自动创建本地仓库
├─ package.json // 相关配置文件
├─ README.md
└─ yarn.lock再介绍一下 public 目录下的 index.html 文件中的代码意思。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- 直接采用 `%PUBLIC_URL%` 原因是 `webpack` 配置好了,它代表的意思就是 `public` 文件夹 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 用于配置安卓手机浏览器顶部颜色,兼容性不大好 -->
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- 苹果手机触摸版应用图标 -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- 应用加壳时的配置文件 -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>3. 第一个脚手架应用
1)public 目录
保持 public 中的 index.html 不变。
2)src 目录
修改 src 下面的 APP.js 以及 index.js 文件。
App.js: 【注意:创建好的组件一定要暴露出去】
// 创建外壳组件APP
import React from 'react'
class App extends React.Component{
render(){
return (
<div>Hello world</div>
)
}
}
export default Appindex.js: 【主要的作用其实就是将 App 这个组件渲染到页面上】
// 引入核心库
import React from 'react'
import ReactDOM from 'react-dom'
// 引入组件
import App from './App'
ReactDOM.render(<App />,document.getElementById("root"))这样在重新启动应用,就成功了。
不建议这样直接将内容放入 App 组件中,尽量还是用内部组件。
我们在写一个 Hello 组件:
import React,{Componet} from 'react'
export default class Hello extends Componet{
render() {
return (
<h1>Hello world</h1>
)
}
}在 App 组件中,进行使用。
import React,{Componet} from 'react'
import Hello from './Hello'
class App extends Component{
render(){
return (
<div>
<Hello />
</div>
)
}
}这样的结果和前面是一样的。
企业常见约束
在 src 目录下创建 components 文件夹,用来存放除了 APP 组件的其他组件。
在 components 文件夹下创建以组件名为文件夹名字的文件夹,里面存放 index.js 和 index.css(导入的时候只需要写到组件文件夹即可,其他的 Node.js 会补全的)。
最好组件使用 jsx 文件后缀。
4. 样式冲突
当组件逐渐增多起来的时候,我们发现,组件的样式也是越来越丰富,这样就很有可能产生两个组件中样式名称有可能会冲突,就会根据引入 App 这个组件的先后顺序,后面的会覆盖前面的。
为了避免这样的样式冲突,我们采用下面的形式:
1)将 css 文件名修改: hello.css ==> hello.module.css
2)引入并使用的时候改变方式:
import React,{Component}from 'react'
import hello from './hello.module.css' // 引入的时候给一个名称
export default class Hello extends Component{
render() {
return (
<h1 className={hello.title}>Hello</h1> // 通过大括号进行调用
)
}
}第二章:脚手架配置代理
React 本身只关注于页面,并不包含发送 Ajax 请求的代码,所以一般都是集成第三方的包,或者自己封装的。自己封装的话,比较麻烦,而且也可能考虑不全。
常用的有两个库,一个是 JQuery,一个是 axios。
① JQuery 比较重,因为 Ajax 服务也只是它这个库里的一小块功能,它主要做的还是 DOM 操作,而 React 自己就能操作 DOM,所以不推荐使用。
② axios 这个就比较轻,而且采用 Promise 风格,代码的逻辑会相对清晰,推荐使用。
因此这里采用 axios 来发送客户端请求。
以前,在发送请求的时候,经常会遇到一个很重要的问题:跨域!

在以前的学习中,基本上都需要操作后端服务器代码才能解决跨域的问题,配置请求头……这些都需要后端服务器的配合,因此我们前端需要自己解决这个问题的话,就需要这个技术了:代理。
在说代理之前,先谈谈为什么会出现跨域?
这个应该是源于浏览器的同源策略。所谓同源(即指在同一个域)就是两个页面具有相同的协议,主机和端口号, 当一个请求 URL 的协议、域名、端口三者之间任意一个与当前页面 URL 不同即为跨域 。
也就是说 xxx:3000和 xxx:4000 会有跨域问题,xxx:3000 与 abc:3000 有跨域问题
那接下来我们采用配置代理的方式去解决这个问题。
一、全局代理
它直接将代理配置在了配置文件 package.json 中。
"proxy":"http://localhost:5000" // "proxy":"请求的地址"这样配置代理时,首先会在原请求地址上访问,如果访问不到文件,就会转发到这里配置的地址上去请求。

我们需要做的就是在我们的请求代码中,将请求的地址改到转发的地址,即可。
但是这样会有一些问题,它会先向我们请求的地址,也就是这里的 3000 端口下请求数据,如果在 3000 端口中存在我们需要访问的文件,会直接返回,不会再去转发。
因此这就会出现问题,同时因为这种方式采用的是全局配置的关系,导致只能转发到一个地址,不能配置多个代理。
二、单独配置
这种配置方式,可以给多个请求配置代理,非常不错。
第一步:创建代理配置文件
在 src 目录下,创建代理配置文件 setupProxy.js。
注意:这个文件只能叫这个名字,脚手架在启动的时候,会自动执行这些文件。
第二步:编写 setupProxy.js 配置具体代理规则
1)引入 http-proxy-middleware 中间件。然后需要导出一个对象,这里建议使用函数,使用对象的话兼容性不大好。
2)在 app.use 中配置我们的代理规则。首先 proxy 接收的第一个参数是需要转发的请求,当有这个标志的时候,预示着需要采用代理。例如 /api1 ,就需要在我们 axios 的请求路径中,加上 /api1 ,这样所有添加了 /api1 前缀的请求都会转发到这。
3)第二个参数接受的是一个对象,用于配置代理。
target属性用于配置转发目标地址,也就是数据的地址。changeOrigin属性用于控制服务器收到的请求头中host字段,可以理解为一个伪装效果,为true时,收到的host就为请求数据的地址。pathRewrite属性用于去除请求前缀,因为通过代理请求时,需要在请求地址前添加一个标志,但是实际的地址是不存在这个标志的,所以一定要去除这个前缀。
配置一个代理的完整代码如下:
// 引入中间件
const proxy = require('http-proxy-middleware')
// 导出一个函数
module.exports = function(app) {
app.use(
proxy('/api1', { // api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', // 配置转发目标地址(能返回数据的服务器地址)
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
changeOrigin: true, // 控制服务器接收到的请求头中host字段的值
pathRewrite: {'^/api1': ''} // 去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
// 还可以继续写规则......
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}第三章:组件间通信
一、父给子传
props 属性。
二、子给父传
1. 回调函数
父组件把一个函数通过 props 传递给子组件,子组件在合适的时候调用并传入实参。
2. 消息订阅发布
说明
也适用于兄弟间传递。
消息订阅和发布的机制:订阅了一个消息假设为 A,当另一个人发布了 A 消息时,因为我们订阅了消息 A ,那么我们就可以拿到 A 消息,并获取数据。
那要怎么实现呢?
1)安装 pubsub-js 库
yarn add pubsub-js2)引入这个库
import PubSub from 'pubsub-js'3)订阅消息
通过 subscribe 来订阅消息,它接收两个参数。第一个参数是消息的名称,第二个是消息成功的回调,回调中也接受两个参数,一个是消息名称,一个是返回的数据。
PubSub.subscribe('search',(msg,data)=>{
console.log(msg, data);
})4)发布消息
采用 publish 来发布消息,用法如下。
PubSub.publish('search',{name:'tom',age:18})三、父与后传
1. 仅适用于类式组件
当想要给子类的子类传递数据时,马上想到 redux 的做法。这里介绍的 Context 我觉得也类似于 Redux。
需要引入一个 MyContext 组件,然后引用 MyContext 下的 Provider。
{/* React.createContext(defaultValue): defaultValue 是当组件在顶层查找过程中没有找到对应的 Provider,那么就使用默认值 */}
const MyContext = React.createContext();
const { Provider } = MyContext;用 Provider 标签包裹 A 组件内的 B 组件,并通过 value 值,将数据传递给子组件,这样以 A 组件为父代组件的所有子组件都能够接受到数据。
<Provider value={{ username, age }}>
<B />
</Provider>但是需要在使用数据的组件中引入 MyContext。
static contextType = MyContext;在使用时,直接从 this.context 上取值即可。
const {username, age} = this.context2. 适用于函数和类式组件
由于函数式组件没有自己的 this,所以不能通过 this.context 来获取数据。
这里就需要从 Context 身上暴露出一个 Consumer。
const { Provider, Consumer} = MyContext;然后通过 value 取值即可。
function C() {
return (
<div>
<h3>我是C组件,我要从A接收到数据</h3>
<Consumer>
{
(value) => {
{/* 基于 context 值进行渲染 */}
return `${value.username},年龄是${value.age}`;
}
}
</Consumer>
</div>
);
}第四章:路由
说明
旧版 react 路由(React Router 5)。
一、相关概念
1. SPA
单页 Web 应用(single page web application, SPA)。整个应用只有一个完整的页面。
点击页面中的链接不会刷新页面,只会做页面的局部更新。
数据都需要通过 ajax 请求获取,并在前端异步展现。
2. 路由的原理
1)实现方式
history
前端路由主要依靠的是 history,也就是浏览器的历史记录。history 是 BOM 对象下的一个属性。在 H5 中新增了一些操作 history 的 API。
浏览器的历史记录就类似于一个栈的数据结构,前进就相当于入栈,后退就相当于出栈。并且历史记录上可以采用 listen 来监听请求路由的改变,从而判断是否改变路径。
在 H5 中新增了 createBrowserHistory 的 API ,用于创建一个 history 栈,允许我们手动操作浏览器的历史记录。新增 API:pushState、replaceState,原理类似于 Hash 实现。 用 H5 实现,单页路由的 URL 不会多出一个 # 号,这样会更加的美观。
点我查看代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>前端路由的基石_history</title>
</head>
<body>
<!-- 有onclick事件,不会跳转,只会调用函数 -->
<a href="https://www.baidu.com" onclick="return push('/test1') ">push test1</a><br><br>
<button onClick="push('/test2')">push test2</button><br><br>
<button onClick="replace('/test3')">replace test3</button><br><br>
<button onClick="back()"><= 回退</button>
<button onClick="forword()">前进 =></button>
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
<script type="text/javascript">
// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
let history = History.createHashHistory() //方法二,hash值(锚点)
//向浏览器的历史记录中放一些记录
function push (path) {
history.push(path)
return false
}
//replace是替换栈顶的那条历史记录
function replace (path) {
history.replace(path)
}
function back() {
history.goBack()
}
function forword() {
history.goForward()
}
//监听url路径的变化
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
</script>
</body>
</html>hash
使用锚点(也称为哈希或者片段标识符)来实现一种简单的前端路由。这种方式的优点是兼容性好,即使在不支持 HTML5 History API 的老版本浏览器中也可以使用。
点我查看代码
<!-- 创建一些链接,点击这些链接会改变URL的哈希 -->
<a href="#home">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
<!-- 创建一些div,我们会根据URL的哈希来显示或隐藏这些div -->
<div id="home">This is the home page.</div>
<div id="about">This is the about page.</div>
<div id="contact">This is the contact page.</div>
<script>
// 首先,隐藏所有的div
document.querySelectorAll('div').forEach(div => {
div.style.display = 'none';
});
// 然后,根据URL的哈希来显示对应的div
function route() {
// 获取URL的哈希
const hash = window.location.hash;
// 隐藏所有的div
document.querySelectorAll('div').forEach(div => {
div.style.display = 'none';
});
// 显示对应的div
const div = document.querySelector(hash);
if (div) {
div.style.display = 'block';
}
}
// 当URL的哈希改变时,调用route函数
window.addEventListener('hashchange', route);
// 当页面加载时,调用route函数
window.addEventListener('load', route);
</script>2)BrowserRouter 和 HashRouter 的区别
底层实现原理不一样
对于 BrowserRouter 来说它使用的是 React 为它封装的 history API,这里的 history 和浏览器中的 history 有所不同噢!通过操作这些 API 来实现路由的保存等操作,但是这些 API 是 H5 中提出的,因此不兼容 IE9 以下版本。
对于 HashRouter 而言,它实现的原理是通过 URL 的哈希值(锚点跳转。因为锚点跳转会保存历史记录,从而让 HashRouter 有了相关的前进后退操作,HashRouter 不会将 # 符号后面的内容请求。兼容性更好!)。
地址栏的表现形式不一样
HashRouter 的路径中包含 #,例如 localhost:3000/#/demo/test。
刷新后路由 state 参数改变
在 BrowserRouter 中,state 保存在 history 对象中,刷新不会丢失。
HashRouter 则刷新会丢失 state。
3. 路由组件和一般组件
1)写法不一样
一般组件:<Demo>
路由组件:<Route path="/demo" component={Demo}/>
2)存放的位置一般不同
一般组件:components
路由组件:pages
3)接收的内容【props】
一般组件:写组件标签的时候传递什么,就能收到什么。
路由组件:接收到三个固定的属性【history, location, match】。
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"二、路由实现
1. 基本路由
react-router-dom 专门给 web 人员使用的库。
引入 react-router-dom 库,暴露一些属性 Link、BrowserRouter...
import { Link, BrowserRouter, Route } from 'react-router-dom'导航区的 a 标签改为 Link 标签。
<Link className="list-group-item" to="/about">About</Link>同时需要用 Route 标签,来进行路径的匹配,从而实现不同路径的组件切换。
<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>这样之后我们还需要一步,加个路由器,在上面我们写了两组路由,同时还会报错指示我们需要添加 Router 来解决错误,这就是需要我们添加路由器来管理路由,如果我们在 Link 和 Route 中分别用路由器管理,那这样是实现不了的,只有在一个路由器的管理下才能进行页面的跳转工作。

因此我们也可以在 Link 和 Route 标签的外层标签采用 BrowserRouter 包裹,但是这样当我们的路由过多时,我们要不停的更改标签包裹的位置,因此我们可以这么做。
我们回到 App.jsx 目录下的 index.js 文件,将整个 App 组件标签采用 BrowserRouter 标签去包裹,这样整个 App 组件都在一个路由器的管理下。还有 HashRouter。
// index.js
<BrowserRouter>
< App />
</BrowserRouter>NavLink 标签
<NavLink> 组件会在其指向的路由与当前 URL 匹配时,给自己添加一个"active"的类或样式。可以通过 activeStyle 属性来设置激活时的内联样式,或者通过 activeClassName 属性来设置激活时的类名。
import { NavLink } from 'react-router-dom';
// ...
<NavLink to="/home" activeStyle={{ color: 'red' }}>Home</NavLink>
<NavLink to="/about" activeClassName="active">About</NavLink>属性
<NavLink> 只会在其 to 属性完全匹配当前 URL 时才会激活。如果想在部分匹配时就激活,可以添加 exact 属性。例如,<NavLink to="/" exact> 只会在 URL 完全等于"/"时激活,而不会在 URL 为"/about"或"/contact"时激活。
NavLink 封装
在上面的 NavLink 标签中,可以发现每次都需要重复的去写这些样式名称或者是 activeClassName ,这并不是一个很好的情况,代码过于冗余。那是不是可以想想办法封装一下它们呢?
可以采用 MyNavLink 组件,对 NavLink 进行封装。
首先需要新建一个 MyNavLink 组件。return 一个结构。
<NavLink className="list-group-item" activeClassName="active" {...this.props} />有一点非常重要的是,在标签体内写的内容都会成为一个 children 属性,因此在调用 MyNavLink 时,在标签体中写的内容,都会成为 props 中的一部分,从而能够实现。
接下来在调用时,直接写。
<MyNavLink to="/home">home</MyNavLink>即可实现相同的效果。
Switch 的使用
Route 的机制是当匹配上了第一个 <Route path="/xxx" component={xxx}></Route> 组件后,它还会继续向下匹配,如果下面还有一个 path 与现在一样的,那么就会一起展示。但是,一般情况下,一个路径只对应一个组件,怎么解决效率低的问题?
可以采用 Switch 对组件进行包裹。
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/about" component={About}></Route>
</Switch>在使用 Switch 时,需要先从 react-router-dom 中暴露出 Switch 组件。
样式错误
- 使用 %PUBLIC_URL%。
- 引入样式文件时不带
.。- 使用 HashRouter。
2. 重定向路由
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
function App() {
return (
<Router>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route path="/about">
<AboutPage />
</Route>
<Redirect from="/old-path" to="/new-path" />
<Route>
<NotFoundPage />
</Route>
</Switch>
</Router>
);
}
function HomePage() {
return <h2>Home</h2>;
}
function AboutPage() {
return <h2>About</h2>;
}
function NotFoundPage() {
return <h2>404 Not Found</h2>;
}
export default App;定义了三个路由:"/"(Home 页面)、"/about"(About 页面)和一个捕获所有未匹配路由的 404 页面。我们还定义了一个重定向,当 URL 为"/old-path"时,会自动重定向到"/new-path"。
请注意,<Switch> 组件会渲染与当前 URL 匹配的第一个 <Route> 或 <Redirect>。所以,我们把 404 页面的 <Route> 放在了最后,这样只有当所有其他路由都不匹配时,才会渲染 404 页面。
另外,404 页面的 <Route> 没有 path 属性,所以它会匹配所有的 URL。如果你想让它只匹配某些特定的 URL,你可以添加一个 path 属性,例如 <Route path="/404">。
还可以不写 from 属性,那么页面找不到指定路径时,就会重定向到 /home 页面。
<Redirect to="/home" />3. 嵌套路由
当点击 /home/news 的 Link 标签时,会先从最开始注册的路由找,找到 Home 组件,渲染。
<Switch>
<Route path="/about" component={About}></Route> {/* 不匹配 */}
<Route path="/home" component={Home}></Route> {/* 匹配,渲染此组件 */}
<Redirect to="/404" />
</Switch>紧接着到 Home 组件里,发现又注册了路由,继续匹配。
<Switch>
<Route path="/home/news" component={About}></Route> {/* 匹配,不在继续向下查找了 */}
<Route path="/home/message" component={Home}></Route>
</Switch>因此,不能在
<Route path="/home" component={Home}></Route>标签上添加exact属性。因为,添加上后,/home与/home/news就不匹配了,直接到/404路由。
4. 编程式导航
1)路由跳转
push 与 replace 模式。
push 方法会在浏览器历史中添加一个新的记录,然后跳转到这个新的 URL。这就像用户点击了一个链接或者在地址栏输入了一个新的 URL。当用户点击浏览器的后退按钮时,他们会返回到上一个 URL。
replace 方法会替换浏览器历史中的当前记录,然后跳转到这个新的 URL。这就像用户在地址栏输入了一个新的 URL,然后按下了回车键。当用户点击浏览器的后退按钮时,他们会返回到上上一个 URL,因为当前的 URL 已经被替换掉了。
<NavLink replace to="/about" activeClassName="active">About</NavLink>2)实现编程式导航
采用绑定事件的方式实现路由的跳转,在按钮上绑定一个 onClick 事件,当事件触发时,执行一个回调 replaceShow。
这个函数接收两个参数,用来仿制默认的跳转方式,第一个是点击的 id、第二个是标题。
在回调中,调用 this.props.location 对象下的 replace 方法。
replaceShow = (id, title) => {
// params 参数
this.props.history.replace(`/home/message/detail/${id}/${title}`)
// search 参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
// state 参数
// this.props.history.replace(`/home/message/detail`, {id, title})
}
replaceShow = (id, title) => {
// params 参数
this.props.history.push(`/home/message/detail/${id}/${title}`)
}同时还可以借助 this.props.history 身上的 API 实现路由的跳转,例如 go(正负数)、goBack() 、goForward()。
3)withRouter
当需要在页面内部添加回退前进等按钮时,由于这些组件是一般组件的方式去编写,因此会遇到一个问题,无法获得 history 对象,这正是因为采用的是一般组件造成的。
只有路由组件才能获取到 history 对象。如何解决这个问题呢?
利用 react-router-dom 对象下的 withRouter 函数来对导出的 Header 组件进行包装,这样就能获得一个拥有 history 对象的一般组件。
需要对哪个组件包装就在哪个组件下引入。
// Header/index.jsx
import { withRouter } from 'react-router-dom'
// ......
class Header extends Component {
// ......
}
// 在最后导出对象时,用 `withRouter` 函数对 index 进行包装
export default withRouter(Header);这样就能让一般组件获得路由组件所特有的 API。
三、路由传参
实现路由传参的路由,也叫动态路由。
1. 传递 params 参数
1)在 Link 标签的 to 上添加传递的参数。
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>2)在 Route 标签的 path 上添加占位符 /:数据名。
<Route path="/home/message/detail/:id/:title" component={Detail} />那之后怎么在 detail 组件中拿到传递过来的数据呢?还记得前面说过 路由组件:接收到三个固定的属性【history, location, match】,这些属性是以 props 传递的,也就是说,在 props 属性中有这三个属性。打印 this.props 来查看当前接收的数据情况。

明白啦,使用 const { id, title } = this.props.match.params 就能拿到传递过来的数据了。
2. 传递 search 参数
1)在 Link 中采用 ? 符号的方式来表示后面的为可用数据。
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>2)采用 search 传递的方式,无需在 Route 中再次声明,可以在 Detail 组件中直接获取到。

可以发现,数据都保存在了 location 对象下的 search 中,是以字符串的形式保存的。因此,可以引用一个库来进行转化 querystring。
import qs from 'querystring'这个库是 React 中自带的,它有两个方法,一个是 parse、一个是 stringify。基本用法如下:
const querystring = require('querystring');
// 解析查询字符串
let parsed = querystring.parse('key1=value1&key2=value2');
console.log(parsed); // 输出:{ key1: 'value1', key2: 'value2' }
// 格式化对象为查询字符串
let str = querystring.stringify({ key1: 'value1', key2: 'value2' });
console.log(str); // 输出:'key1=value1&key2=value2'那么,使用 parse 方法,将字符串转化为键值对形式的对象。
const qs = require('querystring');
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))这样就能成功的获取数据,并进行渲染。
3. 传递 state 参数
采用传递 state 参数的方法,是最完美的一种方法,因为它不会将数据携带到地址栏上,采用内部的状态来维护。
<Link to={
{ pathname: '/home/message/detail',
state: { id: msgObj.id, title: msgObj.title }
}
}>
{msgObj.title}
</Link>首先,需要在 Link 中注册跳转时,传递一个路由对象,包括一个跳转地址名,一个 state 数据,这样就可以在 Detail 组件中获取到这个传递的 state 数据。
注意:采用这种方式传递,无需声明接收。

可以在 Detail 组件中的 location 对象下的 state 中取出所传递的数据。
const { id, title } = this.props.location.state解决清除缓存造成报错的问题,我们可以在获取不到数据的时候用空对象来替代,例如:
const { id, title } = this.props.location.state || {}当获取不到 state 时,则用空对象代替。
这里的 state 和状态里的 state 有所不同。
四、路由守卫
如何使用 <Prompt> 组件或者路由的生命周期钩子(例如 history.block)来阻止路由的改变。
