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

工欲善其事,必先利其器

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

jsx

<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是怎么来的

// 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

export function jsx(type, config, maybeKey) {let propName;//标签上的属性集合const props = {};//单独处理key reflet key = null;let ref = null;if (maybeKey !== undefined) {key = '' + maybeKey;}if (hasValidKey(config)) {// 处理合法的keykey = '' + config.key;}if (hasValidRef(config)) {// 处理合法的refref = config.ref;}// 把属性加到props中for (propName in config) {if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {props[propName] = config[propName];}}// 处理默认propsif (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

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

// packages/react/src/ReactElement.js
export function createElement(type, config, children) {let propName;// 记录标签上的属性集合const props = {};//单独处理key reflet 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;}// 记录信息用于debugself = 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;}// 处理默认propsif (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

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 elementtype: 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到底干了啥。

// 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方法了。

react源码分析:babel如何解析jsx相关推荐

  1. 推荐一个 React 技术揭秘的项目,自顶向下的 React 源码分析

    大家好,我是你们的 猫哥,那个不喜欢吃鱼.又不喜欢喵 的超级猫 ~ just-react 这本书的宗旨是打造一本严谨.易懂的 React 源码分析教程. 为了达到这个目标,在行文上,本书会遵循: 不预 ...

  2. react源码分析-setState分析

    前言 是否有过这样的疑问: setState做了什么? setState是如何触发ui变化的? isWorking 如果此时isWorking为true,react将不会立即执行更新操作,而是把更新操 ...

  3. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  4. React源码分析与实现(一):组件的初始化与渲染

    原文链接地址:github.com/Nealyang 转载请注明出处 前言 战战兢兢写下开篇...也感谢小蘑菇大神以及网上各路大神的博客资料参考~ 阅读源码的方式有很多种,广度优先法.调用栈调试法等等 ...

  5. 窥探React - 源码分析

    所谓知其然还要知其所以然. 本文将分析 React 15-stable的部分源码, 包括组件初始渲染的过程和组件更新的过程.在这之前, 假设读者已经: 对React有一定了解 知道React elem ...

  6. React源码分析(一)= scheduler分析

    文章目录 1. 前言 2. getCurrentTime 3. unstable_scheduleCallback函数 4. scheduleHostCallbackIfNeeded 4.1. flu ...

  7. React源码解毒 - render方法解析

    render方法解析 要将react元素渲染到页面当中,分为了两个阶段: render阶段 和 commit阶段. render阶段:由协调层负责的阶段 在这个阶段当中要为每一个react元素构建对应 ...

  8. Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 一

    搭建nginx服务器时,主要的配置文件 nginx.conf 是部署和维护服务器人员经常要使用到的文件, 里面进行了许多服务器参数的设置.那么nginx 以模块 module为骨架的设计下是如何运用模 ...

  9. React源码分析(二)= Reac初次渲染分析

    文章目录 1. render阶段 legacyRenderSubtreeIntoContainer 1.1. legacyCreateRootFromDOMContainer 1.1.1. React ...

  10. react源码分析:深度理解React.Context

    开篇 在 React 中提供了一种「数据管理」机制:React.context,大家可能对它比较陌生,日常开发直接使用它的场景也并不多. 但提起 react-redux 通过 Provider 将 s ...

最新文章

  1. Google Brain与牛津大学主持最新《计算机视觉前沿》报告(146页PPT)
  2. LeetCode455 分发饼干(二分法)
  3. 服务器flask远程访问_在Flask中使用什么API来检查远程(其他)服务器的连接?...
  4. 洛谷P3414 SAC#1 - 组合数
  5. BootStrap笔记-信息提示框的使用
  6. SQL 查询结果为 XML
  7. sdk manager的列表怎么消失了_腾讯安全SDK的Dll Dump研究
  8. SecureCRT的下载、安装( 过程非常详细!!值得查看)
  9. 半桥驱动器芯片 TPS28225 中文资料
  10. #49:Photoshop技巧的大杂烩
  11. 2106_视频处理与压缩技术_中文综述
  12. 地理信息系统(GIS)应用场景步骤
  13. 智能电视是否是一台计算机,误区四 智能电视代替电脑使用_平板电视_液晶电视评测-中关村在线...
  14. Remix-IDE安装开发环境与使用文档(Windows环境)
  15. 微信小程序开发教程-微信小程序入门
  16. SpringCloud-Zuul常用路由网关
  17. 全终端办公电子邮件集成方案
  18. 虚拟机安装Ubuntu18.04,vmtools,配置语言,要是再搞不定你打死我!!!
  19. java8常见的stream流处理
  20. 华软2016校内选拔赛

热门文章

  1. 市面上最流行的九大现场总线
  2. 工业电脑量产测试之----U盘DOS 启动盘制作(1)
  3. Java 正则表达式之提取数字
  4. 教你如何卷积操作进行边缘检测,基础必备
  5. angular-busy用法
  6. 2014TI杯(D题)带啸叫检测与抑制的音频功率放大器
  7. Speedoffice(Excel)怎样给文字添加删除线?
  8. PyTorch学习(二):Transform
  9. 局部边缘保持滤波(LEP)高动态范围图像HDR压缩 matlab程序(二)
  10. Vue项目上传图片后在ie浏览器卡死