# 张天禹React基础学习记录
**Repository Path**: zje_xtrx/react-learning-record
## Basic Information
- **Project Name**: 张天禹React基础学习记录
- **Description**: React学习笔记备份
视频链接:https://www.bilibili.com/video/BV1wy4y1D7JT
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 7
- **Created**: 2023-12-24
- **Last Updated**: 2023-12-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: 学习资料, React, 前端学习资料
## README
# React笔记
## 相关资源
[FrontEndNotes笔记](https://brucecai55520.gitee.io/my-notes)
[原课程链接](https://www.bilibili.com/video/BV1wy4y1D7JT)
[大佬仓库](https://gitee.com/hongjilin/hongs-study-notes)
[React 中文官网](https://react.docschina.org/)
## 前言 | Reat简介
React:用于构建用户界面的 JavaScript 库。由 Facebook 开发且开源。是一个将视图渲染为html视图的开源库
### 为何学习 React
原生 JavaScript 的痛点:
- 操作 DOM 繁琐、效率低
- 使用 JavaScript 直接操作 DOM,浏览器进行大量重绘重排
- 原生 JavaScript 没有组件化编码方案,代码复用率低
### React 的特点:
- 采用组件化模式、声明式编码,提高开发效率和组件复用率
- 在 React Native 中可用 React 语法进行移动端开发
- 使用虚拟 DOM 和 Diffing 算法,减少与真实 DOM 的交互(数据-虚拟DOM-真实DOM)
### 需要掌握的js基础
判断this指向
class类
ES6语法规范
npm包管理
原型以及原型链
数组常用方法
模块化
## 第一章:React入门
### Hello React
相关js库
- react.development.js :React 核心库
- react-dom.development.js :提供 DOM 操作的 React 扩展库
- babel.min.js :解析 JSX 语法(js语法糖),转换为 JS 代码
```html
```
### 创建VDOM的两种方式
第一种 jsx方式(推荐)
```html
```
第二种方式
```html
```
### VDOM | DOM
关于虚拟 DOM:
- 本质是 Object 类型的对象(一般对象)
- 虚拟 DOM 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多的属性。
- 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。
```html
```
### React JSX
全称:JavaScript XML
React 定义的类似于 XML 的 JS 扩展语法;本质是 React.createElement() 方法的语法糖
作用:简化创建虚拟 DOM
补充:js中,JSON的序列化和反序列化使用parse()/stringify()
JSX 语法规则
- 定义虚拟 DOM 时,不要写引号
- 标签中混入 JS 表达式需要使用 {}
- 指定类名不用 class,使用 className
- 内联样式,使用 style={{ key: value }} 的形式
- 只能有一个根标签
- 标签必须闭合,单标签结尾必须添加 /:``
- 标签首字母小写,则把标签转换为 HTML 对应的标签,若没有,则报错
- 标签首字母大写,则渲染对应组件,若没有定义组件,则报错
```html
jsx语法规则
...
```
补充:表达式与语句(代码)的区别
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方(如数值处理等)
```
a
a+b
demo(1)
arr.map()
function test () {}
```
- 语句(代码),下面这些都是语句(如逻辑判断语句)
```
if(){}
for(){}
switch(){case:xxxx}
```
## 第二章:React面向组件
### 创建组件的两种方式
#### 函数式组件
```html
```
要点:
- 组件名称首字母必须大写,否则会解析成普通标签导致报错(JSX 语法规则)
- 函数需返回一个虚拟 DOM
- 渲染组件时需要使用标签形式,同时标签必须闭合
渲染组件的过程:
- React 解析标签,寻找对应组件
- 发现组件是函数式组件,则调用函数,将返回的虚拟 DOM 转换为真实 DOM ,并渲染到页面中
#### 类组件
类的基本知识
```html
```
组件渲染过程:
1. React 解析组件标签,寻找组件
2. 发现是类式组件,则 new 该类的实例对象,通过实例调用原型上的 render 方法
3. 将 render 返回的虚拟 DOM 转为真实 DOM ,渲染到页面上
### 组件实例的核心属性
#### 核心属性1:state | 状态
state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。
要点:
- 初始化 state
- React 中事件绑定
- this 指向问题
- setState 修改 state 状态
- constructor 、render 、自定义方法的调用次数
1. 标准写法:
```html
```
2. 简写:
```html
```
#### 核心属性2:props | 标签属性
每个组件对象都有 props 属性,组件标签的属性都保存在 props 中。(注意:props 是只读的,不能修改。)
要点:
- 展开运算符解构赋值
- props数据采用标签属性的方式传值
- 批量传递标签属性
1. 标准写法:
```html
```
2. 限制标签属性
在 React 15.5 以前,React 身上有一个 PropTypes 属性可直接使用,即 name: React.PropTypes.string.isRequired ,没有把 PropTypes 单独封装为一个模块。
从 React 15.5 开始,把 PropTypes 单独封装为一个模块,需要额外导入使用。
```html
```
3. 函数式组件使用props:
```html
```
4. 简写形式:
```html
```
#### 核心属性3:refs | 标识符
通过定义 ref 属性可以给标签添加标识。
1. 字符串形式的ref(这种形式已过时,效率不高,官方不建议使用。)
```html
```
2. 回调函数形式的ref(便捷 使用最多)
要点:
- currentNode => this.input1 = currentNode 就是给组件实例添加 input1 属性,值为节点
- 由于是箭头函数,因此 this 是 render 函数里的 this ,即组件实例
- 回调ref中调用次数问题:原文(如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。见[官方文档](https://react.docschina.org/docs/refs-and-the-dom.html))
```html
```
3. createRef API(官方推荐使用)
该方式通过调用 React.createRef 返回一个容器(对象)用于存储节点,且一个容器只能存储一个节点。
```html
```
#### 事件处理
- React 使用自定义事件,而非原生 DOM 事件,即 onClick、onBlur :为了更好的兼容性
- React 的事件通过事件委托方式进行处理:为了高效
- 通过 event.target 可获取触发事件的 DOM 元素:勿过度使用 ref
当触发事件的元素和需要操作的元素为同一个时,可以不使用 ref :
```html
class Demo extends React.Component {
showData2 = (event) => {
alert(event.target.value)
}
render() {
return (
)
}
}
```
#### 受控组件&非受控组件
非受控组件:现用现取。即需要使用时,再获取节点得到数据
受控组件:类似于 Vue 双向绑定的从视图层绑定到数据层(推荐使用,因为非受控组件需要使用大量的 ref 。)
1. 受控组件
```html
```
2. 非受控组件
```html
```
#### 补充:高阶函数&函数柯里化
高阶函数:参数为函数或者返回一个函数的函数,常见的如 Promise、setTimeout、Array.map()等
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
使用高阶函数简化受控组件:
```html
```
### 组件的生命周期
理解
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
#### 生命周期(旧)

1.3.1 初始化阶段
- 由ReactDOM.render()触发—初次渲染
- constructor() —— 类组件中的构造函数
- componentWillMount() —— 组件将要挂载 【即将废弃】
- render() —— 挂载组件
- componentDidMount() —— 组件挂载完成 比较常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
1.3.2 更新阶段
【第一种情况】父组件重新render触发
- componentWillReceiveProps() —— 接收属性参数(非首次)【即将废弃】
- ...
【第二种情况】由组件内部this.setSate()
- shouldComponentUpdate() —— 组件是否应该被更新(默认返回true)
- ...
【第三种情况】强制更新 forceUpdate()
- componentWillUpdate() ——组件将要更新 【即将废弃】
- render() —— 组件更新
- componentDidUpdate() —— 组件完成更新
1.3.3 卸载组件
- 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() —— 组件即将卸载
#### 生命周期(新)

1. 初始化阶段
- 由ReactDOM.render()触发 —— 初次渲染
- constructor() —— 类组件中的构造函数
- static getDerivedStateFromProps(props, state) 从props得到一个派生的状态【新增】
- render() —— 挂载组件
- componentDidMount() —— 组件挂载完成 比较常用
2. 更新阶段
- 由组件内部this.setSate()或父组件重新render触发或强制更新forceUpdate()
- getDerivedStateFromProps() —— 从props得到一个派生的状态 【新增】
- shouldComponentUpdate() —— 组件是否应该被更新(默认返回true)
- render() —— 挂载组件
- getSnapshotBeforeUpdate() —— 在更新之前获取快照【新增】
- componentDidUpdate(prevProps, prevState, snapshotValue) —— 组件完成更新
3. 卸载组件
- 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() —— 组件即将卸载
```html
```
#### diffing算法与key
1. 虚拟DOM中key的作用
- 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
- 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
```
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
```
!{reffing算法基本原理图}(https://img-blog.csdnimg.cn/2021060821262976.png)
2. 用index作为key可能会引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题
注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
## 第三章 React脚手架
### 脚手架安装与使用
全局安装配置 :`npm i -g create-react-app`
创建项目:`create-react-app 项目名`
```
C:\Users\Mrnianj>npm i -g create-react-app
...
C:\Users\Mrnianj>create-reacrt-app 项目名
...
C:\Users\Mrnianj>cd 项目(文件夹)名
...
C:\Users\Mrnianj>npm start
```
脚手架目录:
```
public ---- 静态资源文件夹
favicon.icon ------ 网站页签图标
index.html -------- 主页面
logo192.png ------- logo图
logo512.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
App.css -------- App组件的样式
App.js --------- App组件
App.test.js ---- 用于给App做测试
index.css ------ 样式
index.js ------- 入口文件
logo.svg ------- logo图
reportWebVitals.js
--- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js
---- 组件单元测试的文件(需要jest-dom库的支持)
```
### 最简单的项目实例
public/index.html
```html
```
src/index.js
```js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
,
document.getElementById('root')
);
```
src/app.js
```jsx
// 创建外壳组件
import React,{Component} from "react";
import Hello from "./components/Hello/Hello"
import Welcome from "./components/Welcome"
// 暴露并创建组件
export default class App extends Component {
render() {
return (
)
}
}
```
src/components/Hello
```jsx
import React ,{ Component } from "react";
export default class Hello extends Component{
render() {
return (
Hello React!!!
)
}
}
```
src/components/Welcome
```jsx
import React,{Component} from "react"
export default class Welcome extends Component {
render() {
return Welcome
}
}
```
TodoList案例
- 拆分组件、实现静态组件,注意:className 、style 的写法
- 动态初始化列表,如何确定将数据放在哪个组件的 state 中?
某个组件使用:放在其自身的 state 中
某些组件使用:放在他们共同的父组件 state 中,即状态提升
- 关于父子之间通信:
父传子:直接通过 props 传递
子传父:父组件通过 props 给子组件传递一个函数,子组件调用该函数
```jsx
// 父组件
class Father extends Component {
state: {
todos: [{ id: '001', name: '吃饭', done: true }],
flag: true,
}
addTodo = (todo) => {
const { todos } = this.state
const newTodos = [todo, ...todos]
this.setState({ todos: newTodos })
}
render() {
return
}
}
// 子组件
class Son extends Component {
// 由于 addTodo 是箭头函数,this 指向父组件实例对象,因此子组件调用它相当于父组件实例在调用
handleClick = () => {
this.props.addTodo({ id: '002', name: '敲代码', done: false })
}
render() {
return
}
}
```
- 注意(标签属性) defaultChecked 和 checked 的区别,类似的还有:defaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
## 第四章 React网络请求(ajax)
### axios | 网络请求
安装 axios `npm i axios`
```jsx
export default class App extends Component {
getStuData = ()=> {
axios.get('http://localhost:3000/api1/students').then(
response => {console.log(response.data);},
err => {console.log(err);}
)
}
getCarData = ()=> {
axios.get('http://localhost:3000/api2/cars').then(
response => {console.log(response.data);},
err => {console.log(err);}
)
}
render() {
return (
)
}
}
```
### React 脚手架配置代理(跨域问题)
[官方文档](https://www.html.cn/create-react-app/docs/proxying-api-requests-in-development/)
方法1:
优点:配置简单,前端请求资源可不加前缀
缺点:不能配置多个代理
工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器
在 package.json 文件中进行配置:`"proxy": "http://localhost:5000"`
方法2:
在 src 目录下创建代理配置文件 setupProxy.js ,进行配置:
```cjs
// const proxy = require('http-proxy-middleware')
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
//api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
createProxyMiddleware('/api1', {
//配置转发目标地址(能返回数据的服务器地址)
target: 'http://localhost:5000',
//控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但一般将changeOrigin改为true
*/
changeOrigin: true,
//去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
pathRewrite: { '^/api1': '' },
}),
createProxyMiddleware('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: { '^/api2': '' },
})
)
}
```
### 消息订阅与发布机制
React 中兄弟组件或任意组件之间的通信方式。
[使用的工具库:pubsub-js](https://www.npmjs.com/package/pubsub-js)
1. 安装 `npm install pubsub-js --save`
2. 基础用法示例
```jsx
// A组件
import PubSub from 'pubsub-js'
// 组件被挂载后订阅消息
componentDidMount() {
this.token = PubSub.subscribe('topic', (msg,data)=>{//msg 订阅消息名,data 传递的数据
console.log('List组件收到订阅消息',data);
this.setState(data)
})
}
// 组件将被卸载时取消订阅
componentWillUnmount() {
PubSub.unsubscribe(this.token)
}
// B组件
// 发布消息
import PubSub from 'pubsub-js'
export default class Search extends Component {
saveUsers = ()=>{
// 发布消息
// 发送网络请求后 通知A组件更新状态
PubSub.publish('topic', {isLoading:true,isFirst:false})
axios.get(`https://api.github.com/search/users?q=username`).then(
(res)=>{
PubSub.publish('topic', {isLoading:false,users:res.data.items})
},
(err)=>{
PubSub.publish('topic', {isLoading:false,err})
}
)
}
}
```
#### 扩展 fetch | 发送ajax请求(了解即可)
[阮一峰 Fetch API 教程](https://www.ruanyifeng.com/blog/2020/12/fetch-tutorial.html)
常见可以发送ajax请求的方式
- xhr (常见的有:jQuery\axios)
- fetch (原生,但是不常用)
下面是fetch使用
```js
// 使用fetch发送请求
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("获取数据失败", e))
// 使用 await 语法优化
async function getJSON() {
let url = 'https://api.github.com/users/ruanyf';
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log('请求出错', error);
}
}
```
#### Github 搜索框案例知识点总结
1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
2. ES6 知识点:解构赋值 + 重命名
```js
let obj = { a: { b: 1 } }
//传统解构赋值
const { a } = obj
//连续解构赋值
const {
a: { b },
} = obj
//连续解构赋值 + 重命名
const {
a: { b: value },
} = obj
```
3. 消息订阅与发布机制
- 先订阅,再发布(隔空对话)
- 适用于任意组件间通信
- 要在 componentWillUnmount 钩子中取消订阅
4. fetch 发送请求(*关注分离*的设计思想)
```js
try {
// 先看服务器是否联系得上
const response = await fetch(`/api1/search/users2?q=${keyWord}`)
// 再获取数据
const data = await response.json()
console.log(data)
} catch (error) {
console.log('请求出错', error)
}
```
## 第五章 React路由
### SPA页面&路由理解
1. SPA页面的特点
- 单页web应用
- 整个应用只有一个完整页面
- 点击页面链接不会刷新页面,只会做页面的局部刷新
- 数据都需要经过ajax请求获取,并在前端一部展现
2. 何为路由?
- 一个路由是一个映射关系
- key 为路径地址,value 可能是 function 或 component
3. 路由分类
- 后端路由:
- value 是 function ,用于处理客户端的请求
- 注册路由:`router.get(path, function(req, res))`
- 工作过程:Node 接收到请求,根据路径匹配路由,调用对应函数处理请求,返回响应数据
- 前端路由:
- value 是组件
- 注册路由:``
- 工作过程:浏览器路径变为 /test ,展示 Test 组件
### 补充:路由的基本原理
`window.history`
BOM 浏览器对象中包含 history 对象用于管理浏览器历史记录,History 对象是 window 对象的一部分,可通过 window.history 属性对其进行访问。
```
方法 说明
back() 加载 history 列表中的前一个 URL
forward() 加载 history 列表中的下一个 URL
go() 加载 history 列表中的某个具体页面
```
### react-router-dom | 路由插件
react的一个插件库,用来实现一个SPA应用。
[react-router web应用官方文档](https://react-router.docschina.org/web/guides/philosophy)
#### 路由的基本使用
1. 安装 react-router-dom :
```
// 安装 5.X 版本路由
npm install react-router-dom@5.2.0 -S
是
// 最新已经 6.X 版本,用法和 5.X 有所不同
npm install react-router-dom -S
```
[6.x 版本的基本使用参考](https://zhuanlan.zhihu.com/p/191419879)
2. 编写基本路由
index.js
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter} from "react-router-dom";
ReactDOM.render(
,
document.getElementById('root')
);
```
App.jsx
```jsx
import React, { Component } from 'react'
import About from './components/About';
import Home from './components/Home';
import { Link,Route} from "react-router-dom";
export default class App extends Component {
render() {
return (
)
}
}
```
3. 总结
- 导航区正宗的跳转链接改为Link标签
- 展示区写的Route标签进行路由匹配
- index.js入口文件中的``外侧需要包裹一个``或者``标签
#### 路由组件和一般组件
1. 存放位置不同:
一般组件:components
路由组件:pages
2. 写法不同:
一般组件:
路由组件:
3. 接收到的 props 不同:
一般组件:标签属性传递
路由组件:会接收到三个固定的属性(*history,location=history.location | 语法糖,match*)
```
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/home/message/detail/2/hello"
search: ""
state: undefined
match:
params: {}
path: "/home/message/detail/:id/:title"
url: "/home/message/detail/2/hello"
```
#### NavLink的使用
NavLink是Link的迭代,NavLink 可以通过 activeClassName 属性指定点击之后追加的样式名,默认追加类名 active ,
```
About
Home
```
**封装NavLink:**
MyNavLink.jsx
```jsx
/*
针对 NavLink 的二次封装
*/
import React, { Component } from 'react'
import { NavLink } from "react-router-dom";
export default class MyNavLink extends Component {
render() {
let {} = this.props
return (
// {this.props.children}
)
}
}
```
调用方式
`MyNavLink`
#### Switch 单一路由的匹配
1. 一般情况下,一个路由对应一个组件(需要借助Switch),注意:在v6.xx版本中,Switch 已经被 Routes 标签代替!
2. Switch 可以提高路由匹配效率,如果匹配成功,则不再继续匹配后面的路由,即单一匹配。
```jsx
import { Route,NavLink,Switch } from "react-router-dom";
{/* ... */}
{/* 编写路由链接 */}
About
Home
{/* 注册路由 包裹 Switch 标签 */}
```
#### 解决多级路由样式丢失
当路由路径中出现多级路由时,如``时,页面加载后可能会出现样式文件丢失
三种解决方案:
1. public/index.html 中 引入样式时不写 ./ 写 / (常用),如:``
2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用),如:``
3. 使用 HashRouter(哈希路由/锚点路由),如:
```jsx
import { HashRouter } from "react-router-dom";
ReactDOM.render(
,
document.getElementById('root')
);
```
#### 路由的严格匹配与模糊匹配
模糊匹配(左侧匹配)示例
```jsx
Home
{/* 可以匹配 */}
```
开启严格匹配示例
```jsx
Home
{/* 可以匹配 */}
{/* 不可以匹配 */}
```
- react-router-dom 默认使用模糊匹配(输入的路径必须包含要匹配的路径,且是顺序匹配)
- 使用`exact={true}`(语法糖:可以省略属性值) 开启严格匹配:``
- 严格匹配需要再开,开启可能会导致无法继续匹配二级路由
#### Redirect | 重定向、重定向路由
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由
```jsx
import { Route,Switch,Redirect } from "react-router-dom";
```
#### 多级路由
注册子路由需写上父路由的 path
路由的匹配是按照注册路由的顺序进行的
home.jsx | 不要开启严格匹配
```jsx
About
Home
```
/pages/home/news.jsx & ../message.jsx
```jsx
```
#### 路由传参
三种方式:params, search, state 参数
state 方式当前页面刷新可保留参数,但在新页面打开不能保留。
params 和 search 参数都会变成字符串,两种方式由于参数保存在 URL 地址上,因此都能保留参数。
1. 传递 params 参数方式
```jsx
{/* 向路由组件传递 params 参数 */}
{msgObj.title}
{/* 声明接收 params 参数 */}
// 接收 params 参数
const {id,title} = this.props.match.params
```
2. 传递 search 参数方式(需要自己处理传递后的参数,较麻烦)
```jsx
{/* 向路由组件传递 search 参数 */}
{msgObj.title}
{/* search 参数无需声明接收 */}
// 接收 params 参数(需要自己处理 字符串截取转对象)
const {search } = this.props.location //search:"?id=01&title=消息1"
// 处理 search 参数
let searchObj = {}
var splitArr = search.slice(1,search.length).split("&") //['id=03','title=消息3']
splitArr.map((item) => {
let Objkey,Objvalue = ''
let Obj = {}
item.split("=").map((value,index) => {
index==0 ? Objkey = value : Objvalue = value
})
Obj[Objkey] = Objvalue
searchObj = {...searchObj,...Obj}
})
```
3. 传递 state 参数方式
```jsx
{/* 向路由组件传递 state 参数 */}
{msgObj.title}
{/* state 参数也无需声明接收 */}
// 接收 state 参数
const { id,title } = this.props.location.state || {}
```
补充:开启replace模式 ,rreact-router-dom默认启用push模式
push与replace的区别
push 压栈操作,会产生历史记录
replace 替换操作,不会产生历史记录,无法返回
```jsx
{msgObj.title}
// 简写( == 严格匹配模式)
{msgObj.title}
```
#### 编程式路由
通过 history 对象提供的api实现路由跳转
history:
props.history.go: ƒ go(n) 参数传递整数(正数前进 负数后退)
props.history.goBack: ƒ goBack() 前进
props.history.goForward: ƒ goForward() 后退
props.history.push: ƒ push(path, state) 添加历史记录
props.history.replace: ƒ replace(path, state) 替换历史记录
```jsx
// 三种编程式导航传参的方式
this.props.history.push(`/home/message/detail/${id}/${title}`)
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
this.props.history.push(`/home/message/detail`, { id: id, title: title })
```
#### withRouter 的使用
withRouter 的作用:加工一般组件,让其拥有路由组件的 API ,如 this.props.history.push 等。
```jsx
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
render() {
{/*...*/}
}
}
export default withRouter(Header)
```
#### BrowserRouter 和 HashRouter
1. 底层原理不一样:
BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本。
HashRouter 使用的是 URL 的哈希值。
2. 路径表现形式不一样
BrowserRouter 的路径中没有 # ,如:localhost:3000/demo/test
HashRouter 的路径包含#,如:localhost:3000/#/demo/test
3. 刷新后对路由 state 参数的影响
BrowserRouter 没有影响,因为 state 保存在 history 对象中。
HashRouter 刷新后会导致路由 state 参数的丢失!
4. 备注:HashRouter 可以用于解决一些路径错误相关的问题(多级路由刷新样式丢失问题)。
## 第六章 UI组件库 antd-UI
[Ant Design官方网站](https://ant.design/index-cn)
1. 安装 ` npm install antd --save`
2. 基本使用
```jsx
import React, { Component } from 'react'
// 引入antd
import { Button } from 'antd';
import { PoweroffOutlined }from '@ant-design/icons';
import "antd/dist/antd.css";
export default class App extends Component {
render() {
return (
}>
Click me!
)
}
}
```
3. 3.x按需引入
Antd,默认情况下:组件的js是支持按需引入,但css并不支持按需引入,即默认情况下Antd的CSS样式被整体引入,
- 安装依赖
`npm install react-app-rewired customize-cra babel-plugin-import`
- 修改 package.json
```json
/* package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
```
- 项目根目录下创建 config-overrides.js
```jsx
//配置具体的修改规则
const { override, fixBabelImports, addLessLoader } = require('customize-cra')
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
})
)
```
4. 补充 :3.x版本配置主题已不适用与antd4.x版本,配置按需引入&定制主题参考:
[官网文档](https://ant.design/docs/react/use-with-create-react-app-cn)
[Antd4.x 按需引用&自定义主题-简书](https://juejin.cn/post/6995446514881396773)
## 第七章 Redux
[阮一峰 Redux入门](https://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html)
[Redux 中文文档](https://www.redux.org.cn/)
### Redux 概述
- Redux 是用于做 状态管理 的 JS 库,除此之外还有[DvaJS](https://dvajs.com/)(推荐)等
- 可用于 React、Angular、Vue 等项目中,常用于 React
- 集中式管理 React 应用多个组件共享的状态
何时用 Redux ?
- 某个组件的状态,需要让其他组件拿到(状态共享)
- 一个组件需要改变另一个组件的状态(通信)
- 使用原则:不到万不得已不要轻易动用
Redux 工作流程

组件想操作 Redux 中的状态:需要把动作类型和数据(对象形式)告诉 Action Creators
- Action Creators 创建 action :同步 action 是一个普通对象,异步 action 是一个函数
- Store 调用 dispatch() 分发 action 给 Reducers 执行
- Reducers 接收 previousState 、action 两个参数,对状态进行加工后返回新状态
- Store 调用 getState() 把状态传给组件
#### redux基本使用:
store.js
```jsx
// 引入 createStore 创建 store 对象
import { createStore } from "redux";
// 引入为 Count 服务的 countReducer
import countReducer from "./reduce";
// 暴露 store
export default createStore(countReducer)
```
reduce.js
```jsx
/*
为Count组件创建的服务reducer
作用:
初始化store
接收两个参数:之前的状态(preState),动作对象(action)
*/
const initState = 0
export default function countReducer(preState = initState,action) {//默认参数
const { type,data } = action
switch (type) {
case 'increment':
return data + preState
default:
return preState//无参数,返回默认值 0
}
}
```
index.jsx
```jsx
import React, { Component } from 'react'
import store from "../../redux/store";
export default class Count extends Component {
componentDidMount(){
// 组件挂载完毕,监测状态更新,重新渲染
store.subscribe(()=>{
this.setState({})
})
}
// 加
increment = ()=>{
// 获取用户输入数据
const userInput = (this.selectNumber.value)*1
// 通知redux 更新状态
store.dispatch({type:'increment',data:userInput})
}
render() {
return (
当前求和结果为:Redux:{store.getState()}
)
}
}
```
补充:监测redux中数据改变重新绘排,可以在index.js入口文件中监测整个App
```jsx
// ...
store.subscribe(()=>{
ReactDom.render(,document.querySelector('#root'))
})
```
#### redux完整流程
1. 文件结构
redux
+ constant.js 定义 action 中 type 的常量值,一次定义,多处引用
+ count_action.js 创建 action 对象
+ count_reducer.js 1.初始化store 2.接收两个参数:之前的状态(preState),动作对象(action)
+ store.js
```js
// redux/count_action.js
import { INCREMENT, DECREMENT } from './constant'
export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })
```
```js
// redux/constant.js
// 保存常量值
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
```
### Redux 异步Action
要点:
- 延迟的动作不想交给组件,而是 action
- 当操作状态所需数据要靠异步任务返回时,可用异步 action
- 创建 action 的函数返回一个函数,该函数中写异步任务
- 异步任务完成后,分发一个同步 action 操作状态
- 异步 action 不是必要的,完全可以在组件中等待异步任务结果返回在分发同步 action
1. 安装 异步支持中间件
`npm install redux-thunk -S`
2. 定义异步 action
```js
// count_action.js
import { INCREMENT, DECREMENT } from './constant.js'
// 同步 action 返回一个对象
export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })
// 异步 action 返回一个函数
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
```
3. 配置 thunk 插件
```js
// store.js
// 引入 applyMiddleware 异步支持函数
import { createStore, applyMiddleware } from 'redux'
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
export default createStore(countReducer, applyMiddleware(thunk))
```
4. 正常调用异步 action
```js
// Count.jsx
incrementAsync = () => {
const { value } = this.selectNumber
store.dispatch(createIncrementAsyncAction(value * 1))
}
```
总结:
store 在分发 action 时,发现返回一个函数(为异步 action)。因此 store 执行这个函数,同时给这个函数默认传递 dispatch 参数,等待异步任务完成取到数据后,直接调用 dispatch 方法分发同步 action 。
### react-redux
Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。 [react-redux 中文文档](https://www.redux.org.cn/docs/react-redux/)

#### react-redux的基本使用
安装: `npm install react-redux`
store.js
```js
// 引入 createStore 创建 store 对象
import { createStore } from "redux";
// 引入为 Count 服务的 countReducer
import countReducer from "./count_reduce";
// 暴露 store
export default createStore(countReducer)
```
count_action.js
```js
import { INCREMENT,DECREMENT } from "./constant";
export const createIncrementAction = data => ( {type:INCREMENT,data:data} )
export const createDecrementAction = data => ( {type:DECREMENT,data:data} )
```
count_reducer.js
```js
/*
为Count组件创建的服务reducer
作用:
初始化store
接收两个参数:之前的状态(preState),动作对象(action)
*/
import { INCREMENT,DECREMENT } from "./constant";
const initState = 0 //初始化默认参数
export default function countReducer(preState = initState,action) {//默认参数
const { type,data } = action
switch (type) {
case INCREMENT:
return data + preState
case DECREMENT:
return preState - data
default:
return preState//无参数,返回默认值 0
}
}
```
App.jsx
```js
/*
为容器组件传入store 并渲染
*/
import React, { Component } from 'react'
import store from "./redux/store";
import Count from "./containers/Count";
export default class App extends Component {
render() {
return (
求和案例-react实现
)
}
}
```
Count.jsx
```js
/*
CountUI容器组件
引入connet 链接ui组件与redux
映射状态和状态方法
*/
import CountUI from "../../components/Count";
import { connect } from "react-redux";
import { createIncrementAction,createDecrementAction,createIncrementActionAsync } from "../../redux/count_action";
// 映射状态
function mapStateToProps(state) {
return {count:state}
}
// 映射操作状态的方法
function mapDispathToProps(dispath) {
return {
increment: inputNumber => {dispath(createIncrementAction(inputNumber))},
decrement: inputNumber => {dispath(createDecrementAction(inputNumber))},
incrementAsync: (inputNumber,time) => {dispath(createIncrementActionAsync(inputNumber,time))},
}
}
// 映射状态和方法 并暴露容器组件
export default connect(
mapStateToProps,
mapDispathToProps
)(CountUI)
```
CountUI.jsx
```js
import React, { Component } from 'react'
import './Count.css'
export default class Count extends Component {
// 加
increment = ()=>{
const userInput = (this.selectNumber.value)*1
this.props.increment(userInput)
}
// 减
decrement = ()=>{
const userInput = (this.selectNumber.value)*1
this.props.decrement(userInput)
}
// 奇数加
incrementIfOdd = ()=>{
const userInput = (this.selectNumber.value)*1
if (this.props.count % 2 !== 0) {
this.props.increment(userInput)
}
}
// 异步加
incrementAsync = ()=>{
const userInput = (this.selectNumber.value)*1
this.props.incrementAsync(userInput,500)
}
render() {
// const userInput = (this.selectNumber.value)*1
const {count} = this.props
return (
当前求和结果为:{count}
)
}
}
```
#### react-redux的优化
1. mapDispatchToProps 可以写成对象形式,React-Redux 底层会帮助自动分发。
```js
...
export default connect(
state => ({count:state}),// 映射状态
{
increment:createIncrementAction,
decrement:createDecrementAction,
incrementAsync:createIncrementActionAsync
} //映射状态的简写方式(对象 react-redux 会自动 分发dispath)
)(Count)
```
2. React-Redux 容器组件可以自动监测 Redux 状态变化,因此 index.js 不需要手动监听:
3. index.js入口文件中 Provider 组件可以让所有组件都能获得状态数据,不必一个一个传递
4. 整合容器组件和 UI 组件为一个文件:
```js
import React, { Component } from 'react'
import {
createIncrementAction,
createDecrementAction,
} from '../../redux/count_action'
import {connect} from 'react-redux'
// 定义 UI 组件
class Count extends Component {
...
}
// 创建容器组件
export default connect(
state => ({count: state}),
{
add: createIncrementAction,
sub: createDecrementAction
}
)(Count)
```
#### 多组件共享状态
文件结构,
容器组件和 UI 组件合为一体后放在 containers 文件夹(存放react-redux管理的需要管理状态的组件)。
redux 文件夹新建 actions 和 reducers 文件夹分别用于存放每个容器组件需要的 action 和 reducer 。
重点:*在 store.js 中引入 combineReducers() 整合多个 reducer 来合并总状态对象,组件中通过对象的形式访问*
redux/store.js | redux中管理状态的store模块
```js
// 引入 createStore 创建 store 对象
import { createStore,applyMiddleware,combineReducers } from "redux";
// 引入 redux-thunk , 用于支持异步action
import thunk from 'redux-thunk'
// 引入为 Count 服务的 countReducer
import countReducer from "./reducers/count"
// 引入为 Persion 服务的 countReducer
import persionReducer from "./reducers/persion"
// 汇总 reducers
const allReducers = combineReducers({
sum: countReducer,//合并后的key 会作为总状态对象中的key,value为状态
persions: persionReducer
})
// 暴露 store
export default createStore(allReducers,applyMiddleware(thunk))
```
redux/actions/persion.js | 创建action对象模块
```js
import { ADDPERSION } from "../constant";
export const createAddPersionAction = data => ({ type:ADDPERSION,data })
```
redux/reducers/persion.js | 初始化状态和处理状态的reducer模块
```js
import { ADDPERSION } from "../constant";
const initState = [{id:136780,name:'root',age:'0'}]
export default function persionReducer(preState=initState,action) {
const { type,data } = action
switch (type) {
case ADDPERSION:
return [data,...preState]
default:
return preState
}
}
```
redux/constant.js | redux中的常量模块
```js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADDPERSION = 'addPersion'
```
containers/Persion/index.jsx | UI组件和容器组件
```js
import React, { Component } from 'react'
import { connect } from "react-redux";
import { nanoid } from "nanoid";
import { createAddPersionAction } from "../../redux/actions/persion";
import './Persion.css'
class demoUI extends Component {
addPersion = ()=>{
let name = this.nameNode.value
let age = this.ageNode.value
const persionObj = {id:nanoid(),name,age}
this.props.addPersionObj(persionObj)
}
render() {
return (
)
}
}
export default connect(
state => ({
persionObjArr:state.persions ,
sum:state.sum ,
}),
{ addPersionObj: createAddPersionAction }
)(demoUI)
```
#### 补充 数组与对象方法
```js
let arr = [1,2]
// 给arr数组追加并return,下面两种方式的区别
return ['a','b',...arr]
arr.unshift('a','b')
return arr
/*
返回的内存地址不同
*/
```
#### Redux 开发者工具
1. Chrome 安装 Redux DevTools 扩展工具
2. 项目下载依赖包 `npm i redux-devtools-extension --save-dev`,
3. 配置:
```js
redux/store.js
import { composeWithDevTools } from 'redux-devtools-extension'
...
//需要异步中间件
export default createStore(Reducers, composeWithDevTools(applyMiddleware(thunk)))
// 不需要异步中间件
export default createStore(Reducers, composeWithDevTools())
```
## foot