Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >babel如何解析jsx

babel如何解析jsx

原创
作者头像
flyzz177
发布于 2022-12-02 07:59:59
发布于 2022-12-02 07:59:59
66600
代码可运行
举报
运行总次数:0
代码可运行

同作为MVVM框架,React相比于Vue来讲,上手更需要JavaScript功底深厚一些,本系列将阅读React相关源码,从jsx -> VDom -> RDOM等一些列的过程,将会在本系列中一一讲解

工欲善其事,必先利其器

经过多年的发展,React已经更新了大版本16、17、18,本系列主要讲的是 version:17.0.2,在讲这个版本之前,我们先看一看在babel的编译下,每个大版本之下会有什么样的变化。

jsx

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<div className='box'>
  <h1 className='title' style={{'color':'red'}}>React源码解析</h1>
  <ul>
    <li>第一章</li>
    <li>第二章</li>
    <li>第三章</li>
    <li>第四章</li>
  </ul>
</div>

v16.x及以前版本

在这里插入图片描述
在这里插入图片描述

v17及之后版本

在这里插入图片描述
在这里插入图片描述

所以各位看到了,在v16及以前我们babel进行jsx解析编译的是根据@babel/babel-preset-react-app解析成React.createElement进行包裹的,而v17以及之后的版本,官网早就说明,对jsx的转换用react/jsx-runtime,而不再依赖React.createElement了,看到这里我想各位对不同版本的babel解析jsx已经有了眉目了,早已经迫不及待想去看看jsx-runtime和createElement到底是如何玩的,那么进入源码

在babel解析后的v17产物中我们可以看得到 var _jsxRuntime = require("react/jsx-runtime");那么我们追本溯源可以找到在packages/react/src/jsx/ReactJSX.js里面的jsxs是怎么来的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// packages/react/src/jsx/ReactJSX.js
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
import {
  jsxWithValidationStatic,
  jsxWithValidationDynamic,
  jsxWithValidation,
} from './ReactJSXElementValidator';
import {jsx as jsxProd} from './ReactJSXElement';
const jsx = __DEV__ ? jsxWithValidationDynamic : jsxProd;
const jsxs = __DEV__ ? jsxWithValidationStatic : jsxProd;
const jsxDEV = __DEV__ ? jsxWithValidation : undefined;

export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs, jsxDEV};

在非dev环境下我们继续去找jsProd

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export function jsx(type, config, maybeKey) {
  let propName;

  //标签上的属性集合
  const props = {};

  //单独处理key ref
  let key = null;
  let ref = null;

  if (maybeKey !== undefined) {
    key = '' + maybeKey;
  }

  if (hasValidKey(config)) {
    // 处理合法的key
    key = '' + config.key;
  }

  if (hasValidRef(config)) {
    // 处理合法的ref
    ref = config.ref;
  }

  // 把属性加到props中
  for (propName in config) {
    if (
      hasOwnProperty.call(config, propName) &&
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      props[propName] = config[propName];
    }
  }

  // 处理默认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,
      undefined,
      undefined,
      ReactCurrentOwner.current,
      props
  )
}

ReactElement

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 表示是否为ReactElement
    $$typeof: REACT_ELEMENT_TYPE,

    // 元素自身属性
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  if (__DEV__) {
    element._store = {};

    // 开发环境下将_store、_self、_source属性变为不可枚举
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });

    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });

    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    // 冻结props、element防止被手动修改
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

这上面便是v17及之后版本的jsx-runtime所做的事情。那么这里再去看一下v16中的createElement所做的事情吧。

