react虚拟dom

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

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

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

比如下面一段 html代码:

      Hello ConardLi

     

           

  • 苹果
  •        

  • 橘子
  •      

在 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

Hello ConardLi
;

  }

}

第二种是直接使用 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

 

 

;

将会被 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 ;

}

需要变成下面的写法:

function Story(props) {

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

  const SpecificStory = components[props.storyType];

  return ;

}

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

{

    "plugins": [

        ["transform-react-jsx", {

            "pragma": "React.createElement"

        }]

    ]

}

创建虚拟DOM

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

      Hello ConardLi

     

           

  • 苹果
  •        

  • 橘子
  •      

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.当对同一层级的节点进行比较时,对于不同的组件类型,直接将整个组件替换为新类型组件

作者:katherine_a120

链接:https://www.jianshu.com/p/e0a3ac85db5c

来源:

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(react虚拟dom)