IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 你真的了解 setState 吗? -> 正文阅读

[JavaScript知识库]你真的了解 setState 吗?

setState 算是 React 里被使用的最高频的 api,但你真的了解 setState 吗?比如下面这段代码,你能清楚的知道输出什么吗?

import { Component } from 'react'
export class stateDemo extends Component {
	state = {
		count: 0
	}
	componentDidMount() {
		this.setState({ count: this.state.count + 1 })
		console.log(this.state.count)
		this.setState({ count: this.state.count + 1 })
		console.log(this.state.count)
		setTimeout(() => {
			this.setState({ count: this.state.count + 1 })
			console.log(this.state.count)
			this.setState({ count: this.state.count + 1 })
			console.log(this.state.count)
		}, 0)
	}

	render() {
		return null
	}
}

export default stateDemo

要彻底弄懂这道题,就不得不聊 setState 的异步更新,另外输出结果也要看当前处于哪种模式下。
我们先从 setState 的用法说起,以便全面掌握该 api。

1、为什么需要 setState

虽然我们一直在用 setState,可有没想过为什么 React 里会有该 api
React 是通过管理状态来实现对组件的管理,即 UI = f(state) f 就是我们的代码,最主要的就是 this.setState ,调用该函数后 React 会使用更新的 state 重新渲染此组件及其子组件,即达到了 UI 层的变更。

2、什么是 setState

setState 是 React 官方提供的更新 state 的方法,通过调用 setState,React 会使用最新的 state 值,并调用 render 方法将变化展现到视图。
React v16.3 版本之前,调用 setState 方法会依次触发以下生命周期函数

  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate
    那么 state 在哪个生命周期里会更新为最新的值?
import React, { Component } from 'react'

export default class stateDemo2 extends Component {
	state = {
		count: 0
	}
	shouldComponentUpdate() {
		console.info('shouldComponentUpdate', this.state.count) // shouldComponentUpdate 0
		return true
	}
	componentWillUpdate() {
		console.info('componentWillUpdate', this.state.count) // componentWillUpdate 0
	}
	increase = () => {
		this.setState({
			count: this.state.count + 1
		})
	}
	render() {
		console.info('render', this.state.count) // render 1
		return (
			<div>
				<p>{this.state.count}</p>
				<button onClick={this.increase}>累加</button>
			</div>
		)
	}
	componentDidUpdate() {
		console.info('componentDidUpdate', this.state.count) // componentDidUpdate 1
	}
}

可以看到,直到 render 执行时,state 的值才变更为最新的值,在些之前,state 一直保持为更新前的状态。

React v16.3 版本之后,调用 setState 方法会依次触发以下生命周期函数

确切的说,应该是 v16.4 版本之后,v16.3 版本 setState 并不会触发 getDerivedStateFromProps 函数

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate
    那么 state 在哪个生命周期里会更新为最新的值?
import React, { Component } from 'react'

export default class stateDemo3 extends Component {
	state = {
		count: 0
	}
	static getDerivedStateFromProps(props, state) {
		console.info('getDerivedStateFromProps', state.count) // getDerivedStateFromProps 1
		return { ...state }
	}
	shouldComponentUpdate() {
		console.info('shouldComponentUpdate', this.state.count) // shouldComponentUpdate 0
		return true
	}
	increase = () => {
		this.setState({
			count: this.state.count + 1
		})
	}
	render() {
		console.info('render', this.state.count) //render 1
		return (
			<div>
				<p>{this.state.count}</p>
				<button onClick={this.increase}>累加</button>
			</div>
		)
	}
	getSnapshotBeforeUpdate() {
		console.info('getSnapshotBeforeUpdate', this.state.count) //getSnapshotBeforeUpdate 1
		return null
	}
	componentDidUpdate() {
		console.info('componentDidUpdate', this.state.count) //componentDidUpdate 1
	}
}

可以看到新增的两个生命周期函数 getDerivedStateFromPropsgetSnapshotBeforeUpdate 获取到的 state 都是新值

3、setState 用法

3.1、setState(stateChange[, callback])

第一个参数是一个对象,会将传入的对象浅层合并到 ;第二个参数是个可选的回调函数

例如,调整购物车商品数:

this.setState({quantity: 2})

在回调函数参数里,可以获取到最新的 state 值,但推荐使用 componentDidUpdate

3.2、setState(updater, [callback])

第一个参数是个函数,(state, props) => stateChange,第二个参数同上是个可选的回调函数

例如:

this.setState((state, props) => {
  return {counter: state.counter + props.step};
});

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

4、state 的不可变性

我们要严格遵行 state 是不可变的原则,即不可以直接修改 state 变量,例如底下的做法就是不可取的

this.state.count ++
this.state.count ++
this.setState({})

这样是实现了同步更改 state 的目的,但违背了 state 是不可变的原则

4.1、基本数据类型

this.setState({
  count: 1,
  name: 'zhangsan',
  flag: true
})

4.2、对象类型

使用 ES6 的 Object.assign 或解构赋值

this.setState({
	// person: Object.assign({}, this.state.person, { name: 'lisi' })
    person:{...this.state.person,age:22}
})

4.3、数组类型

  • 追加选项
    使用 concat 或者解构赋值
