三、类组件的state & setState

本文深入讲解React中的状态管理,包括state的基本使用、setState的注意事项及其潜在问题,对比不同使用方式的特点,并提供了解决异步更新问题的最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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"))

本文参考自此

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值