间接进入主题。

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

// 保留的propsconst RESERVED_PROPS = {  key: true,  ref: true,  __self: true,  __source: true,};// react元素的类型const REACT_ELEMENT_TYPE = Symbol.for("react.element");// 判断是不是无效的reffunction hasValidRef(config) {  return config.ref !== undefined;}// 判断是不是无效的keyfunction 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就是一个对象。

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