this.setState((prevState) => {
	return {
		// hobbys: prevState.hobbys.concat('writing')
	    hobbys:[...prevState.hobbys,'writing']
	}
})
  • 截取选项
    使用 slice
this.setState({
	hobbys: this.state.hobbys.slice(0, 2)
})
  • 插入选项
    使用 slice 克隆一份,然后用 splice 插入选项
this.setState((prevState) => {
	let currentState = prevState.hobbys.slice() // 先克隆一份
	currentState.splice(1, 0, 'basketball')
	return {
		hobbys: currentState
	}
})
  • 过滤选项
    使用 filter
this.setState({
	hobbys: this.state.hobbys.filter((item) => item.length < 5)
})

注意,不能直接使用 push pop splice shift unshift 等,因为这些方法都是在原数组的基础上修改,这样违反不可变值

5、setState 到底是异步还是同步?

先给出答案:

  • legacy 模式中,即通过 ReactDOM.render(<App />, rootNode) 创建的,在合成事件和生命周期函数里是异步的,在原生事件和 setTimeoutpromise 等异步函数是同步的
  • blocking 模式中,即通过 ReactDOM.createBlockingRoot(rootNode).render(<App />) 创建的,任何场景下 setState 都是异步的
  • concurrent 模式中,即通过 ReactDOM.createRoot(rootNode).render(<App />) 创建的,任何场景下 setState 都是异步的

模式的说明详看官网
但由于后两种模式目前处于实验阶段,所以我们先重点分析下 legacy 模式,后面源码分析时,会说明下为什么其他两个模式都是异步的。

5.1 合成事件和生命周期函数里是异步的

import React, { Component } from 'react'

export default class stateDemo5 extends Component {
  state = {
    count:0
  }

  componentDidMount() {
    this.setState({
      count:this.state.count+1
    })
    console.info("didMount count:",this.state.count) // didMount count: 0
  }
  handleChangeCount = () => {
    this.setState({
      count:this.state.count+1
    })
    console.info("update count:",this.state.count) // update count: 1
  }
  render() {
    return (
      <div>
        {this.state.count}
        <button onClick={this.handleChangeCount}>更改</button>
      </div>
    )
  }
}

可以看到在 componentDidMount 生命周期函数与 handleChangeCount 合成事件里,setState 之后,获取到的 state 的值是旧值。

5.1.1、setState 合并处理

采用这种设置 state 方式,也会出现合并的现象:

import React, { Component } from 'react'

export default class stateDemo6 extends Component {
  state = {
    count:0
  }
  handleChangeCount = () => {
    this.setState({
      count:this.state.count+1
    },() => {
      console.info("update count:",this.state.count)
    })
    this.setState({
      count:this.state.count+1
    },() => {
      console.info("update count:",this.state.count)
    })
    this.setState({
      count:this.state.count+1
    },() => {
      console.info("update count:",this.state.count)
    })
  }
  render() {
    return (
      <div>
        {this.state.count}
        <button onClick={this.handleChangeCount}>更改</button>
      </div>
    )
  }
}

输出控制台信息如下:

update count: 1
update count: 1
update count: 1

本质上等同于 Object.assign

Object.assign(state,
  {count: state.count + 1},
  {count: state.count + 1},
  {count: state.count + 1}
 )

即后面的对象会覆盖前面的,所以只有最后的 setState 才是有效
那么要怎么弄才不会合并呢?
setState 的第一个参数设置为函数形式:

import React, { Component } from 'react'

export default class stateDemo7 extends Component {
  state = {
    count:0
  }
  handleChangeCount = () => {
    this.setState(prevState => {
      return {
        count:prevState.count+1
      }
    },() => {
      console.info("update count:",this.state.count)
    })
    this.setState(prevState => {
      return {
        count:prevState.count+1 
      }
    },() => {
      console.info("update count:",this.state.count)
    })
    this.setState(prevState => {
      return {
        count:prevState.count+1 
      }
    },() => {
      console.info("update count:",this.state.count)
    })
  }
  render() {
    return (
      <div>
        {this.state.count}
        <button onClick={this.handleChangeCount}>更改</button>
      </div>
    )
  }
}

输出控制台信息如下:

update count: 3
update count: 3
update count: 3

函数式 setState 工作机制类似于:

[
    {increment: 1},
    {increment: 1},
    {increment: 1}
].reduce((prevState, props) => ({
    count: prevState.count + props.increment
}), {count: 0})
// {count: 3}

5.2 在原生事件和 setTimeout 里是同步的

import React, { Component } from 'react'

export default class stateDemo8 extends Component {
  state = {
    count:0
  }
  componentDidMount() {
    document.querySelector("#change").addEventListener("click", () => {
      this.setState({
        count: this.state.count + 1,
      });
      console.log("update count1:", this.state.count); // update count1: 1
    });
  }
  handleChangeCount = () => {
      setTimeout(() => {
        this.setState({
          count: this.state.count + 1,
        });
        console.log("update count2:", this.state.count); // update count2: 1
      }, 0);
  }
  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button id="change">更改1</button>
        <button onClick={this.handleChangeCount}>更改2</button>
      </div>
    )
  }
}

可以看到原生的事件(通过 addEventListener 绑定的),或者 setTimeout 等异步方式更改的 state 是同步的。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-12 16:30:39  更:2021-08-12 16:33:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 8:44:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码