React

177 阅读29分钟

# 一、关于React

英文官网:reactjs.org/

中文官网:zh-hans.reactjs.org/

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

react在发展过程中,一直跟随原生js的脚步,特别是从v16.0版本开始(用到了class来创建组件)

2015年推出了使用react来编写移动端的app ---- react-native

重要版本发版时间

| 序号 | 版本号 | 发版时间 | 重要更新 |

| ---- | ------ | -------------- | -------------------------- |

| 1 | 16 | 2017年9月26 | 引入es6的类组件 |

| 2 | 16.3 | 2018年4月3日 | 生命周期更新 |

| 3 | 16.4 | 2018年5月23日 | 生命周期更新 |

| 4 | 16.8 | 2019年2月6日 | 引入 react hooks |

| 5 | 17.0 | 2020年10月20日 | 过渡版本 |

| 6 | 18.0 | 2022年3月29日 | 写法改变,严格模式发生改变 |

# 二、脚手架

英文官网:create-react-app.dev/

中文官网:create-react-app.bootcss.com/

> 补充:react的脚手架并不是只有 `create-react-app`,还有`dva-cli`,`umi`等

## 2.1 create-react-app脚手架的使用

> Create React App 让你仅通过一行命令,即可构建现代化的 Web 应用。

>

> 本文档之后称之为cra

创建项目的方式:

> 需要保证电脑安装node版本在14以上,系统在win7以上

>

> node14以上才会自带npx

```sh

# 方式1:使用npx

$ npx create-react-app react-basic

# 方式2:使用npm

$ npm init react-app react-basic

# 方式3:使用yarn

$ yarn create react-app react-basic

```

如果需要使用ts开发项目,创建项目时可以通过`--template typescript`指定模版

```sh

$ npx create-react-app myapp --template typescript

```

react脚手架不像vue,有那么多选项,只会给你创建一个基本的

如果出现如下内容,即代表项目创建成功

```sh

Success! Created react-basic at /Users/wudaxun/Desktop/workspace/code/bk2207A/code/react-course/react-basic

Inside that directory, you can run several commands:

npm start

Starts the development server.

npm run build

Bundles the app into static files for production.

npm test

Starts the test runner.

npm run eject

Removes this tool and copies build dependencies, configuration files

and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

cd react-basic

npm start

Happy hacking!

```

该项目使用的react+webpack,react项目基本上都是用webpack做的构建工具

## 2.2 项目目录解析

项目创建完毕生成目录结构如下:

```sh

react-basic

├── README.md

├── node_modules

├── package.json

├── .gitignore

├── public

│ ├── favicon.ico

│ ├── index.html

│ ├── logo192.png

│ ├── logo512.png

│ ├── manifest.json

│ └── robots.txt

└── src

├── App.css

├── App.js

├── App.test.js

├── index.css

├── index.js

├── logo.svg

├── reportWebVitals.js // 做性能测试

└── setupTests.js // 测试

```

`src/reportWebVitals.js`

```js

const reportWebVitals = onPerfEntry => {

if (onPerfEntry && onPerfEntry instanceof Function) {

import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {

getCLS(onPerfEntry); // 衡量视觉稳定性。为了提供一个好的用户体验,CLS应该小于0.1

getFID(onPerfEntry); // 衡量可交互性。为了提供一个好的用户体验,FID应该在100毫秒内。

getFCP(onPerfEntry); // 首次内容绘制

getLCP(onPerfEntry); // 衡量加载性能。为了提供一个好的用户体验,LCP应该在2.5秒内

getTTFB(onPerfEntry); // 到第一个字节的时间

});

}

};

export default reportWebVitals;

```

> react官方文档已经给了我们性能提升的方案:zh-hans.reactjs.org/docs/optimi…

打开`package.json`,发现可运行命令如下:

```json

"scripts": {

"start": "react-scripts start",

"build": "react-scripts build",

"test": "react-scripts test",

"eject": "react-scripts eject"

},

```

`start`指令用于启动开发者服务器

`build`指令用于打包项目

`test`指令用于测试

`eject`指令用于抽离配置文件

> `cra`脚手架基于`webpack`,默认`webpack`的配置在 `node_modules `下的` react-scripts` 内部,但是一般情况下,传输代码时,不会上传 `node_modules`,那么在必要情况下就必须得抽离配置文件。

>

# 三、JSX-基于js给js扩展了一个能力

设想如下变量声明:

```js

const element =

Hello, world!

;

```

这个有趣的标签语法既不是字符串也不是 HTML。

