从 React 的组件更新谈 Immutable 的应用

在上一篇文章《Immutable 在 JavaScript 中的应用》 中主要介绍了 Immutable 之于 JavaScript。而基于 Immutable 的特性,将其应用在 React 项目的开发中非常合适,解决了 React 中的一些痛点,能进一步提升 React 组件的性能以及更好的管理组件的状态。

在介绍 Immutable 如何在 React 中应用之前,先来谈谈 React 组件是如何更新的。

React 是基于状态驱动的开发,可以将一个组件看成是一个有限状态机,组件要更新,必须更新状态。

通常说的组件的状态就是组件的 state 对象,state 是可以由当前组件自行修改更新的,这种自更新的状态的为了便于理解区分可以称之为"动态"的状态。但除了更新 state 外,组件还可以通过 props 来更新,props 属性不能由组件自行修改,必须由父组件来修改,然后再传递给当前组件,更新组件的 props 也能引起组件的更新,可以将 props 称之为"静态"的状态。这样的状态区分是广义上的,如果你不认可 props 也是状态也没关系,这里可以不用拘泥于文字。

组件的更新说起来可能会显得抽象一点,实际上我们说要更新一个组件其实就是更新 DOM 树,React 设计的再牛逼,要在浏览器中跑起来最终还是要生成 DOM 树。在使用 React 时通常并不直接操作 DOM,而是由状态驱动,状态可以随时更新,而 DOM 树并不会随着状态的更新而更新,因为对于 DOM 的频繁的操作是很耗费性能的。为了节省性能,React 内部实现了一套 Virtual DOM (虚拟的 DOM),简言之就是将原生的 DOM 树通过 JavaScript 对象来做一次映射。状态的更新会引起 Virtual DOM 的更新,React 通过高效的 Diff 算法来比较状态更新前和更新后的 Virtual DOM 是否真的变化,只有 Virtual DOM 更新了才会更新真实的 DOM 树。

React组件的更新流程

只要状态更新,那么就一定会引起 Virtual DOM 的 Diff 操作,而原生 DOM 是否更新就要看 Diff 的结果。

如果要更新状态,无论是动态的 state 状态,还是静态的 props 状态,必然要主动调用 setState 方法,而且只要调用了该方法,那么就一定会更新状态。这也意味着每调用一次 setState 方法都会触发 Virtual DOM 的 Diff 操作,尽管可能并未更新原生 DOM,Diff 操作也会带来性能的开销。

每更新一次状态,组件都会执行 Update 的生命周期中的一系列方法:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • componentDidUpdate

当然,还会执行 render 方法。

不过实际情况还要复杂些,只要一个组件更新了动态的 state 状态,那么这个组件包含的所有子组件以及子组件一层层嵌套的子组件都会执行上面的 Update 流程。

React的组件树的Render示意图

关于 setState 方法,调用之后并不会立即执行,而是会有一个事件循环,在这个循环中标记组件为 dirty 状态,等事件循环结束,才会将所有标记为 dirty 状态的组件执行 Update 的流程,此时才会重新 render 所有的子孙组件。所以 setState 是一个异步的操作

React 的这种组件的更新方式,虽然用了很多办法来节省性能开销,诸如事件循环的机制Virtual DOM高性能的 Diff 算法,但仍然避免不了会有一些性能开销,并且随着组件的复杂度的提升对于性能的开销而成正比。

那么有没有办法对这种更新方式进行优化呢?先来看看 React 都提供了什么。在 Update 的生命周期阶段会触发一个 shouldComponentUpdate 的方法,在这个方法中可以主动的去 Diff 状态。

shouldComponentUpdate (nextProps, nextState) {
        return nextProps.id !== this.props.id;
    };

shouldComponentUpdate 方法在执行完如果返回的是 true,那么组件就会继续 Update 的流程,如果返回 false,则不会继续 Update 的流程,默认情况下都是返回的 true。上面的方法判断的是两次的 id 是否相等,如果不相等才继续 Update 流程。

有没有觉得这样挺好的,好像有哪里不对啊,组件的状态我不可能都以硬编码的形式写上一大坨而且无法复用的代码,而且说好的 Immutable 也没见用上啊。