React.createElement

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// packages/react/src/ReactElement.js
export function createElement(type, config, children) {
  let propName;

  // 记录标签上的属性集合
  const props = {};

  //单独处理key ref
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  // 当config部位null的时候,表示标签上有属性,加到props里面去
  if (config != null) {
    // 合法的ref才做处理
    if (hasValidRef(config)) {
      ref = config.ref;

      if (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    if (hasValidKey(config)) {
      // 有合法的key才做处理
      key = '' + config.key;
    }

    // 记录信息用于debug
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;

    // 处理self,source,key,ref以外的属性,加入props中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // 处理子节点
  const childrenLength = arguments.length - 2;
  // 单标签子节点
  if (childrenLength === 1) {
    props.children = children;
    //嵌套子节点
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    //开发环境冻结,childArray防止被修改
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // 处理默认props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    // dev环境下,key 与 ref不挂到props中去
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 调用返回
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

相关参考视频讲解:进入学习

由React.createElement源码得知,他做了如下事情

  • 解析config参数中是否有合法的 keyref属性,并处理,并将其他的属性挂到props上。
  • 解析函数的第三参数,并分情况将第三参数挂到props.children上。
  • 对默认props进行处理,如果存在该属性则直接挂载到props上,不存在则要添加上。
  • 开发环境下将 _store、_self、_source 设置为不可枚举状态,为后期的diff比较作优化,提高比较性能。
  • type、key、ref、props等属性通过调用ReactElement函数创建虚拟dom。

ReactElement

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  if (__DEV__) {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

仔细瞧一瞧,这个其实跟jsxs调用的ReactElement实现的差不多的功能,但是为什么要写两遍?仔细看来,在两个版本的ReactElement中,传入的参数不一致,在开发环境下,分别对其做劫持不可枚举状态,仅此而已

React.Component

写惯了hooks组件,但是Class组件也别忘了哟,因为在React17里面Class组件也是没有被抹去的,所以既然是源码解析,那么我们也要来看一看这个Component到底干了啥。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// packages/react/src/ReactBaseClasses.js
function Component(props, context, updater) {
  // 接受各种参数,挂到this上
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // updater ?? 
  this.updater = updater || ReactNoopUpdateQueue;
}

// 原型上挂载了isReactComponent用来区分函数组件与类组件
Component.prototype.isReactComponent = {};

//原型上挂载了setState方法用来触发更新
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  // 调用updater上的enqueueSetState方法???
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

// 原型上挂载了强制更新的方法
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

从源码上可以得知,React.Component 主要做了以下几件事情:

  • props, context, updater 挂载到this 上,props,context一目了然,后面的updater位触发器,上面挂了很多方法,我们后面再谈。
  • Component 原型链上添加 isReactComponent 对象,用于区分函数组件还是类组件。
  • Component 原型链上添加 setState 方法,触发更新。
  • Component 原型链上添加 forceUpdate 方法,强制更新。

总结

不管是类组件还是函数组件,最终我们写的jsx都被babel转化成了可识别的元素,其中我们也看了ReactElement,createElement,Component等内部实现,了解到了作为ReactElement他是怎么被创建的,但是远远没有完,因为我们知道我们在写React的时候,会在后面带上一个ReactDOM.render(<Element/>, 'root'),没错我们下一章节就要去探索一下ReactDOM.render方法了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
react源码分析:babel如何解析jsx_2023-02-27
同作为MVVM框架,React相比于Vue来讲,上手更需要JavaScript功底深厚一些,本系列将阅读React相关源码,从jsx -> VDom -> RDOM等一些列的过程,将会在本系列中一一讲解
flyzz177
2023/02/27
2760
React源码分析1-jsx转换及React.createElement
我们从 react 应用的入口开始对源码进行分析,创建一个简单的 hello, world 应用:
goClient1992
2022/10/06
9370
React源码解析之React.createElement()和ReactElement()
一、JSX语法转换到Js语法 从 JSX 转换到 JS 会用到React.createElement(),所以先熟悉下 JSX 到 JS 的转换。
进击的小进进
2019/09/05
1.3K0
React源码解析之React.createElement()和ReactElement()
react的jsx和React.createElement是什么关系?面试常问
在React17之前,我们写React代码的时候都会去引入React,并且自己的代码中没有用到,这是为什么呢?
beifeng1996
2022/10/03
5420
ReactJS分析之入口函数render
前言   在使用React进行构建应用时,我们总会有一个步骤将组建或者虚拟DOM元素渲染到真实的DOM上,将任务交给浏览器,进而进行layout和paint等步骤,这个函数就是React.render()。首先看下该函数的接口定义: ReactComponent render( ReactElement element, DOMElement container, [function callback] ) 接收2-3个参数,并返回ReactComponent类型的对象,当组件被添加到DOM中后,执
欲休
2018/03/15
1.1K0
第一篇:JSX 代码是如何“摇身一变”成为 DOM 的?
时下虽然接入 JSX 语法的框架越来越多,但与之缘分最深的毫无疑问仍然是 React。2013 年,当 React 带着 JSX 横空出世时,社区曾对 JSX 有过不少的争议,但如今,越来越多的人面对 JSX 都要说上一句“真香”!本课时我们就来一起认识下这个“真香”的 JSX,聊一聊“JSX 代码是如何‘摇身一变’成为 DOM 的”。
越陌度阡
2021/10/19
1.5K0
React源码学习入门(二)React的render究竟返回的是什么?
可以很明显的看出来,render返回值是一个ReactNode,而ReactNode可以是很多类型,其中最重要常见的类型是ReactElement。
孟健
2022/09/21
7170
React 源码深度解读(一):首次DOM元素渲染 - Part 1
React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深。阅读 React 源码是一个非常艰辛的过程,在学习过程中给我帮助最大的就是这个系列文章。作者对代码的调用关系梳理得非常清楚,而且还有配图帮助理解,非常值得一读。站在巨人的肩膀之上,我尝试再加入自己的理解,希望对有志于学习 React 源码的读者带来一点启发。
Dickensl
2022/06/14
6180
React 源码深度解读(一):首次DOM元素渲染 - Part 1
【React源码解读】- 组件的实现
react使用也有一段时间了,大家对这个框架褒奖有加,但是它究竟好在哪里呢? 让我们结合它的源码,探究一二!(当前源码为react16,读者要对react有一定的了解)
用户2356368
2024/02/04
1220
【React源码解读】- 组件的实现
【React源码解读】- 组件的实现
react使用也有一段时间了,大家对这个框架褒奖有加,但是它究竟好在哪里呢? 让我们结合它的源码,探究一二!(当前源码为react16,读者要对react有一定的了解)
用户2356368
2019/04/03
7040
【React源码解读】- 组件的实现
react源码解析5.jsx&核心api
一句话概括就是,用js对象表示dom信息和结构,更新时重新渲染更新后的对象对应的dom,这个对象就是React.createElement()的返回结果
zz1998
2021/11/30
4190
react源码解析--jsx&核心api
一句话概括就是,用js对象表示dom信息和结构,更新时重新渲染更新后的对象对应的dom,这个对象就是React.createElement()的返回结果
长腿程序员165858
2022/12/12
3660
react源码解析5.jsx&核心api
一句话概括就是,用js对象表示dom信息和结构,更新时重新渲染更新后的对象对应的dom,这个对象就是React.createElement()的返回结果
用户9002110
2021/12/04
4080
react源码解析5.jsx&核心api
一句话概括就是,用js对象表示dom信息和结构,更新时重新渲染更新后的对象对应的dom,这个对象就是React.createElement()的返回结果
全栈潇晨
2021/06/04
4810
react源码解析5.jsx&核心api
react之jsx编译原理
使用react的朋友想必对jsx的语法相当熟悉,简单点来说,就是JavaScript和html可以混着写,灵活快速,而且可以一目了然的看清楚DOM的结构,当然jsx需要经过babel编译为javascript对象,再经过渲染插入到页面上。 接下来我们来探讨几个问题: react内部是如何处理JavaScript和html混写的代码? 组件名为啥首字母一定要大写? 在花括号{}里边内容,比如是一行表达式 var a = 'hello world'; 为啥会报错? 下边来具体看看jsx处理逻辑,上例子: 1、
QQ音乐前端团队
2019/07/02
3.1K0
react之jsx编译原理
小前端读源码 - React16.7.0(一)
2019年,前端的情况暂时还是三足鼎立的局面,React,Vue和Angular。平常开发中我们基本上离不开框架的使用,但是大部分人也只是了解如何使用,或者深入一点的就是知道用什么框架做什么样的功能会有什么样的坑(经验所谈)。但是又有多少人愿意去认真读一读框架的源码,深入理解背后的逻辑呢?
LamHo
2022/09/26
4370
小前端读源码 - React16.7.0(一)
美团前端高频面试题集锦_2023-03-15
实际开发中不会使用createElement来创建ReactElement的,一般都是使用JSX的形式开发。
gogo2027
2023/03/15
9420
字节前端必会面试题(持续更新中)_2023-02-27
对于定长的数据包而言,发送端在发送数据的过程中,需要设置Content-Length,来指明发送数据的长度。
gogo2027
2023/02/27
9150
来自大厂 10+ 前端面试题附答案(整理版)_2023-03-15
WebSocket是HTML5提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。
java_js0166293
2023/03/15
6050
React源码解析之React.createRef()/forwardRef()
一、React.createRef() GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactCreateRef.js
进击的小进进
2019/09/05
1.6K0
React源码解析之React.createRef()/forwardRef()
相关推荐
react源码分析:babel如何解析jsx_2023-02-27
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验