「react源码解析」谈JSX与DOM的本质关系,以及虚拟dom的产生实现

542 阅读3分钟

前言

OK,这里是分享与爱的大鹅鹅鹅,今天我们瞬间移动,来到react源码解析栏目,我们来学习JSX与DOM的本质关系,以及虚拟dom的产生实现。消息就是这样,看看能学到点啥知识。

JSX代码如何转换成DOM?

JSX的本质是什么?它和js之间的关系?

JSX的本质是js的语法扩展,JSX充分具备js的能力。

为何要用JSX?

因为JSX的本质是React.createElement,JSX语法糖允许开发者使用我们最熟悉的类HTML标签来创建虚拟DOM,避免手动编写React.createElement代码,降低学习成本的同时,也提升了研发效率与研发体验

JSX背后的功能模块是什么?这个功能模块做了什么事?

image.png

JSX语法是通过Babel编译后,在js中生效的。

Babel将JSX编译成React.createElement(),而React.createElement()将返回一个叫做“React Elememt”的js对象。

JSX本质是React.createElement这个js调用的语法糖,这个完美的呼应了React官方给出的“JSX充分具备js的能力”这句话。

虚拟dom如何产生的?

先说结论,调用React.createElement方法返回ReactElement对象实例,ReactElement对象实例是以js对象存在,表示为DOM的描述,也就是“虚拟DOM”。

React.createElement()源码解析

方法入参格式:

function createElement(type, config, children)

三个参数基本含义:

  • type(节点类型) ;
  • config(元素的属性);
  • children(子元素可能有多个,从第三个入参开始往后,传入的参数都是children);

从源码可以看到,createElement 函数主要做了三件事:

  • 处理 config;
  • 构造 props(3步:提取config,处理子元素、处理默认值);
  • 返回 ReactElement函数的调用;

具体来说:

  1. 首先声明 propName、props、key、ref、self、source 等变量;
  2. 依次对 ref、key、self 和 source 属性赋值,其中 key 会强制转换为字符串;
  3. 开始构造 props,首先遍历 config 对象,筛选出可以提取到 props 中的属性;
  4. 处理子元素,生成 props.children,这里分为一个和多个子元素两种情况;
  5. 处理 defaultProps,如果有默认属性值则覆盖;
  6. 传入处理好的参数,返回一个 ReactElement 函数的调用;

这里来看下createElement函数的源码实现:

export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      // 筛选出可以提进 props 对象里的属性
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  const childrenLength = arguments.length - 2;
  // 如果抛去type和config,就只剩一个参数,一般意味着文本节点出现了
  if (childrenLength === 1) {
    props.children = children;
    // 处理嵌套多个子子元素的情况
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
    // 把子元素推进数组里
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type &amp;&amp; type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

通过阅读源码,你就会发现createElement函数的“工作量”并没有多大,没有十分复杂的逻辑和算法,每一个步骤几乎都是在格式化数据

说直白点,createElement就是开发者和ReactElement调用之间的一个“转换器”。

接着再来说ReactElement函数。

ReactElement函数源码解析

ReactElement函数主要的工作就是“创建”,说白点就是ReactElement把传入的参数按照一定的规范,“组装”进了一个 ReactElement对象并将其返回给了React.createElement

ReactElement 的源码如下:

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };
  
  if(__DEV__) {
    // do something...
  }

  return element;
};

如上源码,ReactElement对象实例,本质是以js对象形式存在,表示为DOM的描述,也就是“虚拟DOM”。

这里我们可以验证,这里我们写入如下示例,打印出JSX的部分:

const APP = <div calssName='App'>
<h1 calssName='title'>I am the title</h1>
<p calssName='content'>I am the content</p>
</div>

console.log(App)

控制台输出如下:

image.png

以上就是“虚拟DOM”的描述。

虚拟 DOM到真实 DOM

而如何根据虚拟 DOM是,构建渲染到页面上的真实 DOM ,这个需要ReactDOM.render方法来实现:

const virtualElement = React.createElement('div', null);
const rootElement = document.getElementById("root");
ReactDOM.render(virtualElement, rootElement);

结语

以上就是本篇的全部内容,如果你觉得此文对你有一点点帮助,不妨点个赞,一键三连,鼓励一下大鹅鹅鹅,O(∩_∩)O哈哈~