深入浅出理解react虚拟DOM

作者: katherine_a120 | 来源:发表于2019-05-26 22:45 被阅读22次

    说实话React源码真的很难读,很枯燥。React虚拟DOM的特性很多人都知道,但相信很多人并没那么清楚它究竟是什么,React的虚拟 DOM和 Diff算法是 React的非常重要的核心特性,这部分源码也非常复杂,理解这部分知识的原理对更深入的掌握 React是非常必要的

    在原生的 JavaScript代码中,前端工程师直接对 DOM进行创建和更改,而 DOM元素通过我们监听的事件和我们的应用程序进行通讯。

    而 React会先将你的代码转换成一个 JavaScript对象,然后这个 JavaScript对象再转换成真实 DOM。这个 JavaScript对象就是所谓的虚拟 DOM。

    比如下面一段 html代码:

    <div class="title">

          <span>Hello ConardLi</span>

          <ul>

            <li>苹果</li>

            <li>橘子</li>

          </ul>

    </div>

    在 React可能存储为这样的 JS代码:

    const VitrualDom = {

      type: 'div',

      props: { class: 'title' },

      children: [

        {

          type: 'span',

          children: 'Hello ConardLi'

        },

        {

          type: 'ul',

          children: [

            { type: 'ul', children: '苹果' },

            { type: 'ul', children: '橘子' }

          ]

        }

      ]

    }

    当我们需要创建或更新元素时, React首先会让这个 VitrualDom对象进行创建和更改,然后再将 VitrualDom对象渲染成真实 DOM;

    当我们需要对 DOM进行事件监听时,首先对 VitrualDom进行事件监听, VitrualDom会代理原生的 DOM事件从而做出响应。

    为何使用虚拟DOM

    React为何采用 VitrualDom这种方案呢?

    1.提高开发效率

    使用 JavaScript,我们在编写应用程序时的关注点在于如何更新 DOM,而且操作dom代价很大。

    使用 React,只需要告诉 React我们想让视图处于什么状态, React则通过 VitrualDom确保DOM与该状态相匹配。你不必自己去完成属性操作、事件处理、 DOM更新, React会完成这一切。

    这让我们更关注我们的业务逻辑而非 DOM操作,这一点即可大大提升我们的开发效率。

    2.关于提升性能

    关于VitrualDom可以提升性能,在很多技术文章中都有提到,然而也很多人提出这一说法实际上是很片面的。

    直接操作 DOM是非常耗费性能的,这一点毋庸置疑。但是 React使用 VitrualDom也是无法避免操作 DOM的。

    如果是首次渲染, VitrualDom不具有任何优势,甚至它要进行更多的计算,消耗更多的内存。

    VitrualDom的优势在于 React的 Diff算法和批处理策略, React在页面更新之前,提前计算好了如何进行更新和渲染 DOM。实际上,这个计算过程我们在直接操作 DOM时,也是可以自己判断和实现的,但是一定会耗费非常多的精力和时间,而且往往我们自己做的是不如 React好的。所以,在这个过程中 React帮助我们"提升了性能"。

    所以,我更倾向于这个说法: “VitrualDom帮助我们提高了开发效率,在重复渲染时它帮助我们计算如何更高效的更新,而不是它比 DOM操作更快。”

    跨浏览器兼容

    React基于 VitrualDom自己实现了一套自己的事件机制,自己模拟了事件冒泡和捕获的过程,采用了事件代理,批量更新等方法,抹平了各个浏览器的事件兼容性问题

    跨平台兼容

    VitrualDom为 React带来了跨平台渲染的能力。以 ReactNative为例子。React根据 VitrualDom画出相应平台的 ui层,只不过不同平台画的姿势不同罢了

    虚拟DOM实现原理

    JSX和createElement

    我们在实现一个 React组件时可以选择两种编码方式,第一种是使用 JSX编写:

    class Hello extends Component {

      render() {

        return <div>Hello ConardLi</div>;

      }

    }

    第二种是直接使用 React.createElement编写:

    class Hello extends Component {

      render() {

        return React.createElement('div', null, `Hello ConardLi`);

      }

    }

    实际上,上面两种写法是等价的, JSX只是为 React.createElement(component,props,...children)方法提供的语法糖。也就是说所有的 JSX代码最后都会转换成 React.createElement(...), Babel帮助我们完成了这个转换的过程。

    如下面的 JSX

    <div>

      <img src="avatar.png" className="profile" />

      <Hello />

    </div>;

    将会被 Babel转换为

    React.createElement("div", null, React.createElement("img", {

      src: "avatar.png",

      className: "profile"

    }), React.createElement(Hello, null));

    注意, babel在编译时会判断 JSX中组件的首字母,当首字母为小写时,其被认定为原生 DOM标签, createElement的第一个变量被编译为字符串;当首字母为大写时,其被认定为自定义组件, createElement的第一个变量被编译为对象;

    另外,由于 JSX提前要被 Babel编译,所以 JSX是不能在运行时动态选择类型的,比如下面的代码:

    function Story(props) {

      // Wrong! JSX type can't be an expression.

      return <components[props.storyType] story={props.story} />;

    }

    需要变成下面的写法:

    function Story(props) {

      // Correct! JSX type can be a capitalized variable.

      const SpecificStory = components[props.storyType];

      return <SpecificStory story={props.story} />;

    }

    所以,使用 JSX你需要安装 Babel插件 babel-plugin-transform-react-jsx

    {

        "plugins": [

            ["transform-react-jsx", {

                "pragma": "React.createElement"

            }]

        ]

    }

    创建虚拟DOM

    下面我们来看看虚拟 DOM的真实模样,将下面的 JSX代码在控制台打印出来:

    <div className="title">

          <span>Hello ConardLi</span>

          <ul>

            <li>苹果</li>

            <li>橘子</li>

          </ul>

    </div>

    ReactElement

    ReactElement将传入的几个属性进行组合,并返回。

    type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或 class)

    key:组件的唯一标识,用于 Diff算法,下面会详细介绍

    ref:用于访问原生 dom节点

    props:传入组件的 props

    owner:当前正在构建的 Component所属的Component

    $$typeof:一个我们不常见到的属性,它被赋值为REACT_ELEMENT_TYPE:

    var REACT_ELEMENT_TYPE =

      (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||

      0xeac7;

    可见,$$typeof是一个Symbol类型的变量,这个变量可以防止XSS。

    虚拟DOM转换为真实DOM

    当任何一个组件使用setState时,React都会认为该组件变‘脏’了,于是触发组件本身的重新渲染,同时因其始终同时维护两套虚拟的DOM,其中一套是更新后的虚拟DOM;另一套是前一个状态的虚拟DOM,通过对这两套虚拟DOM运行diff算法,找到需要变化是最小单元集,然后把这个最小单元集应用在真实的DOM中。

    而这个diff算法是将两棵DOM树之间的diff复杂度缩减到线性函数级别O(N)

    而React的这个神奇的Diff算法是基于两大假设:

    1.DOM节点的跨层级移动忽略不计

    2.拥有相同类的两个组件生产相似的树形结构,拥有不同类的两个组件生成不同的树形结构。

    根据这些假设,React采取的策略如下

    1.React对组件进行分层比较,两棵树只会对同一层级的节点进行比较。

    2.当对同一层级的节点进行比较时,对于不同的组件类型,直接将整个组件替换为新类型组件

    相关文章

      网友评论

        本文标题:深入浅出理解react虚拟DOM

        本文链接:https://www.haomeiwen.com/subject/gzrqtctx.html