React专题:可变状态

栏目: 服务器 · 发布时间: 6年前

内容简介:本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出来我的来我的个人博客 获得无与伦比的阅读体验

本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出

来我的 GitHub repo 阅读完整的专题文章

来我的个人博客 获得无与伦比的阅读体验

React使用一个特殊的对象 this.state 来管理组件内部的状态。

然后开发者就可以通过描述状态来控制UI的表达。

如何描述状态呢?

一般我们会在 constructor 生命周期钩子初始化状态。

import React, { Component } from 'react';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = { name: '', star: 0 };
    }
}

export default App;
复制代码

也可以直接用属性初始化器的写法,看起来更加简洁。

然后通过 this.setSatate() 来改变状态。

import React, { Component } from 'react';

class App extends Component {
    state = { name: '', star: 0 };
    
    componentDidMount() {
        this.setState({ name: 'react', star: 1 });
    }
}

export default App;
复制代码

this.state

首先,改变状态有特殊的门路

开发者不能直接改变 this.state 的属性,而是要通过 this.setSatate 方法。

为什么要这样设计?

可能是为了更加语义化吧,开发者清楚自己在更新状态,而不是像Vue那样改变于无形。

不过别急,我为正在阅读的你准备了一个炸弹:

猜猜下面例子最终渲染出来的star是多少?

import React, { Component } from 'react';

class App extends Component {
    state = { star: 0 };
    
    componentDidMount() {
        this.state.star = 1000;
        this.setState(prevState => ({ star: prevState.star + 1 }));
    }
    
    // componentDidMount() {
        // this.setState(prevState => ({ star: prevState.star + 1 }));
        // this.state.star = 1000;
    // }
    
    // componentDidMount() {
        // this.state.star = 1000;
        // this.setState({ star: this.state.star + 1 });
    // }
    
    // componentDidMount() {
        // this.setState({ star: this.state.star + 1 });
        // this.state.star = 1000;
    // }
}

export default App;
复制代码

答案是1001。

诶,不是说不能直接改变 this.state 的属性么?

听我讲,首先, this.state 并不是一个不可变对象,你(非得较劲的话)是可以直接改变它的属性的。但是它不会触发 render 生命周期钩子,也就不会渲染到UI上。

不过,既然你确实改变了它的值,如果之后调用了 this.setSatate() 的话,它会在你直接改变的值的基础上再做更新。

所以呀少年,要想不懵逼,得靠我们自己的代码规范。

至于注释的部分,只是为了说明顺序问题。

第一部分注释渲染出来的star是1001。因为回调会首先计算star的值,而这时候star的值是1000。

第二部分注释渲染出来的star是1001。这很好理解。

第三部分注释渲染出来的star是1。这也好理解,这个时候star的值还是0。

其次,状态更新会合并处理

大家也看到了,我们可以每次更新部分状态。

新状态并不会覆盖旧状态,而是将已有的属性进行合并操作。如果旧状态没有该属性,则新建。

这类似于 Object.assign 操作。

而且合并是浅合并。

只有第一层的属性才会合并,更深层的属性都会覆盖。

import React, { Component } from 'react';

class App extends Component {
    state = { userInfo: { name: '', age: 0 } };
    
    componentDidMount() {
        this.setState({ userInfo: { age: 13 } });
    }
}

export default App;
复制代码

最后,可以有不是状态的状态

如果你需要存储某种状态,但是不希望在状态更新的时候触发 render 生命周期钩子,那么完全可以直接存储到实例的属性上,只要不是 this.state 的属性。使用起来还是很自由的。

异步更新

什么叫异步更新?

异步更新说的直白点就是批量更新。

它不是真正的异步,只是React有意识的将状态攒在一起批量更新。

React组件有自己的生命周期,在某两个生命周期节点之间做的所有的状态更新,React会将它们合并,而不是立即触发UI渲染,直到某个节点才会将它们合并的值批量更新。

以下,组件更新之后 this.state.star 的值是1。

import React, { Component } from 'react';

class App extends Component {
    state = { star: 0 };
    
    componentDidMount() {
        this.setState({ star: this.state.star + 1 });
        this.setState({ star: this.state.star + 1 });
        this.setState({ star: this.state.star + 1 });
    }
}

export default App;
复制代码

