React 介绍
React 是用于构建用户界面的 JavaScript 库
-
构建用户界面. User Interface,对咱们前端来说,简单理解为:HTML 页面
-
javscrtipt库。不是框架,是库。
-
vue: 是渐进式的javascript框架
-
react 全家桶是框架
- react: 核心库
- react-dom: dom操作
- react-router:路由,
- redux:集中状态管理
-
背景
-
框架背景
- react是Facebook(meta) 内部项目
- vue是尤雨溪个人作品
- angular是goole公司产品
-
趋势
- react全球第一
- vue在国内较多,react也慢慢多了
- angular在跨国公司使用较多
React 特点
1.声明式
只需要描述UI看起来时什么样的,就跟写html一样。
用类似于html的语法来定义页面。react中通过数据驱动视图的变化,当数据发生改变react能够高效地更新并渲染DOM。
<div className="app">
<h1>Hello React! 动态数据变化:{count}</h1>
</div>
2.组件化
组件是react中最重要的内容
组件用于表示页面中的部分内容
组合、复用多个组件,就可以实现完整的页面功能
3.学习一次,随处使用
使用react/rect-dom可以开发Web应用
使用react/react-native可以开发移动端原生应用(react-native)RN
使用react可以开发VR(虚拟现实)应用(react/react360)
React脚手架-从零开始创建项目
脚手架create-react-app
官方工具: create-react-app
创建方式1
-
先全局安装脚手架工具包
命令:
npm i -g create-react-app
-
用脚手架工具来创建项目
命令:
create-react-app your-project-name
创建方式2
直接使用npx来创建项目
命令:
npx create-react-app your-project-name
解释:
- npx create-react-app 是固定命令,
create-react-app
是 React 脚手架的名称 - your-project-name 表示项目名称,可以修改
React脚手架-了解项目的工作方式
启动项目
npm start
目录src
说明:
src
目录是我们写代码进行项目开发的目录- index.js是入口文件
- 查看
package.json
两个核心库:react
、react-dom
(脚手架已经帮我们安装好,我们直接用即可)
理解react-dom
rect包 提供必要功能来定义react组件。
react-dom包用来将react组件渲染到dom中。
react-native包 用来将react组件渲染到IOS和Android程序中。
JSX是什么
JSX:是 JavaScript XML的缩写。
-
在 JS 代码中书写 XML 结构
- 注意:JSX 不是标准的 JS 语法,是 JS 的语法扩展。脚手架中内置了 @babel/plugin-transform-react-jsx 包,用来解析该语法。
-
React用它来创建 UI(HTML)结构
理解:我们之前用html写页面,现在是用jsx来写页面
jsx示例
// 导入react和react-dom
import React from 'react'
import ReactDOM from 'react-dom'
// jsx创建元素
const list = <ul><li>html</li><li>js</li><li>css</li><ul>
// 渲染react元素
ReactDOM.render(list, document.getElementById('root'))
React 组件介绍
特点
- 独立
- 可复用
- 可组合
分类
- 基础组件:指
input
、button
这种基础标签,以及antd封装过的通用UI组件 - 业务组件:由基础组件组合成的业务抽象化UI。例如: 包含了A公司所有部门信息的下拉框
- 区块组件:由基础组件组件和业务组件组合成的UI块
- 页面组件:展示给用户的最终页面,一般就是对应一个路由规则
React 组件的两种创建方式
使用函数创建组件
定义组件
使用 JS 的函数(或箭头函数)创建的组件,叫做函数组件
-
约定1:函数名首字符大写
必须以大写字母开头**,React 据此区分
组件
和普通的 HTML
-
约定2:必须有返回值
表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null
// 1. 使用普通函数创建组件:
function Hello() {
return <div>这是我的第一个函数组件!</div>
}
function Button() {
return <button>按钮</button>
}
// 2. 使用箭头函数创建组件:
const Hello = () => <div>这是我的第一个函数组件!</div>
类组件-用class创建组件
定义格式
使用 ES6 的 class 创建的组件,叫做类(class)组件
// import { Component } from 'react'
// class 类名 extends Component {
import React form 'react'
class 类名 extends React.Component {
// ...
render () {
return 本组件的UI结构
}
}
注意:
- 类名必须以大写字母开头
- extends是一个关键字,用来实现类之间的继承。类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性。
- 类组件必须提供 render 方法,render 方法必须有返回值,表示该组件的 UI 结构。render会在组件创建时执行一次
使用组件
// 导入 React
import React from 'react'
import ReactDom from 'react-dom'
// 1. 定义组件
class Hello extends React.Component {
render() {
return <div>Hello Class Component!</div>
}
}
const content = (<div><Hello/></div>)
ReactDOM.render(content, document.getElementById('root'))
有状态组件和无状态组件
什么是状态(state)
定义:是用来描述事物在某一时刻的形态
的数据。一般称为state
特点:
- 状态能被改变,改变了之后视图会有对应的变化
作用:
- 保存数据。例如:要循环生成一份歌曲列表,那要提前准备好歌曲数据吧
- 为后续更新视图打下基础。如果用户点击了操作,让歌单的内容+1了,视图会自动更新(这是由react库决定的)
小结:
有状态组件:能定义state的组件。类组件就是有状态组件。
无状态组件:不能定义state的组件。函数组件又叫做无状态组件
注意:2019年02月06日,rect 16.8中引入了 React Hooks,从而函数式组件也能定义自己的状态了
定义状态
固定格式,使用
state = 对象
- 在构造函数中用
this.state= 对象
来做初始化
import React from "react";
export default class Hello extends React.Component {
// 1. state就是状态
state = {
list: [{ id: 1, name: "明天会更好" },{ id: 2, name: "难忘今宵" }],
isLoading: true
};
// 2. 构造函数
constructor() {
this.state = {
list: [{ id: 1, name: "明天会更好" },{ id: 2, name: "难忘今宵" }],
isLoading: true
}
}
render() {
return (
<>
<h1>歌单-{this.state.count}</h1>
<ul>
{this.state.list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<div>{this.state.isLoading ? "正在加载" : "加载完成"}</div>
</>
);
}
}
事件绑定
格式
<元素 事件名1={ 事件处理函数1 } 事件名2={ 事件处理函数2 } ></元素>
注意:React 事件名采用驼峰命名法,比如:onMouseEnter、onFocus、 onClick ......
import React from 'react'
import ReactDOM from 'react-dom'
const title = <h1>react中的事件</h1>
export default class Hello extends React.Component {
fn() {
console.log('mouseEnter事件')
}
render() {
return (
<div
onClick={() => console.log('click事件')}
onMouseEnter={this.fn}
能处理鼠标进入或者点击事件
</div>
)
}
}
const content = (
<div>
{title}
{<Hello />}
</div>
)
ReactDOM.render(content, document.getElementById('root'))
注意:
-
事件名是小驼峰命名格式
-
在类中补充方法
-
this.fn不要加括号:
onClick={ this.fn() }
先调用fn(),然后将fn的执行结果当做click事件的处理函数
别忘记了写this
获取事件对象
react中,通过事件处理函数的形参来获取。
落地代码
handleClick(e)=> {
e.preventDefault()
console.log('单击事件触发了', e)
}
render() {
return (
<div>
<button onClick={(e)=>{console.log('按钮点击了', e)}}>按钮</button>
<a href="http://itcast.cn/" onClick={this.handleClick}>嘻嘻</a>
</div>)
}
}
事件处理-this指向问题
不信你快看
class App extends React.Component {
state = {
msg: 'hello react'
}
handleClick() {
console.log(this) // 这里的this是?undefined
}
render() {
console.log(this) // 这里的this是?
return (
<div>
<button onClick={this.handleClick}>点我</button>
</div>
)
}
}
- render方法中的this指向的而是当前react组件。
- 事件处理程序中的this指向的是
undefined
事出有因
- 事件处理程序的函数式函数调用模式,在严格模式下,this指向
undefined
- render函数是被组件实例调用的,因此render函数中的this指向当前组件
class Person(name) {
constructor(){
this.name = name
}
say() {
console.log(this)
}
}
let p1 = new Person('小花')
p1.say()
const t = p1.say
t()
总结:
- class的内部,开启了局部严格模式
use strict
,所以this不会指向windowundefined
onClick={this.fn}
中,this.fn的调用并不是通过类的实例调用的,所以值是undefined
事件处理-this指向三种解决方案
方式1:在外层补充箭头函数
class App extends React.Component {
state = {
msg: 'hello react'
}
handleClick() {
console.log(this.state.msg)
}
render() {
return (
<div>
<button onClick={() => {this.handleClick()}}>点我</button>
</div>
)
}
}
原理:箭头函数中的this指向外层作用域中的this
缺点:需要额外包裹一个箭头函数,结构不美观
方式2:使用bind
class App extends React.Component {
state = {
msg: 'hello react'
}
handleClick() {
console.log(this.state.msg)
}
render() {
return (
<div>
<button onClick={this.handleClick.bind(this)}>点我</button>
</div>
)
}
}
原理:bind能绑定this
方式3:class 的实例方法
class App extends React.Component {
state = {
msg: 'hello react'
}
handleClick = () => {
console.log(this.state.msg)
}
render() {
return (
<div>
<button onClick={this.handleClick}>点我</button>
</div>
)
}
}
小结
- 方式3是最方便的,也是以后用的最多的方式
组件的状态-修改状态
setState
语法:this.setState({ 要修改的部分数据 })
作用:
- 修改 state
- 更新 UI
落地代码
state = {
count: 0
};
this.setState({
count: this.state.count++
})
理解状态不可变
react核心理念之状态不可变
不要直接修改当前状态值,而是创建新的状态值去覆盖老的值。
this.state.count = 100 // 无效
setState的典型用法
落地代码
import { Component } from 'react'
import ReactDOM from 'react-dom'
class HelloReact extends Component {
state = {
name: 'jack',
assets: [{ id: 1, name: '手机' }, { id: 2, name: '耳机' }],
skill: ['vue', 'react'], // angular
info: {
age: 18,
city: '武汉'
}
}
hClick5 = () => {
console.log(this.state.skill)
const newAssets = this.state.assets.filter((item) => item.id !== 2)
this.setState({
assets: newAssets
})
}
hClick4 = () => {
console.log(this.state.skill)
const newSkill = [...this.state.skill, 'angular']
this.setState({
skill: newSkill
})
}
hClick3 = () => {
console.log(this.state.assets)
const newAssets = [...this.state.assets]
newAssets[0].name = '电脑'
this.setState({
assets: newAssets
})
}
hClick2 = () => {
console.log(this.state.info)
this.setState({
info: {
...this.state.info,
age: 20
}
})
}
hClick1 = () => {
console.log(this.state.name)
this.setState({
name: '小花'
})
}
render () {
const { name, assets, skill, info } = this.state
return (
<div>
<p>姓名:{name}</p>
<p>
age:{info.age}, city:{info.city}
</p>
<div>
资产:<ul>{assets.map((item) => <li key={item.id}>{item.name}</li>)}</ul>
</div>
<p>skill:{skill.join(', ')}</p>
<hr />
{/*
1. 把name改成'小花'
2. 把age改成20
3. 把'手机'改成'电脑'
4. 向skill中添加'angular'
5. 删除id为2的assets */}
<button onClick={this.hClick1}>1. 把name改成小花</button>
<button onClick={this.hClick2}>2. 把age改成20</button>
<button onClick={this.hClick3}>3. 把手机改成电脑</button>
<button onClick={this.hClick4}>4. 向skill中添加angular</button>
<button onClick={this.hClick5}>5. 删除id为2的assets</button>
</div>
)
}
}
const app = (
<div>
app
{/* 2.使用组件 */}
<HelloReact />
</div>
)
ReactDOM.render(app, document.getElementById('root'))
非受控组件-ref
借助于ref,使用原生DOM的方式来获取表单元素的值
ref的使用格式
步骤
- 导入方法。
import { createRef } from 'react'
- 调用createRef方法创建引用,假设名为refDom。
const refDom = createRef()
- refDom设置给表单元素的ref属性。
<input ref={refDom}/>
- 通过refDom.current.value来获取值。
console.log(this.refDom.current.value)
内容
- 受控组件是通过 React 组件的状态来控制表单元素的值
- 非受控组件是通过手动操作 DOM 的方式来控制
- 此时,需要用到一个新的概念:ref
- ref:用来在 React 中获取 DOM 元素
落地代码
// 1. 导入方法
import { createRef } from 'react'
class Hello extends Component {
// 2. 调用createRef方法创建引用
txtRef = createRef()
handleClick = () => {
// 4. 通过.current.value来获取值
console.log(this.txtRef.current.value)
}
render() {
return (
<div>
<h1>如何获取input中的值-非受控组件-ref</h1>
{/* 3. 设置给表单元素的ref属性 */}
<p><input type="text" ref={this.txtRef}/></p>
<button onClick={handleClick}>获取文本框的值</button>
<div>
)
}
}
受控组件
如何理解受控
正常情况下,表单元素input是可任意输入内容的,可以理解为input自己维护它的状态(value)
受控组件的思路:
- 在state中定义状态
- 将state中的状态与表单元素的value值绑定到一起,进而通过state中的状态来控制表单元素的值
受控组件:value值受到了react控制的表单元素
基本步骤
有两个步骤:
-
在state中定义状态
-
对表单元素做两件事
- 设置value为上面定义的状态
- 绑定onChange事件,并在回调中通过setState来修改状态值
落地代码
class App extends React.Component {
state = {
// 1. 在state中定义状态
msg: 'hello react'
}
handleChange = (e) => {
this.setState({
msg: e.target.value
})
}
handleClick = ()=> {
console.log(this.state.msg)
}
render() {
return (
<div>
<h1>如何获取input中的值-受控组件</h1>
<p>
{/* 2. 对表单元素做两件事 */}
<input type="text"
value={this.state.msg}
onChange={this.handleChange}
/>
</p>
<button onClick={handleClick}>获取文本框的值</button>
<div>
)
}
}
注意
使用受控组件的方式处理表单元素后,状态的值就是表单元素的值。即:想要操作表单元素的值,只需要操作对应的状态即可
组件通讯
内容
- 组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。
- 在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。
- 而在这个过程中,多个组件之间不可避免的要共享某些数据
- 为了实现这些功能,就需要
打破组件的独立封闭性
,让其与外界沟通。这个过程就是组件通讯。
三种方式
- 父子组件之间
- 兄弟组件之间
- 跨组件层级
总结
组件中的状态是私有的,也就是说,组件的状态只能在组件内部使用,无法直接在组件外使用
props基本使用
格式
父组件-传入数据
<子组件 自定义属性1={值1} 自定义属性2={值2} .... />
子组件-函数式组件-接收数据
// 接收数据: 函数组件需要通过补充形参来获取
function 子组件(props) {
console.log('从父组件中传入的自定义属性被收集在对象:', props)
return (<div>子组件的内容</div>)
}
子组件-类组件-接收数据
// 接收数据: class 组件需要通过 this.props 来获取
class 子组件 extends Component {
console.log('从父组件中传入的自定义属性被收集在对象:', this.props)
render() { return (<div>子组件的内容</div>) }
}
函数组件获取props
// 接收数据:
// props 的值就是:{ age: 19 }
function HelloFunc(props) {
return (
<div>接收到数据:{props.name}</div>
)
}
// 传递数据:
// 可以把传递数据理解为调用函数 Hello,即:HelloFunc({ age: 19 })
<HelloFunc age={19} />
类组件获取props
// 接收数据:
// class 组件需要通过 this.props 来获取
class HelloClass extends Component {
render() {
return (
<div>接收到的数据:{this.props.age}</div>
)
}
}
// 传递数据:
<HelloClass age={19} />
props的三个注意事项
内容
- 可以传递任意数据
- 只读的
- 单向数据流
可以传递任意数据
props可以传递:数字 字符串 布尔类型 数组 对象 函数 jsx
props 是只读对象
只能读取对象中的属性,无法修改
this.props.age =20 // 错误
单向数据流
也叫做:自上而下的数据流
- 父组件中的数据可以通过 props 传递给子组件,并且,当父组件中的数据更新时,子组件就会自动接收到最新的数据
- 父组件的数据更新会流动到子组件,不能反过来,子组件直接去修改父组件的数据
- 类比:就像瀑布的水一样只能从上往下流动,并且,当上游的水变浑浊,下游的水也会受到影响
props的children属性
children属性
- children属性:表示该组件的子节点,只要组件有子节点,props就有该属性
- children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
测试代码
function Hello(props) {
return (
<div>
该组件的子节点:{props.children}
</div>
)
}
<Hello>我是子节点</Hello>
组件通讯-父传子
内容:
- 父组件提供要传递的state数据
- 给子组件标签添加属性,值为 state 中的数据
- 子组件中通过 props 接收父组件中传递的数据
核心代码
父组件提供数据并且传递给子组件
class Parent extends React.Component {
state = { lastName: '王' }
render() {
return (
<div>
传递数据给子组件:<Child name={this.state.lastName} />
</div>
)
}
}
子组件接收数据
function Child(props) {
return <div>子组件接收到数据:{props.name}</div>
}
组件通讯-子传父
步骤
-
父组件
- 定义一个回调函数f(将会用于接收数据)
- 将该函数f作为属性的值,传递给子组件
-
子组件
- 通过 props 获取f
- 调用f,并传入将子组件的数据
落地代码
父组件提供函数并且传递给子组件
class Parent extends React.Component {
state: {
num: 100
}
f = (num) => {
console.log('接收到子组件数据', num)
}
render() {
return (
<div>
子组件:<Child f={this.f} />
</div>
)
}
}
子组件接收函数并且调用
class Child extends React.Component {
handleClick = () => {
// 调用父组件传入的props,并传入参数
this.props.f(100)
}
return (
<button onClick={this.handleClick}>点我,给父组件传递数据</button>
)
}
小结
子传父:在子组件中调用从父组件中定义的方法,并根据需要传入参数
props校验-基本使用
对于子组件来说,props是外来的,无法保证组件使用者传入什么格式的数据,有了类型校验,我们的程序就更加健壮了。
步骤
-
导入
prop-types
包 。这个包在脚手架创建项目时就自带了,无须额外安装import PropTypes from 'prop-types'
这里的PropTypes可以改成其他的名字 -
使用
组件名.propTypes = {属性名1: 类型1, ...}
来给组件的props添加校验规则这里的propTypes是固定写法
例如
import PropTypes from 'prop-types'
class App extends React.component {
render(){
return (
<h1>Hi, {props.colors}</h1>
)
}
}
App.propTypes = {
// 约定colors属性为array类型
// 如果类型不对,则报出明确错误,便于分析错误原因
colors: PropTypes.array
}
作用:规定接收的props的类型必须为数组,如果不是数组就会报错,报错的格式如下
props校验-常见规则
内容:
- 常见类型:array、bool、func、number、object、string
- React元素类型:element
- 必填项:isRequired
- 特定结构的对象:shape({ })
示例
Com.propTypes = {
fn: PropTypes.func, // fn 是函数类型
isEdit: PropTypes:bool.isRequired // isEdit的类型是bool,且必须要传入
// 特定结构的对象
goodInfo: PropTypes.shape({ // goodInfo的格式一个对象,有 数值price属性 和 字符串name属性
price: PropTypes.number,
name: PropTypes.string
})
}
props默认值
默认值
定义: 没有赋值的情况下,给属性赋一个值
好处:简化调用组件时要传入的属性,更方便用户使用
两种方式
两种方式:
defaultProps
- 解构赋值的默认值
方法1:defaultProps
通过defaultProps
可以给组件的props设置默认值,在未传入props的时候生效
定义组件
class App extends React.Component{
return (
<div>
此处展示props的默认值:{ this.props.pageSize }
</div>
)
}
// 设置默认值
App.defaultProps = {
pageSize: 10
}
// 类型限制
App.propTypes = {
pageSize: PropTypes.number
}
使用组件
// 不传入pageSize属性
<App />
方法2:解构赋值的默认值
class App extends React.Component{
return (
// 解构赋值的默认值
const { pageSize = 10} = this.props
<div>
此处展示props的默认值:{ this.props.pageSize }
</div>
)
}
// 类型限制
App.propTypes = {
pageSize: PropTypes.number
}
props校验和默认值简化-类的静态成员-static
什么是静态成员
-
静态成员:通过类或者构造函数本身才能访问的属性或者方法
-
实例成员: 通过实例调用的属性或者方法
示例
class Person {
constructor(name){
this.name = name // 实例成员
}
static n = 1 // 静态成员 有static修饰
}
静态成员的定义和访问
定义
class Person {
constructor(name){
this.name = name // 实例成员
}
// 方法1:在class内部 通过static来定义
static n = 1 // 静态成员 有static修饰
}
// 方法2 在class外部 通过类型.属性名来定义
Person.m = function() { console.log()}
访问
Person.n
Person.m()
组件生命周期-概述
生命周期
生命周期:一个事物从创建到最后消亡经历的整个过程
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
钩子函数
在生命周期的不同阶段,会自动被调用执行的函数,为开发人员在不同阶段操作组件提供了时机。
注意:只有类组件 才有生命周期钩子函数
react类组件的生命周期钩子函数-整体说明
projects.wojtekmaj.pl/react-lifec…
组件生命周期-挂载阶段
内容
- 执行时机:组件创建时(页面加载时)
- 执行顺序:constructor() -> render() -> componentDidMount()
钩子 函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1. 初始化state 2. 创建Ref等 |
render | 每次组件渲染都会触发 | 渲染UI |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1. 发送网络请求 2.DOM操作 |
组件生命周期-更新阶段
更新阶段的钩子
更新阶段会执行两个钩子: render() -> componentDidUpdate()
三种操作可触发组件更新
- 调用setState。它能改数据&& 更新页面
- 调用forceUpdate()
- 组件接收到新的props
说明:以上三者任意一种发生,组件就会进入更新阶段
更新阶段能做的事情
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次组件渲染都会触发 | 渲染UI(与 挂载阶段 是同一个render) |
componentDidUpdate | 组件更新(完成DOM渲染)后 | DOM操作,可以获取到更新后的DOM内容 |
如果有些事情是需要每次更新去做的,就可以写在componentDidUpdate中。例如:数据本地存储
组件生命周期-卸载阶段
内容
执行时机:组件销毁
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等) |
示例:清理定时器&&移除事件
/* eslint-disable react/prop-types */
import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom'
class Son extends Component {
constructor () {
super()
this.timer = setInterval(() => {
console.log('子组件:', Date.now())
}, 1000)
this.fn = () => {
console.log('鼠标移动...')
}
window.addEventListener('mousemove', this.fn)
}
render () {
return <div>子组件:{this.props.content}</div>
}
componentDidUpdate () {
console.log('子组件: componentDidUpdate')
}
componentWillUnmount () {
console.log('子组件 卸载: componentWillUnmount')
// 删除事件
window.removeEventListener('mousemove', this.fn)
// 删除定时器
clearInterval(this.timer)
}
}
export default class App extends Component {
constructor () {
super()
console.log('1. constructor')
this.state = {
content: '',
isShow: true
}
this.refTxt = createRef()
}
hChange = (e) => {
this.setState({ content: e.target.value })
}
click = () => {
this.forceUpdate()
}
render () {
console.log('2. render')
return (
<div>
<button onClick={this.click}>更新</button>
组件<input value={this.state.content} onChange={this.hChange} />
<br />
{this.state.isShow && <Son content={this.state.content} />}
</div>
)
}
componentDidMount () {
console.log('3. componentDidMount')
console.log(this.refTxt.current)
// axios.get()
}
componentDidUpdate () {
console.log('更新完成: componentDidUpdate')
}
}
ReactDOM.render(<App />, document.getElementById('root'))
setState进阶-掌握它的3个特点
setState的特点
-
可以表现为异步。
setState调用之后,并不会立即去修改state的值,也不会立即去更新dom
-
多次调用会合并。setState({对象})会合并,再统一触发一次render()
-
使用不当会死循环。在componentDidUpdate, render 中调用setState()会导致死循环
setState进阶-第二个参数
setState的第二个参数
格式1
this.setState({} [,回调函数])
回调函数是可选的,它的作用是:当本轮setState生效(state更新,页面ui更新)之后,会调用回调函数
格式2
this.setState((上一状态) => {
return 新状态
}[,回调函数])
回调函数是可选的,它的作用是:当本轮setState生效(state更新,页面ui更新)之后,会调用回调函数
示例1
state = {
n: 1
}
hClick = () => {
this.setState( preState => ({ n: preState.n + 1 }))
this.setState( preState => ({ n: preState.n + 2 }))
this.setState( preState => ({ n: preState.n + 3 }))
}
// 页面上的n会是多少?
示例2
state = {
n: 1
}
hClick = () => {
this.setState( preState => ({ n: preState.n + 1 }), ()=>{console.log(this.state.n)})
this.setState( preState => ({ n: preState.n + 2 }), ()=>{console.log(this.state.n)})
this.setState( preState => ({ n: preState.n + 3 }), ()=>{console.log(this.state.n)})
}
// 1. 页面上的n会是多少?
// 2. 三个console.log会输出什么?
setState进阶-同步or异步
内容
setState本身并不是一个异步(setTime, setInterval, ajax,Promise.then.....)方法,其之所以会表现出一种异步的形式,是因为react框架本身的性能优化机制
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的setState调用会同步执行this.state。
所谓“除此之外”,指的是绕过React通过 addEventListener
直接添加的事件处理函数,还有通过setTimeout
|| setInterval
产生的异步调用。
简单一点说:
- 经过React 处理(事件回调,钩子函数)中的setState是
异步更新
- 没有经过React处理(通过
addEventListener
||setTimeout
/setInterval
)中的setState是同步更新
。
示例代码
import reactDom from 'react-dom'
import React, { Component } from 'react'
class App extends Component {
state = {
n: 1
}
// setTimeout(() => {
// this.setState({ n: 2 })
// console.log(this.state.n)
// console.log(document.getElementById('btn').innerHTML)
// }, 2000)
componentDidMount () {
// this.setState({ n: 2 })
// console.log(this.state.n)
// console.log(document.getElementById('btn').innerHTML)
document.getElementById('btn').addEventListener('click', () => {
this.setState({ n: 2 })
console.log(this.state.n)
console.log(document.getElementById('btn').innerHTML)
})
}
click = () => {
this.setState({ n: 2 })
console.log(this.state.n)
console.log(document.getElementById('btn').innerHTML)
}
render () {
return (
<div>
{/* <button id="btn" onClick={this.click}> */}
<button id="btn" onClick={this.click}>
{this.state.n}
</button>
</div>
)
}
}
reactDom.render(<App />, document.getElementById('root'))
总结
setState是同步的方法,但是react为了性能优化,所以setState在react的事件中表现得像异步。
参考链接:
宝,你都看到这了不给我一个star嘛?
PS: 如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请著明出处,如果有问题也欢迎私信交流