关于react.js:React系列-jsx转化为虚拟DOM

间接进入主题。

const element = <div>copyer</div>

下面的标签语法既不是字符串也是HTML,被称为JSX,JavaScript的语法扩大。

React中举荐应用JSX语法。(当然,其余框架也是能够应用的,比方当初的vue3中,也是反对JSX语法的)。

为什么要应用JSX语法?

React 认为渲染逻辑实质上与其余 UI 逻辑外在耦合,比方,在 UI 中须要绑定处理事件、在某些时刻状态发生变化时须要告诉到 UI,以及须要在 UI 中展现筹备好的数据。(官网原话)

那么应用JSX语法,react外部是怎么解析的呢?转化为实在的DOM。

React 16版本

jsx语法通过babel转化为React.createElement函数调用,生成虚构对象。

Babel在线试一试

JSX模式

function App() {
  return <h1 className='ppt'>Hello World</h1>;
}

React.createElement函数模式

"use strict";

function App() {
  return /*#__PURE__*/React.createElement("h1", {
    className: "ppt"
  }, "Hello World");
}

React.createElement参数剖析

/**
 *
 * @param {*} type 元素的类型
 * @param {*} config 配置对象
 * @param {*} children 第一个儿子,如果有多个,顺次放在前面
 */
function createElement(type, config, children) {}

React 17版本

React 17 提供了一个全新的,重构过的 JSX 转换的版本。jsx语法不再化为 React.createElement函数,而是外部通过 react/jsx-runtimejsx函数生成虚构对象。

官网解释JSX transform

jsx 新的转化,也是通过 babel实现的。两种形式

@babel/plugin-transform-react-jsx

// If you're using @babel/plugin-transform-react-jsx
{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "runtime": "automatic"
    }]
  ]
}

@babel/preset-react

// If you are using @babel/preset-react
{
  "presets": [
    ["@babel/preset-react", {
      "runtime": "automatic"
    }]
  ]

示例:

jsx模式

function App() {
  return <h1 className='ppt'>Hello World</h1>;
}

new JSX transform模式

// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1',{ className: 'ppt', children: 'Hello world' });
}

比照

createElement函数与jsx函数的区别

参数的不同

第一个参数,都是元素的类型

  • createElement函数 第二个参数:元素的配置对象; 第三个参数,示意它的第一个子节点,如果有多个子节点,就顺次的从四个参数往下方。(简略的来说,从第三个参数开始,都是元素的子节点)
  • jsx函数,第二个参数就是一个对象,外面蕴含着元素的配置对象({className: 'aa', children: []})。children属性就是示意该元素的子节点。如果只有一个,就是react元素;如果有多个,就是一个数组,外面寄存着所有的子节点。
新的jsx transform的益处
  • 在React16版本,每个组件都必须导入React,不然就会报错。在新的jsx transform中不必导入。(因为新的 JSX 转换会主动引入必要的 react/jsx-runtime 函数,因而当你应用 JSX 时,将无需再引入 React。)
  • JSX 的编译输入可能会稍微改善 bundle 的大小
createElement函数没有废除

只管新的 jsx 编译曾经进去,然而并没有废除 createElement函数。

如果想要应用js创立元素,还是要应用 createElement。

// 由编译器引入(禁止本人引入!)
import {jsx as _jsx} from 'react/jsx-runtime';

源码剖析

createElement函数源码

packages/react/src/ReactElement.js

// 保留的props
const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};

// react元素的类型
const REACT_ELEMENT_TYPE = Symbol.for("react.element");

// 判断是不是无效的ref
function hasValidRef(config) {
  return config.ref !== undefined;
}

// 判断是不是无效的key
function hasValidKey(config) {
  return config.key !== undefined;
}

const ReactCurrentOwner = {
  current: null, // (null: null | Fiber)
};

// react元素对象(虚构节点)
const ReactElement = function (type, key, ref, self, source, owner, props) {
  const element = {
    // react元素的惟一标识
    $$typeof: REACT_ELEMENT_TYPE,

    // react元素的属性
    type: type,
    key: key,
    ref: ref,
    props: props,

    // react元素的创建者
    _owner: owner,
  };
  return element;
};

/**
 *
 * @param {*} type 元素的类型
 * @param {*} config 配置对象
 * @param {*} children 第一个儿子,如果有多个,顺次放在前面
 */
function createElement(type, config, children) {
  let propName;

  // 定义props对象
  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;
    // 保留属性,增加到一个新的对象中
    for (propName in config) {
      // 判断 config的属性是不是保留属性,不是保留属性,就增加到对象中
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName) // reserved_props
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // 把children也增加到新的对象中
  // 依据函数的参数个数,判断children是否存在
  const childrenLength = arguments.length - 2;
  // 如果为1,表明有一个children,就是间接赋值给新对象的children属性
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    // 如果有多个儿子,就放到一个数组中,children的value值就是该数组
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      // 去掉后面的两个参数
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // 判断该元素有不有type和默认的props(针对类组件)
  if (type && 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
  );
}
jsx函数源码

packages/react/src/ReactJSXElement.js

export function jsx(type, config, maybeKey) {   
  let propName;

  const props = {};

  let key = null;
  let ref = null;
  
  // maybeKey 就是解决一种key写法的景象
  // <div {...props} key='hi'></div> 或则 <div key='hi' {...props}></div>
  // props中可能蕴含key,依照第二种写法,props中的key就会本来节点上的key(同一个对象上)
  // jsxDEV冀望的是 <div {...props} key='hi'></div>,确定本节点上的key
  if (maybeKey !== undefined) {
    key = '' + maybeKey;
  }

  if (hasValidKey(config)) {
    key = '' + config.key;
  }

  if (hasValidRef(config)) {
    ref = config.ref;
  }
  
    
  // 过滤掉: 预留的props属性
  for (propName in config) {
    if (
      hasOwnProperty.call(config, propName) &&
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      props[propName] = config[propName];
    }
  }

  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  return ReactElement(
    type,
    key,
    ref,
    undefined,
    undefined,
    ReactCurrentOwner.current,
    props,
  );
}

发现没有,其实两个函数的源码基本上差不多,就是在解决子节点的有点不同。createElement是独自解决的,jsx函数基本就没有解决,间接赋值(因为 children并不预保留属性,所以会间接赋值给props)

总结

jsx被babel解析成虚构对象

jsx如果蕴含子节点,子节点也会被解析,顺次上来,就会造成一个深层次的对象,这个对象被称为虚构DOM。所以虚构DOM就是一个对象。

如果下面写的有误,请指教~~~

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理