资讯专栏INFORMATION COLUMN

深入React知识点整理(二)

villainhr / 476人阅读

摘要:承接上文,深入知识点整理一使用也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。有限状态机,表示有限个状态以及在这些状态之间的转移和动作等行为的模型。

承接上文,深入React知识点整理(一)
使用React也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。下面整理一些知识点,算是React看书,使用,感悟的一些总结:

React 生命周期

setState调用栈

7.React 生命周期
React 的主要思想是通过构建可复用组件来构建用户界面。所谓组件,其实就是有限状态机(FSM),通过状态渲染对应的界面,且每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。

有限状态机,表示有限个状态以及在这些状态之间的转移和动作等行为的模型。一般通过状态、事件、转换和动作来描述有限状态机。

组件的生命周期在不同状态下的执行顺序:

当首次挂载组件时,按顺序执行 getDefaultPropsgetInitialStatecomponentWillMountrendercomponentDidMount

当卸载组件时,执行 componentWillUnmount

当重新挂载组件时,此时按顺序执行 getInitialStatecomponentWillMountrendercomponentDidMount,但并不执行 getDefaultProps

当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

constructor 中的 this.state = {} 其实就是调用内部的 getInitialState 方法。

自定义组件(ReactCompositeComponent)的生命周期主要通过 3 个阶段进行管理——MOUNTING、RECEIVE_PROPS 和 UNMOUNTING,它们负责通知组件当前所处的阶段,应该执行生命周期中的哪个步骤。

创建组件:createClass或者extend Component创建自定义组件,初始化defaultProps。

MOUNTING:调用getInitialState初始化state,调用componentWillMount,之后render,最后调用componentDidMount。通过 mountComponent 挂载组件,初始化序号、标记等参数,判断是否为无状态组件,并进行对应的组件初始化工作,比如初始化 propscontext 等参数。利用 getInitialState 获取初始化state、初始化更新队列和更新状态。

RECEIVE_PROPS:当参数变化的时候,按照图中顺序调用,且在 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdate中也还是无法获取到更新后的 this.state,即此时访问的 this.state 仍然是未更新的数据。updateComponent 本质上也是通过递归渲染内容的,由于递归的特性,父组件的 componentWillUpdate是在其子组件的 componentWillUpdate 之前调用的,而父组件的 componentDidUpdate也是在其子组件的 componentDidUpdate 之后调用的。

UNMOUNTING:如果存在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如果此时在 componentWillUnmount 中调用 setState,是不会触发 re-render 的,这是因为所有更新
队列和更新状态都被重置为 null,并清除了公共类,完成了组件卸载操作。

最后,一张图归纳React生命周期,以及不同生命周期中setState的使用情况:
屏幕快照 2017-12-14 下午10.42.31.png

这里主要介绍了React生命周期执行顺序,以及在不同生命周期内使用setState的情况,重点在于合适使用setState。

8.setState调用栈

React中的setState是个很著名的方法,当你需要更改state触发页面重绘的时候,调用setSatet方法。但是setState并不会立刻执行你的更改,这也是刚开始使用React时很苦恼的一件事。

经过一段时间的使用和学习,知道了React的setState是"异步"的:

State Updates May Be Asynchronous

React may batch multiple setState() calls into a single update for performance.

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

官方给出的回答是React为了优化性能,会合并多次setState操作,并计算出最终值再执行,所以setState看起来是"异步"的。这种想法很好,我们可以借鉴到项目开发中去,但是代码层面是如何实现的?在任何地方调用setState都会是’异步的吗‘?

setState源码:

// 更新 state
ReactComponent.prototype.setState = function (partialState, callback) {
    this.updater.enqueueSetState(this, partialState);
    if (callback) {
        this.updater.enqueueCallback(this, callback, "setState");
    }
};
...
enqueueSetState: function (publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
        publicInstance,
        "setState"
    );
    if (!internalInstance) {
        return;
    }
    // 更新队列合并操作
    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);
    enqueueUpdate(internalInstance);
},
...
// 如果存在 _pendingElement、_pendingStateQueue和_pendingForceUpdate,则更新组件
performUpdateIfNecessary: function (transaction) {
    if (this._pendingElement != null) {
        ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
    }
    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
        this.updateComponent(transaction, this._currentElement, this._currentElement, this._context,
            this._context);
    }
} 

通过上面源码大致可以看出,setState函数执行的时候,把被更新的state放入到_pendingStateQuenue队列中,之后将组件实例传入并执行enqueueUpdate,并且如果有回调函数放到enqueueCallback中。

问题现在全在enqueueUpdate上,通过简易的流程图可以看出enqueueUpdate判断当前组件是否处于批量更新模式中,是就简单将组件存入脏组件队列,不是的话更新脏组件队列,从而更新调用updateComponent,更新props和state,触发页面重绘。
屏幕快照 2017-12-07 下午7.46.18.png

到这里可以看到很清楚地看到,setState并不是全部都是’异步的‘,当组件处于非批量更新模式的时候,是立即更新的。enqueueUpdate源码:

function enqueueUpdate(component) {
    ensureInjected();
    // 如果不处于批量更新模式
    if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
    }
    // 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中
    dirtyComponents.push(component);
} 

batchingStrategy代码如下:

var ReactDefaultBatchingStrategy = {
    isBatchingUpdates: false,
    batchedUpdates: function(callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        if (alreadyBatchingUpdates) {
            callback(a, b, c, d, e);
        } else {
            transaction.perform(callback, null, a, b, c, d, e);
        }
    },
} 

