JSX 是如何一步步变成DOM元素的?

时间:2021-1-8 作者:admin

 在React日常开发中,我们习惯了用JSX描述React组件的内容。关于JSX本身的语法,大家都不会陌生。我来创建一个简单的react组件,用来唤醒大家脑海中的知识。

import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h1 className="title">I am the title</h1>
        <p className="content">I am the content</p>
      </div>
    );
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

看到这段代码,大家想想JSX的本质是什么,它和JS有什么关系呢?

 1. JSX 的本质是JavaScript语法的扩展,既然是语法的扩展那么就不会被JS识别。自然就需要一个编译工具来编译成JavaScript代码。这个工具就是 babel。

引用官方的话:Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

<div className="app">组件</div>     
编译=> React.createElement("div",{className:'app'},"组件") 

大家看到JSX会被编译成React.createElement()函数的调用。那么可以这么说,JSX 就是 React.createElement 的语法糖了。

React.createElement(type,config,children) 

/**
 101\. React的创建元素方法
 */
export function createElement(type, config, children) {
  // propName 变量用于储存后面需要用到的元素属性
  let propName; 
  // props 变量用于储存元素属性的键值对集合
  const props = {}; 
  // key、ref、self、source 均为 React 元素的属性,此处不必深究
  let key = null;
  let ref = null; 
  let self = null; 
  let source = null; 
  // config 对象中存储的是元素的属性
  if (config != null) { 
    // 进来之后做的第一件事,是依次对 ref、key、self 和 source 属性赋值
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    // 此处将 key 值字符串化
    if (hasValidKey(config)) {
      key = '' + config.key; 
    }
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 接着就是要把 config 里面的属性都一个一个挪到 props 这个之前声明好的对象里面
    for (propName in config) {
      if (
        // 筛选出可以提进 props 对象里的属性
        hasOwnProperty.call(config, propName) &&  /
        !RESERVED_PROPS.hasOwnProperty(propName) 
      ) {
        props[propName] = config[propName]; 
      }
    }
  }
  // childrenLength 指的是当前元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度
  const childrenLength = arguments.length - 2; 
  // 如果抛去type和config,就只剩下一个参数,一般意味着文本节点出现了
  if (childrenLength === 1) { 
    // 直接把这个参数的值赋给props.children
    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
    props.children = childArray; 
  } 
  // 处理 defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) { 
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 最后返回一个调用ReactElement执行方法,并传入刚才处理过的参数
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

createElement 有 3 个入参,这 3 个入参囊括了 React 创建一个元素所需要知道的全部信息。

  • type:用于标识节点的类型。它可以是类似“h1”“div”这样的标准 HTML 标签字符串,也可以是 React 组件类型或 React fragment 类型。

  • config:以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中。 

  • children:以对象形式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的“子节点”“子元素”

createElement 函数体拆解

前面你已经阅读过 createElement 源码细化到每一行的解读,这里和探讨creatElement在逻辑层面的任务流转,

React.createElement => 二次处理key,ref,self,source四个属性值 => 遍历config,筛选可以提进props里的属性 => 提取子元素,推入childArray(props.children)数组中 => 格式化defaultProps => 结构以上数据作为入参,发起ReactElement调用。

createElement 中并没有复杂的算法和真实DOM的逻辑,它的每一个步骤机会都在格式化数据。直白点,createElement 就像是开发者和ReactElement 调用之间的一个转换器,一个数据处理层。

虚拟DOM

上面分析,createElement 执行到最后会return一个针对ReactElement的调用.

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement
    $$typeof: REACT_ELEMENT_TYPE,
    // 内置属性赋值
    type: type,
    key: key,
    ref: ref,
    props: props,
    // 记录创造该元素的组件
    _owner: owner,
  };
  // 
  if (__DEV__) {
    // 这里是一些针对 __DEV__ 环境下的处理,对于大家理解主要逻辑意义不大,此处我直接省略掉,以免混淆视听
  }
  return element;
};

ReactElement 的代码出入意料的短,从逻辑上看出,ReactElement 只做了一件事情,”创建”,说的更加精确一点, “组装”,reactElement把传入的参数组装成一个Element对象,然后整体返回出去给了React.createElement, React.createElement 又把它交到开发者手中。

const AppJSX = (<div className="App">
  组建
</div>)
console.log(AppJSX)

打印出来是一个标准的ReactElement对象实例。本质上是以JavaScript对象形式存在的DOM描述,也就是我们常常提起的虚拟DOM。

虚拟DOM,那就意味着和渲染到页面的真实DOM之间还有一些距离,这个距离,就是用React.render 来完成。

总结 :

jsx 通过babel 转换成React.createElement的调用 ,createElement对数据进行格式化 ,然后在调用ReactElement变成ELement对象,也就是虚拟DOM节点,最后在通过ReactDOM.render()映射到真实的DOM元素中。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。