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

3次阅读

共计 4861 个字符,预计需要花费 13 分钟才能阅读完成。

间接进入主题。

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 就是一个对象。

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

正文完
 0