它被称为 JSX,是一个 JavaScript 的语法扩展。

JSX 可以生成 React “元素”。

> React [不强制要求](zh-hans.reactjs.org/docs/react-… JSX,但是大多数人发现,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。

`src`文件夹下只保留`index.js`

## 3.1 React.createElement

先看一个代码

`src/index.js`

```js

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// 因为 JSX 语法上更接近 JavaScript 而不是 HTML,

// 所以 React DOM 使用 `camelCase`(小驼峰命名)来定义属性的名称,

// 而不使用 HTML 属性名称的命名约定。

// 例如,JSX 里的 `class` 变成了 className

// class 在React中被看做了关键字

// const app = (

//

// hello react

//

// )

// ?为什么react模块被显示使用了

const app = React.createElement('div', { className: 'box' }, 'hello react !!!')

const root = ReactDOM.createRoot(document.getElementById('root'))

root.render(app)

```

`React.createElement()` 会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:

```js

// 注意:这是简化过的结构

const element = {

type: 'div',

props: {

className: 'box',

children: 'hello react'

}

};

```

这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。

## 3.2 jsx语法详解

在下面的例子中,我们声明了一个名为 `name` 的变量,然后在 JSX 中使用它,并将它包裹在大括号中

`src/index.js`

```js

import './App.css';

//引入两个组件

import MyHeader from './MyHeader';

import MyFooter from './MyFooter';

//html模板+数据,行为操作

//vue里面-》createElement

//render(h){return h('div',"xxx")}

function App () {

//1,react.createElement类似于vue的createElement

//2,jsx

//jsx的特点,1,jsx可以直接写在js文件中 2,jsx让我们可以把html元素,当成js里面的东西来写

let dom1 =

你好:

react

function createTitle (type) {

if (type) {

return

}

}

//react模板表达式是单花括号的

let content = "num"

return (

{dom1}

{content + "123"}

)

}

//写普通的js,肯定是不能这些

//webpack-》编译js的时候,对于这些jsx的html进行了编译处理

export default App;

```

> 在 JSX 语法中,你可以在大括号内放置任何有效的 [JavaScript 表达式](developer.mozilla.org/en-US/docs/… 或 `formatName(user)` 都是有效的 JavaScript 表达式。

在下面的示例中,我们将调用 JavaScript 函数 `formatName(user)` 的结果,并将结果嵌入到 `

` 元素中。

`src/index.js`

```js

import './App.css';

//引入两个组件

import MyHeader from './MyHeader';

import MyFooter from './MyFooter';

//html模板+数据,行为操作

//vue里面-》createElement

//render(h){return h('div',"xxx")}

function App () {

//1,react.createElement类似于vue的createElement

//2,jsx

//jsx的特点,1,jsx可以直接写在js文件中 2,jsx让我们可以把html元素,当成js里面的东西来写

let dom1 =

你好:

react

//可以在任意js操作中写入jsx

function createContent (type) {

if (type) {

return

content

} else {

return

error

}

}

//react模板表达式是单花括号的

return (

{dom1}

{createContent(true)}

)

}

//写普通的js,肯定是不能这些

//webpack-》编译js的时候,对于这些jsx的html进行了编译处理

export default App;

```

**jsx也是一个表达式**

在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。

也就是说,你可以在 `if` 语句和 `for` 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX。我们可以在任意的js代码中像返回一个变量一样的返回html元素

`src/index.js`

```js

import './App.css';

//引入两个组件

import MyHeader from './MyHeader';

import MyFooter from './MyFooter';

//html模板+数据,行为操作

//vue里面-》createElement

//render(h){return h('div',"xxx")}

function App () {

//1,react.createElement类似于vue的createElement

//2,jsx

//jsx的特点,1,jsx可以直接写在js文件中 2,jsx让我们可以把html元素,当成js里面的东西来写

let dom1 =

你好:

react

//可以在任意js操作中写入jsx

function createContent (type) {

if (type) {

return

content

} else {

return

error

}

}

//react模板表达式是单花括号的

return (

{dom1}

{createContent(true)}

)

}

//写普通的js,肯定是不能这些

//webpack-》编译js的时候,对于这些jsx的html进行了编译处理

export default App;

```

> 因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 `camelCase`(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

>

> 例如,JSX 里的 `class` 变成了 [`className`](developer.mozilla.org/en-US/docs/… `tabindex` 则变为 [`tabIndex`](developer.mozilla.org/en-US/docs/…

Babel 会把 JSX 转译成一个名为 `React.createElement()` 函数调用。

> 上述代码都没见到使用过 React 模块,但是显示却用了,为什么?

\

# 四、组件定义

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

## 4.1 类组件-vue2选项式api一样

ES6的加入让JavaScript直接支持使用class来定义一个类,react的创建类组件的方式就是使用的类的继承,`ES6 class`是一种使用React组件的写法,它使用了ES6标准语法来构建

```jsx

//类组件

//类得父类-react.reactComponet

//引入react

import react from "react"

//继承react.component

class MyHeader extends react.Component {

constructor(props) {

//调用super继承父类

super(props);

}

//核心方法render

render () {

//render方法决定组建的html模板

return (

component-header

)

}

}

export default MyHeader

```

## 4.2 函数组件-vue3的组合式api

定义组件最简单的方式就是编写 JavaScript 函数

```jsx

```

## 4.3 两组组件的区别

- 组件的定义方式不同。

- 生命周期不同:类组件有,函数式组件没有。

- 副作用操作执行不同:class组件通过生命周期函数,函数组件用hook的useEffect。

- state的定义、读取、修改方式不同:函数组件用hook的useState。

- this: class组件有,函数式组件没有。

- 实例: class组件有,函数时组件没有。

- ref使用不同:类组件可以获取子组件实例,函数式组件不可以,因为函数式组件没有实例。

> 官方推荐使用函数式组件,以上不同点虽然现在不明白是啥意思,没有关系,会随着大家的学习印象加深。

# 六、事件绑定

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。

- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。\

```

* 保证是一个函数

* 如果需要使用this关键词,保证this指向

```

## 6.1 ES5语法绑定事件

### 6.1.1 无参数的绑定

#### 6.1.1.1 方法一

- 定义函数

```

handleClick(e) { // e - 事件对象

e.preventDefault();

// doSomething ...

}

```

- constructor 中绑定函数执行上下文

```

this.handleClick = this.handleClick.bind(this);

```

- jsx中调用

```

```

#### 6.1.1.1 方法二

- 定义函数

```

handleClick(e) { // e - 事件对象

e.preventDefault();

// doSomething ...

}

```

- jsx 中调用

```

```

### 6.1.2 有参数的绑定

- 定义函数

```

handleClick(param1, param2, e) {

e.preventDefault();

// do something ...

}

```

> 注意此时无论多少个参数, e 一定放在最后

- jsx 中调用

```

<button onClick={this.hanleClick.bind(this, 'x', 'xx')} />

```

`src/index.js`

```jsx

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx

// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值

// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值

// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

// import App from './01_props/05_App_props_children' // 类插槽

// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)

// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随

// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态

// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态

// import App from './02_state/03_App_setState_function' // 修改状态 传递函数

// import App from './02_state/04_App_setState_object' // 修改状态 传递对象

// import App from './02_state/05_App_computed' // 类组件实现类似计算属性

// import App from './02_state/06_App_lifeCycle' // 类组件生命周期

import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用

root.render()

```

`src/03_event/01_App_event_es5.jsx`

```jsx

import React, { Component } from 'react';

// es5绑定事件 ---- 主要是对this指向的处理

class App extends Component {

constructor (props) {

super(props)

this.handlerClickFn = this.handlerClick.bind(this)

}

handlerClick (event) { // event为事件默认参数

console.log(1, this)

}

handlerParamsClick (a , b, event) { // 自定义参数 event将作为最后一个参数

console.log('a', a) // a 1

console.log('b', b) // b 2

}

render() {

return (

<button onClick={ this.handlerClickFn }>es5绑定事件-构造函数

<button onClick={ this.handlerClick.bind(this) }>es5绑定事件-jsx改变this指向

<button onClick={ this.handlerParamsClick.bind(this, '1', '2') }>es5绑定事件-传递参数

);

}

}

export default App;

```

## 6.2 ES6语法绑定事件

### 6.2.1 无参数绑定

#### 6.2.1.1 方法一

- 定义函数

```

handleClick = (e) => {

e.preventDefault();

// do something ...

}

```

- jsx中调用

```

```

> 比起 es 5 中的无参数函数的绑定调用, es 6 不需要使用 bind 函数;

#### 6.2.1.2 方法二

jsx中定义箭头函数

```

<button onClick={ () => {}} />

```

### 6.2.2 有参数绑定

#### 6.2.2.1 方法一

- 定义函数

```

handleClick = (param1, e) => {

e.preventDefault();

// do something ...

}

```

- jsx调用

```

<button onClick={this.hanleClick.bind(this, 'x')} />

```

> 有参数时,在绑定时依然要使用 bind;

> 并且参数一定要传,不然仍然存在 this 指向错误问题;

#### 6.2.2.2 方法二

- 定义函数

```

handleClick = (param1, e) => {

// do something ...

}

```

- jsx调用

```

<button onClick={() => this.handleClick('c')} />

// 如果需要对 event 对象进行处理的话,需要写成下面的格式

<button onClick={(e) => this.handleClick('c', e)} />

```

`src/index.js`

```jsx

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx

// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值

// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值

// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

// import App from './01_props/05_App_props_children' // 类插槽

// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)

// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随

// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态

// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态

// import App from './02_state/03_App_setState_function' // 修改状态 传递函数

// import App from './02_state/04_App_setState_object' // 修改状态 传递对象

// import App from './02_state/05_App_computed' // 类组件实现类似计算属性

// import App from './02_state/06_App_lifeCycle' // 类组件生命周期

// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式

import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用

root.render()

```

`src/03_event/02_App_event_es6.jsx`

```jsx

import React, { Component } from 'react';

// es5绑定事件 ---- 主要是对this指向的处理

class App extends Component {

handlerClickFn = (event) => {// event 默认参数

console.log(1, this)

}

handlerParamsClick = (a, b, event) => {

console.log('a', a) // a 1

console.log('b', b) // b 2

}

render() {

return (

<button onClick={ this.handlerClickFn }>es6绑定事件-定义箭头函数

<button onClick={ (event) => { // event 默认参数

console.log(2, this)

} }>es6绑定事件-jsx写箭头函数

<button onClick={ this.handlerParamsClick.bind(this, '1', '2') }>es6绑定事件-传递参数

<button onClick={ (event) => { // event 默认参数

console.log(2, this)

// this.fetchData({ count: this.state.count })

} }>es6绑定事件-jsx写箭头函数,直接使用参数

);

}

}

export default App;

```

## 6.3 合成事件的特点

### 6.3.1 事件机制

- `react`自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。

- `react` 的所有事件并没有绑定到具体的`dom`节点上而是绑定在了`document` 上,然后由统一的事件处理程序来派发执行。17以前

- 通过这种处理,减少了事件注册的次数,另外`react`还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。

### 6.3.2 对合成事件的理解

(1)对原生事件的封装

> `react`会根据原生事件类型来使用不同的合成事件对象,比如: 聚焦合成事件对象`SyntheticFoucsEvent`(合成事件对象:`SyntheticEvent`是`react`合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的,原生js的 事件对象为 `PointerEvent`)

(2)不同浏览器事件兼容的处理

> 在对事件进行合成时,`react`针对不同的浏览器,也进行了事件的兼容处理

### 6.3.3 事件机制的流程

#### 1、事件注册

> 在组件挂载阶段,根据组件内声明的事件类型-`onclick`,`onchange` 等,给 `document` 上添加事件 -`addEventListener`,并指定统一的事件处理程序 `dispatchEvent`。

#### 2、事件存储

> 完成事件注册后,将` react dom` ,事件类型,事件处理函数` fn `放入数组存储,组件挂载完成后,经过遍历把事件处理函数存储到 `listenerBank`(一个对象)中,缓存起来,为了在触发事件的时候可以查找到对应的事件处理方法去执行。

> 开始事件的存储,在` react` 里所有事件的触发都是通过 `dispatchEvent`方法统一进行派发的,而不是在注册的时候直接注册声明的回调,来看下如何存储的 。

> `react` 把所有的事件和事件类型以及`react` 组件进行关联,把这个关系保存在了一个` map`里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件id和 事件类型查找到对应的 事件fn

#### 3、事件执行

> 1、进入统一的事件分发函数(`dispatchEvent`)

> 2、结合原生事件找到当前节点对应的`ReactDOMComponent`对象

> 3、开始 事件的合成

>

> > - 根据当前事件类型生成指定的合成对象

> > - 封装原生事件和冒泡机制

> > - 在 `listenerBank`事件池中查找事件回调并合成到 `event`(合成事件结束)

>

> 4.处理合成事件内的回调事件(事件触发完成 end)

### 6.3.4 合成事x件、原生事件之间的冒泡执行关系

结论:

- 原生事件阻止冒泡肯定会阻止合成事件的触发。

- 合成事件的阻止冒泡不会影响原生事件。

原因:

- 浏览器事件的执行需要经过三个阶段,`捕获阶段-目标元素阶段-冒泡阶段`。

> 节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件。

# 七、State

`state` 是 `class`组件的内置对象,用于class组件内部数据更新

`state`就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用`state`的目的就是为了在不同的状态下使组件的显示不同(自己管理)

## 7.1 state及其特点

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件

不要直接修改state

state更新可能是异步的:出于性能考虑,React 可能会把多个 `setState()` 调用合并成一个调用。

state更新会被合并:当你调用 `setState()` 的时候,React 会把你提供的对象合并到当前的 state

## 7.2 state的定义和使用

目前react中的状态有两种使用方式:

### 7.2.1 es6的类 - 构造函数

`src/index.js`

```jsx

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx

// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值

// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值

// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

// import App from './01_props/05_App_props_children' // 类插槽

// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)

// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随

// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用

root.render()

```

`src/02_state/01_App_state_es6.jsx`

```jsx

import React, { Component } from 'react';

/**

* ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。

这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,

得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。

如果不调用super()方法,子类就得不到自己的this对象。

ES5 的继承机制,是先创造一个独立的子类的实例对象,

然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。

ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,

然后再将该对象作为子类的实例,即“继承在前,实例在后”

*/

class App extends Component {

// constructor (props) { // Useless constructor

// super(props)

// }

constructor (props) {

super(props)

this.state = {

date: new Date()

}

}

render() {

return (

当前时间为:{ this.state.date.toLocaleDateString() + this.state.date.toLocaleTimeString() }

);

}

}

export default App;

```

### 7.2.2 es7的类 - 属性初始化器

`src/index.js`

```jsx

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx

// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值

// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值

// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

// import App from './01_props/05_App_props_children' // 类插槽

// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)

// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随

// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态

import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用

root.render()

```

`src/02_state/02_App_state_es7.jsx`

```jsx

import React, { Component } from 'react';

class App extends Component {

state = {

date: new Date()

}

render() {

return (

当前时间为:{ this.state.date.toLocaleDateString() + this.state.date.toLocaleTimeString() }!!

);

}

}

export default App;

```

## 7.3 如何正确的修改state

`setState()` 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式.

将 `setState()` 视为*请求*而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。

`setState()` 并不总是立即更新组件。它会批量推迟更新。这使得在调用 `setState()` 后立即读取 `this.state` 成为了隐患。为了消除隐患,请使用 `componentDidUpdate` 或者 `setState` 的回调函数(`setState(updater, callback)`),这两种方式都可以保证在应用更新后触发。

记住修改状态的三大原则:

- 不要直接修改 State

```js

state = { a: 10 }

this.state.a = 100 // ❌

```

## 7.4 this.setState()方法及其特点

`setState()` 会对一个组件的 `state` 对象安排一次更新。当 state 改变了,该组件就会重新渲染。

`setState()`可以添加两个参数,

`setState()` 的第二个参数为可选的回调函数,它将在 `setState` 完成合并并重新渲染组件后执行

### 7.4.1 传递函数

参数一为带有形式参数的 `updater` 函数:

```

this.setState((state, props) => stateChange[, callback] )

```

`src/index.js`

```js

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx

// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值

// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值

// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

// import App from './01_props/05_App_props_children' // 类插槽

// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)

// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随

// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态

// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态

import App from './02_state/03_App_setState_function' // 修改状态 传递函数

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用

root.render()

```

`src/02_state/03_App_setState_function.jsx`

```jsx

import React, { Component } from 'react';

class App extends Component {

state = {

count: 100

}

render() {

return (

{ this.state.count }

<button

onClick={ () => {

this.setState((state, props) => {

return {

count: state.count + 1

}

}, () => {

console.log(4, this.state.count) // 103

})

console.log(1, this.state.count) // 100

this.setState((state, props) => {

return {

count: state.count + 1

}

}, () => {

console.log(5, this.state.count) // 103

})

console.log(2, this.state.count) // 100

this.setState((state, props) => {

return {

count: state.count + 1

}

}, () => {

console.log(6, this.state.count) // 103

})

console.log(3, this.state.count) // 100

} }

>加1

);

}

}

export default App;

```

> updater 函数中接收的 `state` 和 `props` 都保证为最新。updater 的返回值会与 `state` 进行浅合并。

### 7.4.2 传递对象

`src/index.js`

```jsx

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx

// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值

// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值

// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

// import App from './01_props/05_App_props_children' // 类插槽

// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)

// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随

// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态

// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态

// import App from './02_state/03_App_setState_function' // 修改状态 传递函数

import App from './02_state/04_App_setState_object' // 修改状态 传递对象

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用

root.render()

```

`src/02_state/04_App_setState_object.jsx`

```jsx

import React, { Component } from 'react';

class App extends Component {

state = {

count: 100

}

render() {

return (

{ this.state.count }

<button

onClick={ () => {

this.setState({

count: this.state.count + 1

}, () => {

console.log(4, this.state.count) // 101

})

console.log(1, this.state.count) // 100

this.setState({

count: this.state.count + 1

}, () => {

console.log(5, this.state.count) // 101

})

console.log(2, this.state.count)// 100

this.setState({

count: this.state.count + 1

}, () => {

console.log(6, this.state.count) // 101

})

console.log(3, this.state.count)// 100

} }

>加1

);

}

}

export default App;

```

> 这种形式的 `setState()` 是异步的,并且在同一周期内会对多个 `setState` 进行批处理,相当于

>

> ```

> Object.assign(

> prevState,

> {count: this.state.count + 1},

> {count: this.state.count + 1},

> ...

> )

>

>

> ```

>

> 后调用的 `setState()` 将覆盖同一周期内先调用 `setState` 的值,因此商品数仅增加一次。如果后续状态取决于当前状态,建议使用 updater 函数的形式代替(前面案例已经实现)。或者在第二个参数中再继续操作。

> 思考题:

>

> 1.[何时以及为什么 `setState()` 会批量执行?](stackoverflow.com/a/48610973/…)

>

> 2.[为什么不直接更新 `this.state`?](github.com/facebook/re…)

## 7.5 setState的细节注意点

### 7.5.1 state 的更新可能是异步的,并且会合并

vue的数据更新是同步的,页面渲染是异步的

react数据修改也是异步的

```javascript

this.setState({ a: 888 }, function () {

//这样才能获取带修改后的a属性

console.log(this.state.a);

})

//同步console.log获取到的是之前的

console.log(this.state.a);

```

多次更新会被合并

```javascript

this.setState({

a: 111

})

this.setState({

a: 222

})

this.setState({

a: 333

})

// 比如下面这样,这也是为什么要设计成异步的原因

// Object.assign(this.state, {

// a: 111

// },

// {

// a: 222

// },

// {

// a: 333

// }

// )

```

这也是为什么要设计成异步,应为同步必然是会导致多次更新

### 7.5.2 setState每次调用都会更新

即使设置的值和上一次一模一样,只要调用setState方法,它必定会产生一次更新。

```javascript

//传入空对象,没有修改state,也会导致产生更新.但是这样的更新没必要

this.setState({})

```

解决方案:采用PureComponent,让组件继承于PureComponent

```jsx

class Review extends React.PureComponent {

//es7的新写法,可以跳过constructor简便定义state

state = {

a: 1

}

render(){

return

123

}

}

```

## 7.6修改数组和对象的注意点

当我们使用PureComponent,因为PureComponent会检测state有没有变,从而决定是否更新。

我们如果直接修改原对象,然后setState,是不会触发更新的

```javascript

//无法触发更新

this.state.obj.a=999;

this.setState({

obj:this.state.obj

})

```

解析:对象是否改变是根据内存地址判定的,所以修改this.state.obj.a并没有改变原对象的地址,导致判定为数据没有改变

必须先拷贝原对象,然后修改拷贝后的对象,在进行赋值。

```javascript

this.setState({

obj: {

//展开在新对象里,解除引用

...this.state.obj,

//修改属性

a: 999

}

})

```

# 九、条件渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。

React 中的条件渲染和 JavaScript 中的一致,使用 JavaScript 操作符 if 或条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI。

## 9.1 &&

你可以通过用花括号包裹代码在 JSX 中嵌入任何表达式 ,也包括 JavaScript 的逻辑与 &&,它可以方便地条件渲染一个元素。

`src/index.js`

```jsx

import React from "react";

class IfCom extends React.PureComponent {

state = {

isShow: true,

level: 2

}

changeShow () {

this.setState({

isShow: !this.state.isShow,

})

}

//根据数据判断,如果成立,就返回html,不成立就返回空字符串

render () {

return (

条件渲染

{/*运算,三元,并且或,调用方法 */}

{/* {this.state.isShow ?

123
: ""} */}

{this.state.isShow &&

123
}

{this.state.isShow ? "隐藏" : "显示"}

)

}

}

export default IfCom

```

`src/04_condition/01_App_condition_yu.jsx`

```jsx

import React, { Component } from 'react';

class MainBox extends Component {

render () {

return (

{

this.props.unReadMessage.length > 0 && 还有{ this.props.unReadMessage.length }条未读消息

}

)

}

}

class App extends Component {

state = {

message: ['a', 'b', 'c', 'd']

}

render() {

return (

{

this.state.message.map((item, index) => {

return (

{ item }

<button onClick={ () => {

const arr = this.state.message // 获取数据

arr.splice(index, 1) // 处理数据

this.setState({ // 修改状态

message: arr

})

}}>已读

)

})

}

<MainBox unReadMessage = { this.state.message } >

);

}

}

export default App;

```

## 9.2 三元运算符

条件渲染的另一种方法是使用 JavaScript 的条件运算符:

```jsx

import React from "react";

class IfCom extends React.PureComponent {

state = {

isShow: true,

level: 2

}

changeShow () {

this.setState({

isShow: !this.state.isShow,

})

}

//根据数据判断,如果成立,就返回html,不成立就返回空字符串

render () {

return (

条件渲染

{/*运算,三元,并且或,调用方法 */}

{this.state.isShow ?

123
: ""}

{this.state.isShow ? "隐藏" : "显示"}

)

}

}

export default IfCom

```

## 9.5.3 复杂判断写成方法

但是如果判断比较复杂,三元和&&就不方便了。我们就需要使用一个方法来进行运算,比如下面,根据level是几,来显示对应的h几标签,比如level是1,就显示h1在页面上

```jsx

import React from "react";

class IfCom extends React.PureComponent {

state = {

isShow: true,

level: 2

}

changeShow () {

this.setState({

isShow: !this.state.isShow,

})

}

getH () {

//类似于v-if v-else

let _level = this.state.level

console.log(_level);

if (_level == 1) {

return

title1

} else if (_level == 2) {

return

title2

} else if (_level == 3) {

return

title3

}

}

//根据数据判断,如果成立,就返回html,不成立就返回空字符串

render () {

return (

条件渲染

{/*运算,三元,并且或,调用方法 */}

{/* {this.state.isShow ?

123
: ""} */}

{this.state.isShow &&

123
}

{this.state.isShow ? "隐藏" : "显示"}

{this.getH()}

)

}

}

export default IfCom

```

# 十、列表渲染

### 10.1 模板上渲染一个数组

我们在react模板上渲染,一个数组,react会把这个数组里的每一项拿出来渲染在页面上

比如

```jsx

class ForCom extends React.PureComponent {

state = {

arr: [1, 2, 3, 4 ]

}

render () {

return (

{this.state.arr}

)

}

}

export default ForCom

```

这样的代码,最后渲染在页面上就是1234

因为jsx的缘故,我们是可以在数组里写html的

比如

```jsx

class ForCom extends React.PureComponent {

state = {

arr: [

1
,
2
,
3
,
4
]

}

render () {

return (

{this.state.arr}

)

}

}

export default ForCom

```

这样我们页面上会出现四个div

所以其实我们要把列表循环的本质就是形成一个存放html的数组

```js

//进行这样的一个转化

[1,2,3,4]转化为=>[

1
,
2
,
3
,
4
]

```

### 10.2 列表循环数组

所以我们可以通过一个循环来转化

```jsx

import React from "react";

class ForCom extends React.PureComponent {

state = {

arr: [1, 2, 3, 4]//->[< /div>]

}

showList () {

let _arr = [];

this.state.arr.forEach((item) => {

//通过循环,形成一个div,加入到数组里

_arr.push(

{item}
)

})

//循环完毕后-_arr=[

1
,
2
,
3
,
4
]

return _arr;

}

render () {

return (

{this.showList()}

)

}

}

export default ForCom

```

我们也可以通过map方法一步到位,因为map方法会把返回的内容形成新数组

```jsx

import React from "react";

class ForCom extends React.PureComponent {

state = {

arr: [1, 2, 3, 4]//->[< /div>]

}

showList () {

return this.state.arr.map((item) => {

return

{item}

})

}

render () {

return (

{/*如果使用map,应为是直接调用一个方法, 所以我们可以直接写在模板语法里 */}

{

this.state.arr.map((item) => {

//通过循环,形成一个div,加入到数组里

return

{item}

})

}

{this.showList()}

)

}

}

export default ForCom

```

# 十一、表单绑定

在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:

```html

名字:

```

> 此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。

>

> 表单元素的value值受 state的控制

## 11.1 各种表单的绑定与取值

`src/index.js`

```jsx

// src/index.js

import React from 'react'

import ReactDOM from 'react-dom/client'

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx

// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值

// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值

// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

// import App from './01_props/05_App_props_children' // 类插槽

// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)

// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随

// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态

// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态

// import App from './02_state/03_App_setState_function' // 修改状态 传递函数

// import App from './02_state/04_App_setState_object' // 修改状态 传递对象

// import App from './02_state/05_App_computed' // 类组件实现类似计算属性

// import App from './02_state/06_App_lifeCycle' // 类组件生命周期

// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式

// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式

// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&

// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符

// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class

// import App from './04_condition/04_App_condition_cssinjs' // cssInJs

// import App from './04_condition/05_App_module_css' // 模块化css

// import App from './04_condition/06_App_style' // 动态style

// import App from './05_list/01_App_map' // 列表渲染 使用map

// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历

import App from './06_form/01_App_form' // 表单绑定 受控组件

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用

root.render()

```

`src/06_form/01_App_form.jsx`

```jsx

import React, { Component } from 'react';

class App extends Component {

state = {

userName: '',

password: '',

sex: '女',

hobby: [],

lesson: 1,

note: '',

flag: false

}

// handlerUserNameChange = (event) => {

// this.setState({ userName: event.target.value })

// }

// handlerPasswordChange = (event) => {

// this.setState({ password: event.target.value })

// }

// handlerChange = (type, event) => {

// this.setState({ [type]: event.target.value })

// }

handlerChange = (event) => {

console.log(event.target.name)

this.setState({ [event.target.name]: event.target.value })

}

handlerHobbyChange = (event) => {

const checked = event.target.checked

const value = event.target.value

const arr = this.state.hobby

// checked 为真选中,加入数组,为假 删除

if (checked) {

arr.push(value)

} else {

const index = arr.findIndex(item => item === value)

arr.splice(index, 1)

}

console.log(arr)

this.setState({ hobby: arr })

}

handlerFlagChange = (event) => {

this.setState({ flag: event.target.checked })

}

render() {

return (

{/* <input type="text" placeholder='用户名' value={ this.state.userName } onChange = { this.handlerUserNameChange }/> { this.state.userName } */}

{/* <input type="text" placeholder='用户名' value={ this.state.userName } onChange = { this.handlerChange.bind(this, 'userName') }/> { this.state.userName } */}

<input type="text" placeholder='用户名' name="userName" value={ this.state.userName } onChange = { this.handlerChange }/> { this.state.userName }

{/* <input type="password" placeholder='密码' value={ this.state.password } onChange = { this.handlerPasswordChange }/> { this.state.password } */}

{/* <input type="password" placeholder='密码' value={ this.state.password } onChange = { this.handlerChange.bind(this, 'password') }/> { this.state.password } */}

<input type="password" placeholder='密码' name="password" value={ this.state.password } onChange = { this.handlerChange }/> { this.state.password }

<input type="radio" value="男" name="sex" checked={ this.state.sex === '男'} onChange = { this.handlerChange }/>男

<input type="radio" value="女" name="sex" checked={ this.state.sex === '女'} onChange = { this.handlerChange }/>女 --- { this.state.sex }

<input type="checkbox" name="hobby" value="🏀" onChange={ this.handlerHobbyChange }/>🏀

<input type="checkbox" name="hobby" value="⚽" onChange={ this.handlerHobbyChange }/>⚽

<input type="checkbox" name="hobby" value="🏐" onChange={ this.handlerHobbyChange }/>🏐

<input type="checkbox" name="hobby" value="🏓" onChange={ this.handlerHobbyChange }/>🏓 ---

{

this.state.hobby && this.state.hobby.map(item => {

return <span key = { item }>{item}

})

}

<select name="lesson" value={this.state.lesson} onChange={ this.handlerChange }>

1阶段 2阶段 3阶段

--- { this.state.lesson }

<input type="checkbox" checked = { this.state.flag } onChange = { this.handlerFlagChange } /> ***** 用户协议 -- { this.state.flag + ''}

);

}

}

export default App;

```

## 11.2 受控表单以及非受控组件

在 HTML 中,表单元素(如``、 `` 和 ``)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 [`setState()`](https://react.docschina.org/docs/react-component.html#setstate)来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

> `input`、`textarea`、`select` 受控组件: value的属性受了 `state` 的控制

>

> - 使用了受控组件,一定要写 `value` 属性以及`onChange`事件

>

> `radio`、'checkbox' 受控组件: checked 的属性受了`state`的控制

>

> 如果需要设置默认值,那么需要通过 `defaultValue` 以及`defaultChecked`设置

案例如上