是的,上面说了那么一大堆,总算该轮到 Immutable 出场了。在进行状态的 Diff 时,对于复杂的 Mutable 数据,一项一项的去遍历不现实,借用 Immutable,可以直接实现「值」的比较,而且性能又好。

所有复杂的状态的 Diff,结合 Immutable,都能用下面这个工具方法全搞定。

import { is } from 'immutable';
    const keys = Object.keys;

    const shallowEqualImmutable = (context, nextProps, nextState) => {
        const currentState = context.state;
        const currentProps = context.props;
        const nextStateKeys = keys(nextState || {});
        const nextPropsKeys = keys(nextProps);

        // 先从数据的长度判断
        if (nextStateKeys.length !== keys(currentState || {}).length ||
            nextPropsKeys.length !== keys(currentProps).length
        ) {
            return true;
        }

        // 再按key逐个比较数据是否相等
        let isUpdate = nextStateKeys.some((item) => (
            currentState[item] !== nextState[item] &&
            !is(currentState[item], nextState[item])
        ));

        if (isUpdate) {
            return true;
        }

        return nextPropsKeys.some((item) => (
            currentProps[item] !== nextProps[item] &&
            !is(currentProps[item], nextProps[item])
        ));
    };

那么组件中的 stateprops 状态数据,对于 Mutable 类型的数据都相应的都要转换成 Immutable 数据。

// 组件的初始 state 定义
    state = {
        $list: Immutable.List([1, 2, 3]),
        foo: 'bar'
    };

    // 在某个方法中调用 setState
    doSomething = () => {
        let $list = this.state.$list;
        $list = $list.push(4);

        this.setState({ $list });
    };

    // 调用 Diff 的工具方法
    shouldComponentUpdate (nextProps, nextState) {
        return shallowEqualImmutable(this, nextProps, nextState);
    };

将组件的 Mutable 状态都转换成 Immutable 后,组件的 Update 流程会变成如下所示,同时也避免了调用 setState 后,状态并未变化的带来的不必要的 Update 操作。

优化后的React的组件树的Render示意图

当父组件的 state 状态更新后,如果子组件的状态并未更新,那么子组件将不会再一层一层的去执行 Update 流程,从而达到优化性能的目的。

Chrome 81 正式发布 !消灭混合内容最后一步~

Chrome 81 于前天正式发布了,这个版本其实最初是计划在 3 月 17 号 发布的,但由于冠状病毒(COVID-19)爆发而导致推迟到了现在。Chrome 81 的延迟也扰乱了 Google 正常的六周发布时间表。因此 Google 此前也宣布,下一个版本将直接跳过 Chrome 82 ,直接发布 Chrome 83。 下面我就来带大家看看 Chrome 81 有哪些重要的更新。

发布于:2天以前  |  17次阅读  |  详细内容 »

当浏览器全面禁用三方 Cookie

苹果公司前不久对 Safari 浏览器进行一次重大更新,这次更新完全禁用了第三方 Cookie,这意味着,默认情况下,各大广告商或网站将无法对你的个人隐私进行追踪。而微软和 Mozilla 等也纷纷采取了措施禁用第三方 Cookie,但是由于这些浏览器市场份额较小,并没有给市场带来巨大的冲击。

发布于:2天以前  |  15次阅读  |  详细内容 »

H5 直播的疯狂点赞动画是如何实现的?

直播有一个很重要的互动:点赞。 为了烘托直播间的氛围,直播相对于普通视频或者文本内容,点赞通常有两个特殊需求: 点赞动作无限次,引导用户疯狂点赞 直播间的所有疯狂点赞,都需要在所有用户界面都动画展现出来(广播用户使用websocket消息)

发布于:2天以前  |  13次阅读  |  详细内容 »

探索 Serverless 中的前端开发模式

最近关于 Serverless 的讨论越来越多。看似与前端关系不大的 Serverless,其实早已和前端有了渊源,并且将对前端开发模式产生变革性的影响。本文主要就根据个人理解和总结,从前端开发模式的演进、基于 Serverless 的前端开发案例以及 Serverless 开发最佳实践等方面,与大家探讨 Serverless 中的前端开发模式。本人也有幸在 QCon2019 分享了这一主题。

