三、类组件的state & setState

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

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

本文参考自此

&lt;think&gt;我们正在讨论React组件中的setState使用方法和最佳实践。根据提供的引用,虽然引用中提到了PureComponent和测试,但用户的问题聚焦于setState的使用。因此,我将围绕setState的使用示例和最佳实践进行回答。 在React组件中,`setState`是用于更新组件状态的方法。它接受一个对象或函数作为参数,并且它是异步的。这意味着你不能依赖当前的状态值来立即计算下一个状态,因为多个`setState`调用可能会被批处理。 最佳实践: 1. 当新状态依赖于旧状态时,使用函数形式的`setState`,而不是对象形式。 2. 因为`setState`是异步的,所以如果你需要在状态更新后执行某些操作,可以使用`setState`的回调函数。 示例1:使用函数形式的setState ```jsx this.setState((prevState, props) =&gt; { // 使用prevState而不是this.state来计算新状态 return { count: prevState.count + 1 }; }); ``` 示例2:使用对象形式的setState(当不依赖之前的状态时) ```jsx this.setState({ count: 10 }); ``` 示例3:在setState完成后执行操作(回调函数) ```jsx this.setState( { count: this.state.count + 1 }, () =&gt; { console.log(&#39;状态更新完成,现在的count是:&#39;, this.state.count); } ); ``` 注意:在React的生命周期方法中,如`componentDidMount`或`componentDidUpdate`,你可以直接使用`setState`,但要注意避免在`render`方法中调用`setState`,因为这会导致无限循环。 另外,引用[1]中提到,即使组件有内部状态,也可以从`React.PureComponent`派生,这样可以实现浅比较来避免不必要的渲染。但是,使用`PureComponent`时,要确保状态是不可变的,因为浅比较可能会错过状态对象内部的变化。 引用[3]提到了enzyme库用于组件测试,这对于测试包含`setState`逻辑的组件非常有帮助。 综上所述,正确使用`setState`的关键是理解其异步特性,并在依赖旧状态时使用函数形式。同时,在状态更新后需要执行操作时,使用回调函数。 相关问题: 1. 为什么在ReactsetState是异步的? 2. 如何避免在React组件中不必要的重新渲染? 3.React组件中,除了setState还有哪些更新状态的方法?&lt;/think&gt;在 React 组件中,`setState()` 是管理组件状态的核心方法。以下是使用示例和最佳实践: ### 基本使用示例 ```jsx class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } // 基础用法:对象形式 increment() { this.setState({ count: this.state.count + 1 }); } // 函数形式(依赖前序状态) safeIncrement() { this.setState((prevState) =&gt; ({ count: prevState.count + 1 })); } // 回调函数(状态更新后执行) incrementWithLog() { this.setState( { count: this.state.count + 1 }, () =&gt; console.log(&#39;更新后的值:&#39;, this.state.count) ); } render() { return ( &lt;div&gt; &lt;p&gt;Count: {this.state.count}&lt;/p&gt; &lt;button onClick={() =&gt; this.increment()}&gt;增加&lt;/button&gt; &lt;/div&gt; ); } } ``` ### 最佳实践 1. **使用函数形式更新依赖状态** 当新状态依赖当前状态时,使用函数参数避免竞态条件: ```jsx // ✅ 推荐 this.setState((prevState) =&gt; ({ count: prevState.count + 1 })); // ❌ 避免(可能异步批处理导致错误) this.setState({ count: this.state.count + 1 }); ``` 2. **合并状态更新** React 会自动合并对象形式的 `setState` 调用: ```jsx // 最终结果:{ a: 1, b: 2 } this.setState({ a: 1 }); this.setState({ b: 2 }); ``` 3. **异步处理** `setState` 是异步操作,需用回调函数获取更新后状态: ```jsx this.setState({ data }, () =&gt; { console.log(&#39;DOM 已更新&#39;); // 似 componentDidUpdate }); ``` 4. **性能优化** 在 `shouldComponentUpdate` 中避免不必要的渲染: ```jsx shouldComponentUpdate(nextProps, nextState) { return nextState.count !== this.state.count; // 仅当 count 变化时渲染 } ``` 5. **与 PureComponent 配合** 状态组件可继承 `PureComponent` 自动浅比较优化: ```jsx class OptimizedCounter extends React.PureComponent { // 自动实现 shouldComponentUpdate } ``` [^1] ### 常见错误 - **直接修改状态**: ```jsx // ❌ 错误(不会触发渲染) this.state.count = 5; // ✅ 正确 this.setState({ count: 5 }); ``` - **连续调用依赖状态**: ```jsx // ❌ 可能输出相同值(批处理) this.setState({ count: this.state.count + 1 }); this.setState({ count: this.state.count + 1 }); // ✅ 使用函数形式确保链式更新 this.setState(prev =&gt; ({ count: prev.count + 1 })); this.setState(prev =&gt; ({ count: prev.count + 1 })); ``` ### 实际应用场景 1. **API 数据加载**: ```jsx componentDidMount() { fetch(&#39;/api/data&#39;) .then(res =&gt; res.json()) .then(data =&gt; this.setState({ data })); // 更新状态触发渲染 } ``` 2. **表单处理**: ```jsx handleChange = (e) =&gt; { this.setState({ [e.target.name]: e.target.value }); } ``` 3. **动画状态切换**: ```jsx toggleAnimation = () =&gt; { this.setState(prev =&gt; ({ isAnimating: !prev.isAnimating })); } ``` &gt; 对于复杂状态逻辑,建议结合 React Context 或状态管理库(如 Redux)[^2]。测试时可用 Enzyme 验证状态更新行为[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值