因为这些状态改变的操作都是在组件挂载之后、组件更新之前,所以实际上它们并没有立即生效。

this.state.star 的值一直是0,尽管状态被多次操作,它得到的值一直是1,因此合并之后 this.state.star 的还是1,并不是我们直觉以为的3。

为什么要异步更新?

因为 this.setSatate() 会触发 render 生命周期钩子,也就会运行组件的diff算法。如果每次setState都要走这一套流程,不仅浪费性能,而且是完全没有必要的。

所以React选择了在一定阶段内批量更新。

还是以生命周期为界,挂载之前的所有setState批量更新,挂载之后到更新之前的所有setState批量更新,每次更新间隙的所有setState批量更新。

非异步情况

再来看一种情况:

猜猜最终渲染出来的star是多少?

import React, { Component } from 'react';

class App extends Component {
    state = { star: 0 };
    timer = null;
    
    componentDidMount() {
        this.timer = setTimeout(() => {
            this.setState({ num: this.state.star + 1 });
            this.setState({ num: this.state.star + 1 });
            this.setState({ num: this.state.star + 1 });
        }, 5000);
    }
    
    componentWillUnmount() {
        clearTimeout(this.timer);
    }
}

export default App;
复制代码

答案是3。

卧槽!

说实话,这里我也没想明白。

我在React仓库的Issues里提过这个情况,这是React主创之一Dan Abramov的回答:

setState is currently synchronous outside of event handlers. That will likely change in the future.

Dan Abramov所说的 event handlers 应该指的是React合成事件回调和生命周期钩子。

我的理解,因为只有这些方法才能回应事件,所以它们之中的状态更新是批量的。但是它们之中的异步代码里有状态更新操作,React就不会批量更新,而是符合直觉的样子。

我们看下面的例子,正常的重复setState只会触发一次更新,但是http请求回调中的重复setState却会多次触发更新,看来异步的setState不在React掌控之内。

import React, { Component } from 'react';

class App extends Component {
    state = { star: 0 };
    
    componentDidMount() {
        fetch('https://api.github.com/users/veedrin/repos')
            .then(res => res.json())
            .then(res => {
                console.log(res);
                this.setState({ star: this.state.star + 1 });
                this.setState({ star: this.state.star + 1 });
                this.setState({ star: this.state.star + 1 });
            });
    }
}

export default App;
复制代码

还有一种情况就是原生的事件回调,比如document上的事件回调,也不是异步的。

总结一下:所谓的异步只是批量更新而已。真正异步回调和原生事件回调中的setState不是批量更新的。

不过,Dan Abramov早就提到过,会在将来的某个版本(可能是17大版本)管理所有的setState,不管是不是在所谓的 event handlers 之内。

React的设计有一种简洁之美,从这种对待开发者反馈的态度可见一斑。

回调

既然 this.setSatate() 的设计不符合直觉,React早就为开发者提供了解决方案。

this.setSatate() 的参数既可以是一个对象,也可以是一个回调函数。函数返回的对象就是要更新的状态。

回调函数提供了两个参数,第一个参数就是计算过的state对象,即便这时还没有渲染,得到的依然是符合直觉的计算过的值。同时,贴心的React还为开发者提供了第二个参数,虽然并没有什么卵用。

以下,组件更新之后 this.state.star 的值是3。

有一个小细节:箭头函数如果直接返回一个对象,要包裹一层小括号,以区别块级作用域。

import React, { Component } from 'react';

class App extends Component {
    state = { star: 0 };
    
    componentDidMount() {
        this.setState((prevState, prevProps) => ({ star: prevState.star + 1 }));
        this.setState((prevState, prevProps) => ({ star: prevState.star + 1 }));
        this.setState((prevState, prevProps) => ({ star: prevState.star + 1 }));
    }
}

export default App;
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Build Your Own Web Site the Right Way Using HTML & CSS

Build Your Own Web Site the Right Way Using HTML & CSS

Ian Lloyd / SitePoint / 2006-05-02 / USD 29.95

Build Your Own Website The Right Way Using HTML & CSS teaches web development from scratch, without assuming any previous knowledge of HTML, CSS or web development techniques. This book introduces you......一起来看看 《Build Your Own Web Site the Right Way Using HTML & CSS》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具