通过代码来看,isBatchingUpdates默认是false,也就是组件默认是不处于批量更新模式的,第一次执行enqueueUpdate时候,在batchedUpdates方法中,将开关置true,之后更新都处于批量更新模式。

这里还有一个概念:事务。

事务就是将需要执行的方法使用 wrapper 封装起来,再通过事务提供的 perform 方法执行。而在 perform 之前,先执行所有 wrapper 中的 initialize 方法,执行完 perform 之后(即执行method 方法后)再执行所有的 close 方法。一组 initialize 及 close 方法称为一个 wrapper。从图3-16中可以看出,事务支持多个 wrapper 叠加。

屏幕快照 2017-12-07 下午8.28.26.png

那事务又跟setState有什么关系,我们举个例子:

import React, { Component } from "react";
class Example extends Component {
    constructor() {
        super();
        this.state = {
            val: 0
        };
    }
    componentDidMount() {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val); // 第 1 次输出
        this.setState({val: this.state.val + 1});
        console.log(this.state.val); // 第 2 次输出
        setTimeout(() => {
            this.setState({val: this.state.val + 1});
            console.log(this.state.val); // 第 3 次输出
            this.setState({val: this.state.val + 1});
            console.log(this.state.val); // 第 4 次输出
        }, 0);
    }
    render() {
        return null;
    }
} 

这个例子中,componentDidMount生命周期函数中连着调用了两次setState,之后在setState中又连着调用了两次,结果却完全不同,打印输出0,0,2,3。尝试着在React的各个阶段函数中打印log结果如下:

屏幕快照 2017-12-07 下午9.20.54.png

通过截图可以看到,我们在调用setState的之后,打印输出了事务结束的closeAll,这说明componentDidMount函数被事务当做method调用,之后才打印输出batchedUpdates。原来早在 setState 调用前,已经处于batchedUpdates 执行的事务中了。那这次 batchedUpdate 方法,又是谁调用的呢?让我们往前再追溯一层,原来是 ReactMount.js中的 _renderNewRootComponent 方法。也就是说,整个将 React 组件渲染到 DOM 中的过程就处于一个大的事务中。

接下来的解释就顺理成章了,因为在 componentDidMount 中调用 setState 时,batchingStrategy的 isBatchingUpdates 已经被设为 true,所以两次 setState 的结果并没有立即生效,而是被放进了 dirtyComponents 中。这也解释了两次打印 this.state.val 都是 0 的原因,因为新的 state 还没有被应用到组件中。而setTimeout函数中的setState并没有在事务中,所以立即执行。

所以这里得到一个结论:在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state

React引发的事件处理有很多,都是我们常用的比如所有的生命周期函数(在生命周期函数中可以使用setState的),React代理的事件;这些函数应用场景很高,让我们误以为setState一直是"异步"的。

所以,我们也可以得出解决setState异步的方法:

使用setState第二个参数callback;

setTimeout函数(虽然不好,但也不失为一种办法);

在componentDidUpdate处理一些逻辑(要看应用场景);

第一个参数传入计算函数;

当然了,还有一个比较决绝的办法,就是不用setState。

相关文章可以看看程墨老师的文章:setState为什么不会同步更新组件状态。

总结

React算是一个颠覆式的UI框架,有很多知识点值得学习,深入的研究一些问题对于实际使用上也会有一定帮助。本文也是根据《深入React技术栈》前几章内容摘录,整理的知识点,有兴趣可以看看原著。

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

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

相关文章

  • Record 前端开发知识整理分享

    showImg(https://segmentfault.com/img/remote/1460000018716142?w=200&h=200); showImg(https://segmentfault.com/img/remote/1460000018716143);showImg(https://segmentfault.com/img/remote/1460000010953710);...

    TZLLOG 评论0 收藏0
  • 写一本关于 React.js 的小书

    摘要:因为工作中一直在使用,也一直以来想总结一下自己关于的一些知识经验。于是把一些想法慢慢整理书写下来,做成一本开源免费专业简单的入门级别的小书,提供给社区。本书的后续可能会做成视频版本,敬请期待。本作品采用署名禁止演绎国际许可协议进行许可 React.js 小书 本文作者:胡子大哈本文原文:React.js 小书 转载请注明出处,保留原文链接以及作者信息 在线阅读:http://huzi...

    Scorpion 评论0 收藏0
  • 深入React识点整理(一)

    摘要:以我自己的理解,函数式编程就是以函数为中心,将大段过程拆成一个个函数,组合嵌套使用。越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用。也许继面向对象编程之后,函数式编程会成为下一个编程的主流范式。 使用React也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。下面整理一些知识点,算是React看书...

    Gilbertat 评论0 收藏0
  • 前端资源系列(4)-前端学习资源分享&前端面试资源汇总

    摘要:特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 本以为自己收藏的站点多,可以很快搞定,没想到一入汇总深似海。还有很多不足&遗漏的地方,欢迎补充。有错误的地方,还请斧正... 托管: welcome to git,欢迎交流,感谢star 有好友反应和斧正,会及时更新,平时业务工作时也会不定期更...

    princekin 评论0 收藏0
  • 前端文档收集

    摘要:系列种优化页面加载速度的方法随笔分类中个最重要的技术点常用整理网页性能管理详解离线缓存简介系列编写高性能有趣的原生数组函数数据访问性能优化方案实现的大排序算法一怪对象常用方法函数收集数组的操作面向对象和原型继承中关键词的优雅解释浅谈系列 H5系列 10种优化页面加载速度的方法 随笔分类 - HTML5 HTML5中40个最重要的技术点 常用meta整理 网页性能管理详解 HTML5 ...

    jsbintask 评论0 收藏0

发表评论

0条评论

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