React16学习笔记
React16学习笔记
📌不再建议学习React16了,因为太旧了。如果工作中遇到,可以拿来做参考。
写在最前面,该文档是初学 React 时整理的,内容基本和教程一致,没有自己的特色,主要就是为了方便查阅。后期会不断的丰富和完善。我在写这篇文章的时候 React 都已经出 18.2.0 了,基本上16版本已经算是废弃了。但是最后一个版本是2022年6月14日发布的,写这篇文章是 2023年9月9日 可能 React 要出更新的版本吧。希望不要停更啊,不然刚学会就告诉我这技术废掉了,那我真他妈。
一、React入门
相关的参考文档
1.1 React的基本使用
编写第一个 Hello World应用程序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 渲染容器 -->
<div id="app">
</div>
<!-- 提示这里如果本地没有下载就使用如下CDN -->
<!-- <script src="https://unpkg.com/react@18/umd/react.development.js"></script> -->
<!-- <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> -->
<!-- Don't use this in production: -->
<!-- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> -->
<script src="./js/react16.js"></script>
<script src="./js/react-dom16.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
// 1.创建虚拟DOM
const VDOM = <h1>Hello World</h1>
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('app'))
</script>
</body>
</html>
1.2 React JSX
- 定义虚拟DOM时不要写引号
- 标签中混入JS表达式要使用花括号
- 样式的类名不能使用class,要使用className
- 内联样式,要用 style={{key:value}}的形式去写key要求是小驼峰
- 只有一个根标签
- 标签必须闭合
- 标签首字母
标签首字母小写,则将该标签转为对应的HTML元素,若不存在,则报错
标签首字母大写,React 去渲染对应的组件,若组件未定义,则报错
<body>
<!-- 渲染容器 -->
<div id="app"></div>
<script src="./js/react16.js"></script>
<script src="./js/react-dom16.js"></script>
<script src="./js/babel.min.js"></script>
<script type=text/babel
1.3 安装 React 开发者工具
React Devloper Tools
二、React面向组件编程
2.1 模块与组件和模块化与组件化
模块:在前端开发中,模块一般指JS文件,一个JS文件代表一个模块
模块化:把多个JS模块组合起来就是模块化
组件:实现一个局部功能的代码和资源的集合
组件化:多个组件就形成组件化的应用
2.2 函数式组件
函数式组件示例,在我写这篇文章的时候 React 最推崇的是函数式组件
<script type="text/babel">
// 创建函数式组件
function Hello(props) {
return <h1>hello {props.name}</h1>
}
// 渲染组件
ReactDOM.render(<Hello name="凯爸爸"/>, document.getElementById('app'))
2.3 类式组件
类式组件的示例
<script type="text/babel">
// 创建类式组件
class Hello extends React.Component {
render() {
return <h1>hello world</h1>;
}
}
ReactDOM.render(<Hello />, document.getElementById("app")
2.4 react中的事件处理
React中的事件通过 onXXX进行声明,React使用事件委托
<button onClick="{this.submit}">submit</button>
2.5 三大核心之 state
2.5.1 state 完整写法
示例,这里还是需要强调一下需要强化一下JS类相关的知识,要不然看着还真费劲,就只能死记硬背
<script type="text/babel">
class WeatherChange extends React.Component {
constructor(props) {
super(props);
this.state = {
isHot: true,
};
this.changeWeather = this.changeWeather.bind
在 构造器中对 state 进行属性赋值,通过 setState 修改 state 中的属性值,该操作属于合并而不是覆盖,例如:
// state = {isHot:false,wind:'hsjj'}
// 当执行 setState({isHot:true}) 时
// state = {isHot:true,wind:'hsjj'}
2.5.2 state简写
<script type="text/babel">
class WeatherChange extends React.Component {
state = {isHot: true};
render() {
const { isHot } = this.state;
return <h1 onClick={this.changeWeather}>今天的天气很 {isHot ? '炎热' : '凉爽'} <h1
2.6 三大核心之 props
2.6.1 传递单个props示例
<script type="text/babel">
class MyComponent extends React.Component {
render() {
const { name, age, sex } = this.props;
return (
<div>
<h1>打印个人信息</h1>
<ul>
<li>姓名:nameli
2.6.2 批量传递props示例
<script type="text/babel">
class MyComponent extends React.Component {
render() {
const { name, age, sex } = this.props;
return (
<div>
<h1>打印个人信息</h1>
<ul>
<li>姓名:nameli
2.6.3 对props进行限制
<script type="text/babel">
class MyComponent extends React.Component {
// props 限制
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
};
defaultProps
2.6.4 类式组件中的构造器和props
类式组件如果使用构造器就需要显式的调用super(props),否则会出现this.props是undefined的bug
<script type="text/babel">
class MyComponent extends React.Component {
constractor(props){
super(props)
// 不传props会导致this.props是undefined
// super()
// console.log('constractor',this.props)
}
// render(){} ... 略
}
ReactDOM.render(
2.6.5 函数式组件使用props
<script type="text/babel">
function MyComponent(props) {
const { name, age, sex } = props;
return (
<div>
<h1>打印个人信息</h1>
<ul>
<li>姓名:{name}</li>
<li>年龄:ageli
2.7 三大核心之 ref
2.7.1 字符串形式的Ref
字符串形式的Ref,React官方已经不建议使用,因为存在效率问题
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (
<div>
<input ref="input1" type="text" placeholder="请输入" />
<button
type="button"
onClick={() => {
2.7.2 回调形式的Ref
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (
<div>
<input ref={ref => this.input1 = ref} type="text" placeholder="请输入" />
<button
type="button"
onClick
回调Ref中调用次数的问题
如果写成内联函数,初始化时执行一次,并在页面每次更新的时候会执行2次。如果写成 class 绑定函数只会在初始化时执行一次。
2.7.3 createRef
<script type="text/babel">
class MyComponent extends React.Component {
input1 = React.createRef();
input2 = React.createRef();
render() {
return (
<div>
<input ref={this.input1} type placeholder
2.8 受控组件和非受控组件
2.8.1 非受控组件
需要使用较多的ref
<script type="text/babel">
class Login extends React.Component {
submit = (e) => {
e.preventDefault();
console.log(this.username.value, this.password.value)
}
render(
2.8.2 受控组件
数据的存储和更新都在state中,需要的时候去拿
<script type="text/babel">
class Login extends React.Component {
state = {
username: '',
password: ''
}
submit = (e) => {
e.preventDefault();
console.log(
受控组件和非受控组件属于一种概念,应用在输入类的DOM中
2.9 函数柯里化和高阶函数
2.9.1 函数柯里化
通过函数调用的方式继续返回函数,实现多次接受参数后最后统一处理的方式
function sum (a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
sum(1)(2)(3)
2.9.2 高阶函数
若A函数接收的参数是一个函数,那么这个函数就是高阶函数
若A函数调用的返回值依然是一个函数,那么A就可以称为高阶函数
常见的高阶函数:Promise、setTimeout等等
2.10 react生命周期
2.10.1 旧版生命周期

1.组件挂载流程
constructor -> componentWillMount -> render -> componentDidMount
因为 componentWillMount 是旧版的生命周期钩子,建议使用 UNSAFE_componentWillMount 代替
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0 }
console.log("count-constructor");
}
add = ()
2.setState
组件更新流程
<script type="text/babel">
class MyComponent extends React.Component {
state = { count: 0 }
add = () => {
const { count } = this.state
this.setState({ count: count + 1 })
}
3.forceUpdate
组件强制更新
<script type="text/babel">
class MyComponent extends React.Component {
state = { count: 0 }
add = () => {
const { count } = this.state
this.setState({ count: count + 1 })
}
4.shouldComponentUpdate
该钩子建议使用 UNSAFE_componentWillUpdate
如果手动设置了该钩子,则需要指定返回值。返回值为Boolean类型,如果返回true则接着走更新流程,如果返回false则不往下走。
shouldComponentUpdate(){
console.log("count-shouldComponentWillUpdate");
return false
}
5.componentWillReceiveProps
父子组件,子组件的props有新值时会触发
<script type="text/babel">
class Parent extends React.Component {
state = { carName: '奔驰' }
ChangeCarName = () => {
this.setState({ carName: '宝马' })
}
render() {
return
2.10.2 新版生命周期

首先需要说明的是新版本指的已经不是16版本了,当然18版本的生命周期也是如此
新版本的生命周期对比旧版本的生命周期,废弃了三个钩子
componentWillMount
componentWillUpdate
componentWillReceiveProps
新增了两个钩子
getDerivedStateFromProps
getSnapshotBeforeUpdate
1.getDerivedStateFromProps
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props){
super(props)
console.log("MyComponent-constructor");
this.state = {count:0}
}
static getDerivedStateFromProps(
state的值在任何时候都取决于props。使用场景比较罕见,使用频率也很低。说实话我都很奇怪,这钩子能干啥。
2.getSnapshotBeforeUpdate
一个简单的案例演示一下用法
<script type="text/babel">
class NewsList extends React.Component {
state = { newsList: [] }
componentDidMount() {
console.log('componentDidMount')
this.autoRefreshData()
}
getSnapshotBeforeUpdate() {
三、React脚手架
3.1 使用脚手架创建项目
全局安装 create-react-app
npm i create-react-app -g
创建项目
create-react-app hello-react
修改 package.json中 react和react-dom的版本,脚手架默认会创建最新版本,本教程是16的教程,所以需要修改一下
{
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-scripts": "2.1.5"
}
删除 node_modules
删除 package-lock.json
使用 npm install --legacy-peer-deps安装依赖
修改 index.js入口文件,16和18有些区别
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
3.2 项目的目录结构
每个项目都不太一样,但大致都是如下构造

3.3 简易版 TODOS 案列
该案例主要考察组件拆分,组件通信,JS数组常用的API

案例地址:
四、React AJAX
4.1 脚手架配置代理
方式一:
在项目的 package.json中加入如下代码,此方式的缺陷很明显
{
"proxy": "http://localhost:5000/students"
}
方式二:
在 项目 src下创建 setupProxy.js配置文件,并添加如下内容
// 低版本可使用此写法
// const proxy = require("http-proxy-middleware");
// 高版本采用如下写法
const { createProxyMiddleware: proxy } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
proxy("/api", {
target: "http://localhost:5000",
changeOrigin: true,
pathRewrite: { "^/api": "" },
})
);
}
4.2 github搜索案例
该案列主要考察组件拆分、网络请求

案例地址:
4.3 消息订阅与发布
暂未编写。
五、React Router
5.1 前端路由
浏览器打开一个页面之后,浏览器地址栏发生变化,然后页面上的内容也随之呈现与地址栏对应的内容,但是这个过程中浏览器并没有刷新。
5.1.2 前端路由的原理
- 首先我们需要有方法让浏览器地址栏发生改变——在保证页面不刷新的前提下
- 浏览器地址栏发生改变了,我们需要能感知到这个改变,也就是我们最熟悉的事件监听,即对地址栏改变这个事件进行监听
- 在监听的回调函数中,我们修改页面的内容,即展示新地址栏对应的组件
5.1.3 HashRouter的实现
改变浏览器的URL
页面的url组成(相关文章)最后一部分是锚点(hash)部分,也就是#xxx的内容,这一部分的特点就是http请求并不携带这部分的内容,并且修改hash并不会导致浏览器的刷新(可以随意打开一个网址,然后在最后面写一个#开头的字符串比如#dasfhuasdhf,页面并没有刷新),除了这种手动修改的方式修改hash的内容,还可以通过a标签的href属性来提供给用户修改hash的交互接口,如下html结构,通过三个a标签提供给了用户修改hash的三个按钮,可以类比vue中的<router-link>,react中的<Link>:
<div class="router-nav">
<a href="/#/" class="nav-item">首页</a>
<a href="/#/center" class="nav-item">个人中心</a>
<a href="/#/about" class="nav-item">关于</a>
</div
对浏览器地址hash部分改变的监听
为啥hash模式相较于history实现起来要简单一些(虽然写完发现都不难😄),就是因为当hash改变时会触发window对象的hashchange事件,相当于浏览器原生环境帮咱铺平道路了,泰裤辣,直接写代码吧,思路在注释里:
window.addEventListener("hashchange", (hashchangeEvent) => {
// 1. 总逻辑——修改div.page-content里面的内容
// 2. hashchangeEvent对象里面有个newURL属性,就是在hashchange之后浏览器地址栏里的完整内容,稍微一处理我们就可以拿到#后面的内容(#后面的内容方便与路由配置里的path进行匹配)
// 3. 新的hash字符串即为event.newURL.split("#")[1]
// 4. 上面拿到当前的hash字符串然后去对比路由配置,展示需要展示的组件即可,可以写一个matchComponent函数实现
});
实现 matchComponent 函数,实现hash与页面展示内容的匹配
const matchComponent = (routes, path) => { // routes即为路由配置数组,path即为浏览器地址栏中当前的hash字符串
const { component } = routes.find(route => route.path === path); // 寻找routes数组中与传进来的path匹配的路由配置对象
// 将component的内容呈现在div.page-content里
}
最终代码
class HashRouter {
// constructor的目标就是给hashchange事件添加监听,回调函数refresh的逻辑就是更改页面需要展示的内容
constructor(routes = []) {
this.routes = routes; // 路由映射数组
this.currentHash = ""; // 记录当前地址栏中的hash字符串,也就对应了应该展示的路由组件
// 这里需要永久改变一下refresh的指向,改为HashRouter实例,因为它作为事件回调传递给addEventListener之后的调用就跟HashRouter实例没关系了,但是refresh方法里又要用到其他类方法,如执行this.matchComponent(),所以需要让this始终指向HashRouter实例,才能正确访问类方法与类属性
this.refresh = this.refresh.bind(this);
// 因为我们的页面一上来加载并没有触发hashchange事件,为了让一开始路由组件也正常显示出来一个,这里我们也监听一下load事件让首屏不空白(本玩具的边缘情况😄)
window.addEventListener("load", this.refresh);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title
5.1.3 History 路由的实现
先简单提一句,所谓history模式,相较于hash模式的区别就是url里没有#,看起来更美观一些罢了,实现起来麻烦一些,但总的实现思路与hash一样,分三步:
- 确定url改变的方法
- 对url改变的监听
- 监听的回调函数中对视图的修改(感觉这一步总结为核心有点鸡肋,其实真正的核心感觉就是url改变与对url的监听)
前置知识
浏览器访问历史栈
说白了浏览器对与访问记录的记录用的数据结构是个栈,然后有个指针指向这个栈的某个数据,对应当前显示的网页,举个例子,我们打开浏览器,此时栈中只有当前页面,指针也指向这个记录,左上角的前进后退按钮都无效(因为只有一个记录数据),浏览器栏里输入一个新网址然后访问,新网址就被push进栈了,指针也指向了新记录,然后我们发现可以点击后退按钮了,相当于指针从栈顶移动到栈底,也就是第一个页面。
有个细节:从上面我们知道,有个所谓的“指针”,这个指针可以通过浏览器左上角的前进后退按钮在栈中移动。假设现在我们的指针不在栈顶,即可以点击浏览器的前进按钮,然后这时候我们不管是点击页面上的链接或者是从浏览器地址栏里输入一个新的网址,栈中增加的记录是基于当前指针为栈顶的子栈为基础的(不懂的话读起来就比较绕了,直接看后面例子)。举个具体的例子:打开chrome浏览器,转跳百度,点击后退(此时退回chrome了),此时浏览器地址栏输入gitee(基于chrome为栈顶的这个子栈,push进去gitee),发现此时浏览器栈还是两个记录,一个chrome,一个gitee,因为那个指针没到百度,所以百度被噶掉了。
这里我就不废话了,感兴趣的话了解一下就好了。
window.history对象
这个是h5提供的一个对象,它上面有很多属性和方法,都是与浏览器栈相关的,这里我们介绍一下会用到的属性与api。
window.history.pushState(state, title, url)
- pushState方法可以在浏览器栈中push一个新记录,但是当前页面不会刷新。
- 第一个参数state是一个自定义的对象,可以随意存放一些信息,通过window.history.state可以访问当前浏览器栈指针指向的记录的state对象(pushState的第一个参数),也就是当前页面对应的state对象。
- 第二个参数,会被浏览器忽略,所以直接传""
- 第三个参数,即为新的url路径字符串(基于当前路径,第三个参数作为相对路径进行拼接,随便试一下看下啥表现就知道了,不是啥重点)
注:必须要window.history对象去掉用pushState才可以正常运行(要保证pushState的this为window.history
popstate事件
- 点击浏览器的前进后退按钮会触发此事件
- 但是pushState和replaceState方法的调用不会触发此事件
第一步——url改变的方法我们已经有了,就是利用replaceState和pushState两个api,比如我们给几个按钮添加点击回调,回调逻辑中调用这两个方法即可完成浏览器地址栏url的修改;
然后就是第二件事,我们需要监听到replaceState和pushState两个方法的调用;
最后一步,和hash模式无异,路由匹配,然后修改一下页面显示的内容即可;
改变浏览器地址栏url
<div class="container">
<div class="router-nav">
<button class="nav-item home">首页</button>
<button class="nav-item center">个人中心</button>
<button class="nav-item about">关于</button>
</div>
<div
js部分,给三个按钮增加交互逻辑,使之可以修改url:
const homeButt = document.querySelector(".home");
const centerButt = document.querySelector(".center");
const aboutButt = document.querySelector(".about");
// 三个按钮都绑定了pushState行为,其实replaceState完全一样的
homeButt.addEventListener("click", () => {
history.pushState({path: "/"}, '', './');
})
centerButt.addEventListener
对浏览器地址栏改变的监听
这里我们浏览器地址栏url的改变本质上是pushState方法的调用,所以对浏览器地址栏的监听问题等价转换为对pushState方法调用的监听——重头戏:重写pushState方法
重写pushState方法
我们的目标是重写window.history.pushState(以及replaceState),让他们在调用时可以触发某个事件,然后我们就可以对其进行监听了
// _wrap方法对原生pushState和replaceState进行处理
let _wrap = (type) => {
// 先拿到pushState(replaceState)原生函数体
let originFun = history[type];
// _wrap返回的这个函数要做到两件事:1. 保证pushState(replaceState)的正常功能,说白了也就是保证调用原本的pushState方法时this、arguments参数正确,而且新返回的函数返回结果也与以前的pushState一样
// 2. 触发一个自定义事件,使新的pushState可以被监听
return function () {
let result = originFun.apply(this, arguments); // 这一行代码其实就做到上面要求的第一件事了,第一,我们用apply方法保证了this的正确性,即以后再调用history.pushState(xxx)时让history对象作为this去执行pushState原生函数体,并且也正确传递了参数,然后还保留了执行结果result,最后retuen返回这个result即可。
// 下面三行代码,创建一个pushState事件,然后dispatch出去,就做到了让pushState方法也可被监听,把arguments挂在事件对象e上,目的就一个,pushState的第一个参数不是可以记录一些信息么,看上面给按钮绑定的回调,我们记录了一个path,我们以后就拿着这个path去对比,以得到该展示的路由组件
let e = new Event(type);
e.arguments = arguments;
监听pushState
window.addEventListener("pushState", (e) => {
// 通过matchComponent匹配路由然后页面展示即可
})
matchComponent函数实现——>路由匹配 & 更新视图
const matchComponent = (path) => {
// 跟hash的实现完全一样啊,都只是简单模拟,路由匹配与试图更新主打一个点到为止
const {component} = routes.find(((route) => route.path === path));
document.querySelector(".page-content").innerHTML = component ? component : routes[0].component;
}
考虑到一个问题,我们通过监听用户触发的pushState和replaceState事件进行了路由的更新展示,但是如果用户点击前进后退按钮也会改变url,但是我们并没有进行相应的逻辑处理(路由匹配和更新视图),所以给popstate事件也添加上回调,还有路由首屏展示问题,所以给load事件也添加一个回调:
window.addEventListener("load", () => {
// 直接用"/"去匹配路由
matchComponent("/");
});
window.addEventListener("popstate", (e) => {
// 逻辑是比较粗糙的,如果没有state,就直接默认用"/"路径去匹配了
if (window.history.state) {
matchComponent(window.history.state.path);
} else {
matchComponent(
最终代码
// 重写pushState方法
let _wrap = (type) => {
let originFun = history[type];
return function () {
let result = originFun.apply(this, arguments);
let e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return result;
}
}
history.pushState = _wrap(
通过上面我们的手写,其实就能很清晰的认识到一个问题,不管hash模式还是history模式,说白了就是一句话,监听url的改变修改视图,问题再核心一点就是监听url的改变。
然后所谓的history模式页面刷新的404问题,也非常容易理解了,本质上就是因为pushState方法修改了浏览器栈的记录,但是这个修改就是非常纯粹的修改——不刷新页面,不发请求。这就导致我们经过pushState之后的url实际上可能已经不是一个真实存在的网络资源了(后端根本不存在这个页面),这时候我们手动刷新页面,就会导致发送网络请求去访问这个url,但是不存在,所以报错404。
5.2 路由的基本使用
React-Router有三个版本
- react-router-dom:Web端专用
- react-router-native:给ReactNative使用
- react-router-anywhere:通用
我们这里使用 react-router-dom,安装
# 这里需要注意,react16 只支持5版本以下的,所以这里需要指定一下版本
npm i react-router-dom@5
路由组件
Link组件
<Link to="/">首页<Link>
router分为如下两种
broswerRouter
hashRouter
这两个组件一般放在最外层,index.js中
<BrowerRouter>
<App />
</BrowerRouter>
route注册路由
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
5.3 路由组件与一般组件
路由组件和一般组件接收到的props不一样,总之吧,相较于一般组件,路由组件身上多了一些特殊的属性和方法
5.4 NavLink组件
navlink组件在使用时会有一个默认的active类,一般这个类就是用来设置路由标签被点击的高亮效果的,当然这个类名是可以自定义的,在标签上加上activeClassName即可,如下
<NavLink className="" activeClassName="wukai-active" to="/home">Home</NavLink>
封装一下NavLink
标签体内容是一个特殊的标签属性 children
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return <NavLink className="link" activeClassName="active" {...this.props}/>
}
}
使用和NavLink一样
5.5 Switch组件
switch组件可以提高路由匹配效率
为什么,如果不使用Switch则会遍历所有路由,效率较低,就比如说下面这种情况
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/about11" component={About11}/>
<Route path="/about2" component={About2}/>
<Route path="/about3" component={About3}/>
<Route path="/about4" component={About4}/>
</Switch>
这是加了,Switch组件,当匹配到对应的path后就停止匹配
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
样式丢失的问题
资源路径使用绝对路径
<img src="/assets/images/first.png" />
使用%PUBLIC_URL%
// 这个只适用于 react
使用HashRouter
<HashRouter>
<App />
</HashRouter>
5.6 路由的模糊匹配和严格匹配
严格匹配就是在注册路由组件 <Route/> 上,加上属性 exact
<Switch>
<Route exact path="/home" component={Home}/>
<Route exact path="/about" component={About}/>
</Switch>
严格匹配不建议随便开启,因为可能会导致二级路由失效
5.7 Redirect组件
通俗的说,Redirect 组件就是谁都匹配不上的时候就派上了用场
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Redirect to="/home" />
</Switch>
5.8 嵌套路由
也称为二级路由,采用组件套组件的方式,称之为嵌套路由,如下代码作为示例
// App 组件下有两个组件 Home About
export default class App extends Component {
render() {
return (
<div className="App">
<div className="w-1/2 mx-auto my-0">
<h1 className="bg-[#1da1f2] text-white p-4 w-full">React App</h1>
<div className="p-12 bg-[#fafafa]">
<div className="">
<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/about">About</MyNavLink>
</div>
<div className="mt-3">
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Redirect to="/home" />
</Switch>
</div>
</div>
</div>
</div>
)
}
}
// Home 组件下有两个路由组件 Message News
export default class Home extends Component {
render(){
return (
<div>
<div className="flex justify-start items-center">
<MyNavLink to="/home/news">news</MyNavLink>
<MyNavLink to="/home/message">message</MyNavLink>
</div>
{/* 下面来注册路由 */}
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
<Redirect to="/home/news" />
</Switch>
</div>
)
}
}
基本的写法就是上面的形式
5.9 路由传参
5.9.1 params参数
传递params参数
// 传递params参数
<li key={item.id}><Link to={`/home/message/detail`} >{item.title}</Link></li>
// 注册路由,并需要使用路由占位符
<Route path="/home/message/detail/:id" component={Detail}/>
// 接收params参数
const { id } = this.props.match.params
获
这里有一个问题没有解决就是在React中如何根据路由参数的变化接受新的Props值
5.9.2 search参数
search传递时不需要像params一样使用占位符,我们来看一下如何接受search参数
// 传递search参数
<li key={item.id}>
<Link to={`/home/message/detail/?id=${item.id}`} >{item.title}</Link>
</li>
// 传递search参数不需要使用路由占位符,不需要其他额外操作
<Route path="/home/message/detail" component={Detail}/>
// 接受query参数
// 注意:这里可能需要手动安装一下 querystring 这个库
// yarn add querystring
import qs from 'querystring'
const query = this.props.location.search
const { id } = qs.parse(query.slice(1))
5.9.3 state参数
hashRouter在浏览器页面刷新时会丢失state参数
// 传递state参数
<li key={item.id}>
<Link to={{pathname:'/home/message/detail',state:{id:item.id}}} >{item.title}</Link>
</li>
// 传递state参数不需要使用路由占位符,不需要其他额外操作
<Route path="/home/message/detail" component={Detail}/>
// 接受state参数
const { id } = this.props.location.state
5.10 push 和 replace
Push:堆栈
replace:替换栈顶
5.11 编程式路由导航
5.11.1 传递params参数
pushShow = (id) => {
this.props.history.push(`/home/message/detail/${id}`)
}
replaceShow = (id) => {
this.props.history.replace(`/home/message/detail/${id}`)
}
// 传递params参数
<li key={item.id}>
<Link to={`/home/message/detail`} >{item.title}</Link>
<button type="button" onClick={() => this.pushShow(item.id)}>push查看</button>
<button type="button" onClick={() => this.replaceShow(item.id)}>replace查看</button>
</li>
{/* 注册路由 */}
<Route path="/home/message/detail/:id" component={Detail}/>
// 接收params参数
const { id } = this.props.match.params
5.11.2 传递search参数
pushShow = (id) => {
this.props.history.push(`/home/message/detail/?id=${id}`)
}
replaceShow = (id) => {
this.props.history.replace(`/home/message/detail/?id=${id}`)
}
// 传递search参数
<li key={item.id}>
<Link to={`/home/message/detail/?id=${item.id}`} >{item.title}</Link>
<button type="button" onClick={() => this.pushShow(item.id)}>push查看</button>
<button type="button" onClick={() => this.replaceShow(item.id)}>replace查看</button>
</li>
{/* 注册路由 */}
<Route path="/home/message/detail" component={Detail}/>
// 接受query参数
// 注意:这里可能需要手动安装一下 querystring 这个库
// yarn add querystring
import qs from 'querystring'
const query = this.props.location.search
const { id } = qs.parse(query.slice(1))
5.11.3 传递state参数
pushShow = (id) => {
this.props.history.push(`/home/message/detail`,{id})
}
replaceShow = (id) => {
this.props.history.replace(`/home/message/detail`,{id})
}
// 传递state参数
<li key={item.id}>
<Link to={{pathname:'/home/message/detail',state:{id:item.id}}} >{item.title}</Link>
<button type="button" onClick={() => this.pushShow(item.id)}>push查看</button>
<button type="button" onClick={() => this.replaceShow(item.id)}>replace查看</button>
</li>
{/* 注册路由 */}
<Route path="/home/message/detail" component={Detail}/>
// 接受state参数
const { id } = this.props.location.state
其他 history 对象身上的属性:go goBack goForword
5.12 WithRouter的使用
往一般组件身上添加路由组件的属性
import {withRouter} from 'react-router-dom
class Header extends Component {
// ...
}
export default withRouter(<Header />)
5.13 BrowerRouter和HashRouter

六、Redux
6.1 redux简介
Redux 让你开发出 行为稳定可预测、可运行在不同环境 (客户端、服务端和原生程序)、且 易于测试 的应用。
集中管理应用的状态和逻辑可以让你开发出强大的功能,如 撤销/重做、 状态持久化 等等。
Redux DevTools 让你轻松追踪到 应用的状态在何时、何处以及如何改变。Redux 的架构让你记下每一次改变,借助于 "时间旅行调试",你甚至可以把完整的错误报告发送给服务器。
Redux 可与任何 UI 层框架搭配使用,并且有 庞大的插件生态 来实现你的需求.
前提说明:针对于视频教程整理,redux和react-redux也在不断更新,加上也有别的库管理Store,所以这里
6.2 redux工作流程

6.3 redux求和案例
通过求和案例,了解rudux的应用。这是一个纯 redux 版
yarn add redux
创建redux目录,并创建如下四个文件
- constants.js 用于存放常量
- store.js 仓库,用于存放所有需要共享的数据
- count_reducer.js count的reducer,用于初始化数据和操作数据
- count_action.js 存放和count组件相关的操作
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import countReducer from "./count_reducer";
export default createStore(countReducer, applyMiddleware(thunk));
import { INCREMENT, DECREMENT } from './constant';
const initState = 0;
export default function countReducer(preState=initState, action) {
const { type, data } = action;
switch(type) {
case INCREMENT:
return preState + data;
case DECREMENT:
return preState - data;
default:
return preState;
}
}
import { INCREMENT,DECREMENT } from "./constant";
import store from "./store";
export const createIncrementAction = data => ({type: INCREMENT, data})
export const createDecrementAction = data => ({type: DECREMENT, data})
export const createIncrementAsyncAction = (data,time) => {
return () => {
创建 components文件夹,并创建 Count 组件
- count 组件通过 redux 完成数据展示和相关操作
import React, { Component } from "react";
import store from "../../redux/store";
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from "../../redux/count_action";
export default class Count extends Component {
state = {id: 0};
increment = (value) => store.dispatch(createIncrementAction(value * 1))
decrement = (value) => store.dispatch(createDecrementAction(value * 1))
incrementIfOdd = (value) => {
const count = store.getState();
if (count % 2 === 1) store.dispatch(createIncrementAction(value * 1))
};
incrementAsync = (value) => store.dispatch(createIncrementAsyncAction(value * 1,500))
render() {
return (
<div>
<h1>Count的值是: {store.getState()}</h1>
<select ref={(c) => (this.selectRef = c)}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
</select>
<button onClick={() => this.increment(this.selectRef.value * 1)}>+</button>
<button onClick={() => this.decrement(this.selectRef.value * 1)}>-</button>
<button onClick={() => this.incrementIfOdd(this.selectRef.value * 1)}>
increment if odd
</button>
<button onClick={() => this.incrementAsync(this.selectRef.value * 1)}>
increment async
</button>
</div>
);
}
}
在 index.js中,引入store 并通过 store.subscribe()方法完成数据实时更新
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import store from './redux/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
store.subscribe(() => {
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
})
6.4 react-redux
使用react-redux的项目结构
- containers
- Count
- index.jsx
- Person
- index.jsx
- redux
- actions
- count.js
- person.js
- reducers
- count.js
- person.js
- store.js
- constants.js
- App.jsx
- index.js
...
需要用到的库
redux: 集中式状态管理库
react-redux: react专用的集中式状态管理库
redux-thunk: 使用异步action时需要使用此插件
redux-devtools-extension: redux开发者工具
异步action的概念:在 action 中执行了异步方法,如 setTimeout 、网络请求等
如下是一个使用示例