发布于:2天以前  |  14次阅读  |  详细内容 »

前端需要了解的9种设计模式

设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论,而不是现成的代码,当然每种设计模式都有每种语言中的具体实现方式。学习设计模式更多的是理解各种模式的内在思想和解决的问题,毕竟这是前人无数经验总结成的最佳实践,而代码实现则是对加深理解的辅助。

发布于:2天以前  |  15次阅读  |  详细内容 »

为什么你的网页需要 CSP?

内容安全策略(CSP)是一个 HTTP Header,CSP 通过告诉浏览器一系列规则,严格规定页面中哪些资源允许有哪些来源, 不在指定范围内的统统拒绝。

发布于:3天以前  |  15次阅读  |  详细内容 »

10 种跨域解决方案(附终极方案)

嗯。又来了,又说到跨域了,这是一个老生常谈的话题,以前我觉得这种基础文章没有什么好写的,会想着你去了解底层啊,不是很简单吗。但是最近在开发一个 「vscode 插件」 发现,当你刚入门一样东西的时候,你不会想这么多,因为你对他不熟悉,当你遇到不会的东西,你就是想先找到解决方案,然后通过这个解决方案再去深入理解。

发布于:3天以前  |  24次阅读  |  详细内容 »

移动 Web 最佳实践(干货长文,建议收藏)

笔者在公司用 web 技术开发移动端应用已经有一年多的时间了,开始主要以 vue 技术栈配合 native 为主,目前演进成 vue + react native 技术架构,vue 主要负责开发 OA 业务,比如报销、出差、crm 等等,react native 主要负责即时通信部分,是在 mattermost-mobile的基础上修改的(mattermost 是一个开源的即时通讯方案)。

发布于:3天以前  |  14次阅读  |  详细内容 »

不可错过的实用前端工具

给大家整理了 25 个前端相关的学习网站和一些靠谱的小工具,包括一些小游戏、教程、社区网站和博客,以及一些资源网站,希望可以帮助到大家!

发布于:3天以前  |  18次阅读  |  详细内容 »

理解 WebView

我们通常使用 Chrome, Firefox, Safari, Internet Explorer 和 Edge 等浏览器来浏览网页。你也许正在使用其中一种浏览器阅读本文!虽然浏览器对于访问互联网内容的任务来说非常流行,它们还有一些我们从未过多关注过的竞争对手。这些竞争对手以 WebView 的形式被我们所熟知。这片文章将讲解 WebView 的神秘之处以及为什么它这么棒。

发布于:3天以前  |  15次阅读  |  详细内容 »

Facebook 前端技术栈重构分享

当我们考虑如何构建一个新的网络应用—一个为现代浏览器设计的、具有用户对Facebook(我们已知的)所有期望的功能,我们现有的技术栈无法支持我们所需要的类似于桌面应用的感觉和性能。

发布于:3天以前  |  17次阅读  |  详细内容 »

最多阅读

为Electron程序添加运行时日志 11月以前  |  4344次阅读
初探 React 组件 1年以前  |  2099次阅读
wordpress标签页的制作 1年以前  |  1965次阅读
500行PHP代码搞定富文本安全过滤 1年以前  |  1892次阅读
js动态创建类和实例化 1年以前  |  1872次阅读
Node.js下通过配置host访问URL 1年以前  |  1858次阅读
22个HTML5的初级技巧 1年以前  |  1831次阅读
使用 SRI 增强 localStorage 代码安全 1年以前  |  1831次阅读
浅谈浏览器的原生拖拽事件 1年以前  |  1807次阅读
CSS清除浮动 1年以前  |  1789次阅读
第三版主题上线 1年以前  |  1787次阅读
2014年度总结 1年以前  |  1737次阅读
【译】V8 团队眼中的 ES6、ES7及未来 1年以前  |  1735次阅读
利用服务器返回header来传输数据 1年以前  |  1718次阅读
获取元素的计算的样式 1年以前  |  1714次阅读