一、概念及特点
state的主要作用是用于组件保存、控制以及修改自己的状态,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。
注意
如果直接通过this.state.xxx的方式修改,组件不会重新渲染,但是值会被修改。并且这也是不规范的行为
二、state的定义及使用
1、在constructor中定义state
import React from "react";
export default class LearnState extends React.Component {
constructor() {
super();
/**
* 定义当前组件的state
* 这个对象里面可以存放任意数据
*/
this.state = {
name: '张三'
};
}
render () {
return (
<div>
{/*
使用的时候通过this找到state在找到对应要使用的属性
*/}
<div>{this.state.name}</div>
</div>
)
}
}
2、直接在组件内部定义state
注意
如果在constructor中定义了state,那么会覆盖组件内部直接定义的state
一般来说一个组件只会定义一个state对象,推荐直接在组件内部定义
import React from "react";
export default class LearnState extends React.Component {
/**
* 也可以直接在组件内部
*/
state = {
age: 10
}
render () {
return (
<div>
<div>{this.state.age}</div>
</div>
)
}
}
三、修改state
1、setState用法——第一个参数为函数
/**
* 第一个参数是一个函数接收两个参数
* 函数的
* 第一个参数为更新前的state
* 第二个参数为组件的props
*/
this.setState((preState, props) => {
/**
* 通过返回值修改state的状态
* 返回那个值就修改state里面对应的值
*/
return {
num: preState.num + 1
}
})
2、setState用法——第一个参数为对象
/**
* 传入一个对象
* 对象里面存放要修改state的数据
* 这是最常用的修改方式
*/
this.setState({
num: 2
})
四、使用setState对state进行赋值
1、对state中基本数据类型进行赋值
import React from "react";
export default class LearnSetState extends React.Component {
state = {
age: 18,
name: "张三",
isShow: false,
};
updateInfo () {
/**
* 基本数据类型 可以直接修改
*/
this.setState({
age: 19,
name: '李四',
isShow: true
})
}
render() {
return (
<div>
<div>哈哈 我是setState</div>
<button onClick={() => this.updateInfo()}>更换信息</button>
<div>
{this.state.name}-{this.state.age}-{this.state.isShow}
</div>
</div>
);
}
}
3、获取最新的state的值
this.setState({
age: 19,
name: '李四',
isShow: true
/**
* setState可以传入第二个参数是一个回调函数
* 该回调函数会在渲染完成后调用,可以在这里获取最新的state的值
*/
}, () => {
console.log(this.state)
})
4、数组类型的赋值
// 方法一:使用preState、concat创建新数组(不推荐,违反state是不可变数据的原则)
this.setState((preState) => {
return {
books: this.state.books.concat(["红宝书"]),
};
});
/**
* 方法二:ES6数组扩展 (推荐)
*/
this.setState(preState => ({
books: [...preState.books, '红宝书']
}))
5、对象类型进行赋值
/**
* 使用对象结构的方式进行赋值
*
* 注意:如果对象巨大则不是则该方法不适应,后面会讲
* 如何使用不可变数据更新state
*/
this.setState({
info: {
...this.state.info,
name: '王五'
}
})
五、深入理解setState
1、react 18版本,点击按钮会会打印出什么
import React from "react";
export default class LearnSetState3 extends React.Component {
state = {
num: 1,
};
action() {
setTimeout(() => {
this.setState({
num: 2,
});
console.log(this.state.num);
this.setState({
num: 3,
});
console.log(this.state.num);
});
}
render() {
return (
<div>
<button onClick={() => this.action()}>执行</button>
</div>
);
}
}
最终输出 1 1
2、react 17.x版本,点击按钮会会打印出什么
以react@17.0.2 版本为主
重新使用create-react-app创建项目并把并把react版本改成17.0.2
index.js入口文件修改成
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
App.js
import React from "react";
export default class App extends React.Component {
state = {
num: 1,
};
add() {
setTimeout(() => {
this.setState({
num: 2,
});
console.log(this.state.num);
this.setState({
num: 3,
});
console.log(this.state.num);
});
}
render() {
return (
<div>
<div>
<button onClick={() => this.add()}>+1</button>
</div>
</div>
);
}
}
我们发现这两个log是同步执行
3、那么为什么会出现这个问题呢
我们先来梳理下 React 的渲染流程,包括 render 阶段、commit 阶段,render 阶段是从 vdom 转 fiber,包含 schedule 和 reconcile,commit 阶段是把 fiber 更新到 dom。渲染流程的入口是 performSyncWorkOnRoot 函数。
setState 会创建 update 对象挂到 fiber 对象上,然后调度 performSyncWorkOnRoot 重新渲染。
在 react17 中,setState 是批量执行的,因为执行前会设置 executionContext。但如果在 setTimeout、事件监听器等函数里,就不会设置 executionContext 了,这时候 setState 会同步执行。可以在外面包一层 batchUpdates 函数,手动设置下 excutionContext 来切换成异步批量执行。
在 react18 里面,如果用 createRoot 的 api,就不会有这种问题了。
setState 是同步还是异步这个问题在 react18 里就不会再有了,因为所有的 setState 都是异步批量执行了
有兴趣知道更多react 18 和 react 17两者区别的同学可以留言联系我们