资讯专栏INFORMATION COLUMN

React专题:可变状态

hosition / 2797人阅读

摘要:的参数既可以是一个对象,也可以是一个回调函数。回调函数提供了两个参数,第一个参数就是计算过的对象,即便这时还没有渲染,得到的依然是符合直觉的计算过的值。专题一览什么是可变状态不可变属性生命周期组件事件操作抽象

本文是『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;
chaos

总之呢,React更新状态的设计到处都是坑。

大家对React吐槽最多的点是什么呢?

圈外人吐槽JSX。

圈内人吐槽this.setState

期盼React给开发者一个不令人困惑的状态更新API吧。

React专题一览

什么是UI
JSX
可变状态
不可变属性
生命周期
组件
事件
操作DOM
抽象UI

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108402.html

相关文章

  • React专题:不可变属性

    摘要:没有团伙,单独作案,干净利落,便于封口。它最大的特点就是不可变。兄弟组件之间传值原理和回调函数一样,只不过这里父组件只是一个桥梁。父组件接收到回调函数的值以后,通过保存该值,并触发另一个子组件重新渲染,重新渲染后另一个子组件便可以获得该值。 本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出来我的 GitHub repo 阅读完整的专题文章来我的 个人博客 ...

    biaoxiaoduan 评论0 收藏0
  • React专题:什么是UI

    摘要:现代前端框架的使命就是开发者管理状态,框架根据状态自动生成。专题一览什么是可变状态不可变属性生命周期组件事件操作抽象 本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出来我的 GitHub repo 阅读完整的专题文章来我的 个人博客 获得无与伦比的阅读体验 什么是UI? 如果你指的是布局和色彩,那更偏向于设计师的工作。 在现代web领域,大家已经有一个共识...

    silvertheo 评论0 收藏0
  • React专题:生命周期

    摘要:而生命周期钩子,就是从生到死过程中的关键节点。异步渲染下的生命周期花了两年时间祭出渲染机制。目前为这几个生命周期钩子提供了别名,分别是将只提供别名,彻底废弃这三个大活宝。生命周期钩子的最佳实践是在这里初始化。 本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出来我的 GitHub repo 阅读完整的专题文章来我的 个人博客 获得无与伦比的阅读体验 生命周期...

    Hanks10100 评论0 收藏0
  • React专题react,redux以及react-redux常见一些面试题

    摘要:我们可以为元素添加属性然后在回调函数中接受该元素在树中的句柄,该值会作为回调函数的第一个参数返回。使用最常见的用法就是传入一个对象。单向数据流,比较有序,有便于管理,它随着视图库的开发而被概念化。 面试中问框架,经常会问到一些原理性的东西,明明一直在用,也知道怎么用, 但面试时却答不上来,也是挺尴尬的,就干脆把react相关的问题查了下资料,再按自己的理解整理了下这些答案。 reac...

    darcrand 评论0 收藏0
  • react进阶漫谈

    摘要:父组件向子组件之间非常常见,通过机制传递即可。我们应该听说过高阶函数,这种函数接受函数作为输入,或者是输出一个函数,比如以及等函数。在传递数据的时候,我们可以用进一步提高性能。 本文主要谈自己在react学习的过程中总结出来的一些经验和资源,内容逻辑参考了深入react技术栈一书以及网上的诸多资源,但也并非完全照抄,代码基本都是自己实践,主要为平时个人学习做一个总结和参考。 本文的关键...

    neuSnail 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<