一、state的基本使用
需求:封装一个Clock组件来实现时钟的自动更新
1.用函数组件实现
function Clock(props){
return (
<div>
<h1>hello world</h1>
<h1>it is {props.time}</h1>
</div>
)
}
function tick(){
ReactDOM.render(<Clock time={new Date().toLocaleTimeString()} />, document.querySelector(".app"))
}
setInterval(tick, 1000);
然而,它忽略了一个关键的技术细节:Clock 组件需要设置一个计时器,并且需要每秒更新 UI,这样就大大降低了效率。
2.用类组件实现
我们需要在 Clock 组件中添加 “state” 来实现这个功能。
props 是外来传入数据,
state 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
class Clock extends React.Component {
constructor(props){
super(props)
// 状态初始化:手动给实例挂载一个属性 指向 一个对象
this.state = {
time : new Date().toLocaleTimeString(),
}
}
render() {
return (
<div>
<h1>Clock Time</h1>
<h1>it is {this.state.time}</h1>
</div>
)
}
// componentDidMount是声明工周期的钩子函数,第一次组件渲染完成后 调用
componentDidMount(){
console.log("componentDidMount");
setInterval(() => {
// setState作用:设置状态,通知react重新渲染视图
// setState哪来的?Component.prototype.setState = function (partialState, callback)
// partialState部分的状态替换,Object.assign(this.state,partialState)
this.setState({
time : new Date().toLocaleTimeString()
})
}, 1000);
}
}
二、setState使用时要注意的问题
构造函数是唯一可以给 this.state 赋值的地方
1.不要直接修改state,直接修改的话不会触发render重新渲染组件
class App extends React.Component {
constructor(props) {
super(props)
// 给state赋值
this.state = {
a: 0
}
}
clickHandle = () => {
// 01-验证:state是可以直接修改成功的,但不会触发render
this.state.a = 1
console.log(this.state.a); // 1
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
{/* React事件都是合成事件(包装事件):作用是跨平台 兼容性好,
自己封装的一套机制 代理原生事件(事件委托)*/}
{/* this.clickHandle 和 this.clickHandle() 加了括号后会自动执行,千万别加括号*/}
<button onClick={this.clickHandle}>button1</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
2.setState是异步的
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 0,
}
}
clickHandle = () => {
// 02-验证:setState 是异步的
this.setState({
a:2
})
console.log(this.state.a); // 打印 0 ,而界面已经渲染为2了,证明了setState是异步的
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
<button onClick={this.clickHandle}>button1</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
多个setState同时执行,会被合并到一起一次执行
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 0,
b: 0,
c: 0
}
}
clickHandle = () => {
// 03-验证多个setState同时执行也是异步的
// 多个setState同时执行,会被合并到一起一次执行,只执行一次render,批量更新,高效,减少重绘次数
this.setState({a:2})
this.setState({b:2})
this.setState({c:2})
console.log(this.state); // {a: 0, b: 0, c: 0}
// 06-更新相同字段时,只重绘最后一次更新的
this.setState({a:3})
console.log(this.state.a); // 0
this.setState({a:4})
console.log(this.state.a); // 0
this.setState({a:5})
console.log(this.state.a); // 0
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
<h1>b:{this.state.b}</h1>
<h1>c:{this.state.c}</h1>
<button onClick={this.clickHandle}>button1</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
三、setState使用的一些问题
1.异步更新所带来的一些问题
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 0,
}
}
clickHandle = () => {
// 04-需求:累加三次 , 但 实际上只执行了一次
// 因为是 异步的,所以都给合并了,render 一起执行了
// setState 不是立刻更新的,在调用setState 如果依赖state里面的数据会有隐患
// 不能依赖上以一个状态 来 计算下一个状态
this.setState({a:this.state.a + 1})
console.log(this.state.a); // 0
this.setState({a:this.state.a + 1})
console.log(this.state.a); // 0
this.setState({a:this.state.a + 1})
console.log(this.state.a); // 0
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
<button onClick={this.clickHandle}>button1</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
由于setState不会立即改变React组件中state的值,所以3次setState中this.state.value都是同一个值0,故而,这3次输出都是0。因而value只被加1。
解决方案
1.函数式setState - 如果this.setState的参数不是一个对象而是一个函数时,这个函数会接收到两个参数,第一个是当前的state值,第二个是当前的props,这个函数应该返回一个对象,这个对象代表想要对this.state的更改;
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 0,
}
}
clickHandle = () => {
// 05-函数式setState
// setState 可以接收一个函数
// 参数:perState 是上一个状态, 注意返回值是一个对象
this.setState((perState, props) => ({ a: perState.a + 1 }))
this.setState((perState, props) => ({ a: perState.a + 1 }))
this.setState((perState, props) => ({ a: perState.a + 1 }))
console.log(this.state.a); // 0
// 这相当于一个函数队列(执行完上一个 才会执行下一个),不会合并
// [(perState,props)=>({a:perState.a + 1}),
// (perState,props)=>({a:perState.a + 1}),
// (perState,props)=>({a:perState.a + 1})]
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
<button onClick={this.clickHandle}>button1</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
2.加定时器
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 0,
}
}
clickHandle = () => {
// 10-定时器
// 验证:setState 在定时器里面是同步函数
// 定时器下不能实现批量更新,也是同步的,不合并
setTimeout(() => {
this.setState({a: this.state.a + 1})
console.log(this.state.a) // 1
this.setState({a: this.state.a + 1})
console.log(this.state.a) // 2
this.setState({a: this.state.a + 1})
console.log(this.state.a) // 3
}, 0);
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
<button onClick={this.clickHandle}>button1</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
2.setState同步的更新
绕过React通过addEventListener直接添加的事件处理函数 和 setTimeout/setInterval(上面例子已展示)产生的异步调用。
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 0,
}
}
clickHandle2 = () => {
this.setState({
a: 1
})
// setState变同步代码了
console.log(this.state.a); // 1
}
componentDidMount() {
document.querySelector("#btn").addEventListener("click", this.clickHandle2)
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
<button onClick={this.clickHandle2}>button2</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
3.传统式setState 和 函数式setState 的混用
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 0,
}
}
clickHandle = () => {
// 11-传统式setState 和 函数式setState 的混用
this.setState((perState, props) => ({ a: perState.a + 1 }))
this.setState((perState, props) => ({ a: perState.a + 1 }))
this.setState({a:this.state.a + 1})
this.setState((perState, props) => ({ a: perState.a + 1 }))
console.log(this.state.a); // 0
}
render() {
console.log("render");
return (
<div>
<h1>a:{this.state.a}</h1>
<button onClick={this.clickHandle}>button1</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))
1> 在几个函数式setState调用中插入一个传统式setState调用,最后得到的结果是让this.state.a增加了2,而不是增加4。
2> 这是因为React会依次合并所有setState产生的效果,虽然前两个函数式setState调用产生的效果是a加2,但是中间出现一个传统式setState调用,一下子强行把积攒的效果清空,用a加1取代。
3> 所以,传统式setState与函数式setState一定不要混用。
4.要如何获取 异步函数setState的 结果呢?
1.使用 - - 回调函数(有可能产生回调地狱)
2.用 promise (不能解决代码合并问题)
3.用 async await (最佳方案)
参考如下:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
clickHandle = () => {
// 01 - 向setState传入 对象,回调函数解决异步问题(要拿到修改后的值)
// 但可能会产生回调地狱问题
// 有依赖计算的话会产生问题(代码合并)
this.setState({count:this.state.count + 1},()=>{
console.log("callback1:",this.state.count); // 1
})
this.setState({count:this.state.count + 1},()=>{
console.log("callback2:",this.state.count); // 1
})
// 02 - 向setState传入 函数(能解决依赖计算的问题)
// 但遇到多次运行,回调函数只能拿到最后的值,因为回调函数只会在所有函数队列运行完毕后再执行
this.setState((perState,props)=>{
return {count: perState.count + 1}
},()=>{
console.log("callback1:",this.state.count); // 2
})
this.setState((perState,props)=>{
return {count: perState.count + 1}
},()=>{
console.log("callback2:",this.state.count); // 2
})
// 03-用promise封装setState
// 但还是异步的,依旧不能解决代码合并问题
this.setStatePromise({ count: this.state.count + 1 }).then(() => {
console.log("callback1:", this.state.count); // 1
}).catch(error => {
console.log(error);
})
this.setStatePromise({ count: this.state.count + 1 }).then(() => {
console.log("callback2:", this.state.count); // 1
}).catch(error => {
console.log(error);
})
}
setStatePromise = (state) => {
return new Promise((resolve, reject) => {
if (!(typeof state === 'object' || typeof state === 'function' || state == null)) {
{
reject("setState(...): 参数不对");
}
} else {
this.setState(state, resolve())
}
})
}
// 04-异步解决方案,async await
// 把异步函数变成同步执行
// 完美解决
clickHandleAsync = async () => {
try {
await this.setStatePromise({ count: this.state.count + 1 })
console.log("callback1:", this.state.count); // 1
await this.setStatePromise({ count: this.state.count + 1 })
console.log("callback2:", this.state.count); // 2
} catch (error) {
console.log(error);
}
}
render() {
console.log("render");
return (
<div>
<h1>count:{this.state.count}</h1>
<button onClick={this.clickHandle}>ChangeCount</button>
<button onClick={this.clickHandleAsync}>AsyncChangeCount</button>
</div>
)
}
}
var app = <App />
ReactDOM.render(app, document.querySelector(".app"))