React系列( jsx转化为虚拟DOM)

直接进入主题。

const element = copyer

上面的标签语法既不是字符串也是HTML,被称为JSX,JavaScript的语法扩展。
React中推荐使用JSX语法。(当然,其他框架也是可以使用的,比如现在的vue3中,也是支持JSX语法的)。
为什么要使用JSX语法?
React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。(官方原话)
那么使用JSX语法,react内部是怎么解析的呢?转化为真实的DOM。
React 16版本 【React系列( jsx转化为虚拟DOM)】jsx语法通过babel转化为React.createElement函数调用,生成虚拟对象。
Babel在线试一试
JSX形式
function App() { return Hello World; }

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 Hello World; }

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写法的现象 //或则 // props中可能包含key,按照第二种写法,props中的key就会原本节点上的key(同一个对象上) // jsxDEV期望的是 ,确定本节点上的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就是一个对象。
如果上面写的有误,请指教~~~

    推荐阅读