React16源码: JSX2JS及React.createElement源码实现

本文主要是介绍React16源码: JSX2JS及React.createElement源码实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JSX 到 Javascript 的转换

  • React中的 JSX 类似于 Vue中的template模板文件,Vue是基于编译时将template模板转换成render函数
  • 在React中,JSX是类似于html和javascript混编的语法,而javascript是真的javascript, html并非真的html
  • 它的可阅读性可维护性都是要高很多的

1 )JSX2JS 原理

  • JSX 通过 babel 进行转换之后,生成了纯JS
    • JSX相对于JS来讲,它唯一的一个区别,就是它可以写类似于HTML的一个标签
    • 比如说我们通过写div 然后在 div 这种方式去声明HML的标签
    • 然后它会给我们返回在React当中需要使用的对象
  • 这就是JSX到JS的一个转化过程

2 ) 工具演示

  • 这个工具是 babel playground
    • babeljs.io/repl
  • 要做一些代码转化的一个测试,可以直接到这个playground上面来
    • 它会实时在为我们展现出我们写的代码转化出来的是什么样的样子
    • 下面的示例是使用较低版本的 babel 来配合 React 16.6 版本

示例1

jsx

<div></div>

js

React.createElement("div", null);

示例2

jsx

<div class="test"></div>

js

React.createElement("div", {class: "test"
});

示例3

jsx

<div id="div" key="key" class="test"><span>1</span><span>1</span>
</div>

js

React.createElement("div", {id: "div",key: "key",class: "test"
}, React.createElement("span", null, "1"), React.createElement("span", null, "1"));

示例4

jsx

function Comp() {return <a>123</a>
}<Comp id="div" key="key"><span>1</span><span>1</span><div id="box"><span class='inner'>2</span></div>
</Comp>

js

function Comp() {return React.createElement("a", null, "123");
}React.createElement(Comp, {id: "div",key: "key"
}, React.createElement("span", null, "1"),React.createElement("span", null, "1"),React.createElement("div", {id: "box"}, React.createElement("span", {class: "inner"}, "2"))
);

3 )说明

  • 从上面我们可以看出来我们的一个类似html的标签,或者是组件的一个标签
  • 通过这种尖括号的方式来写的,它最终都会转换成 React.createElement
  • 我们写的这些标签或者一些props,或者它的 children 都会作为一个参数
    • props 是一个 key-value 形式的一个对象
    • 它可以支持多层,无限层的嵌套,也就是一个树形结构
  • 如果是一个函数式的组件作为参数
    • 这里要分两种情况
      • 1 ) 组件是大写的,这样会直接转换成变量(对象)
      • 2 ) 组件是小写的,这样会直接变成字符串类型的标记(组件将失效)
    • 注意
      • 如果变成字符串,那么在React中,它是会认为这是一个原生的dom节点的
      • 如果不存在这么一个dom节点,那么后续在运行的时候,可能就报错了
      • 所以自定义的组件必须使用大写的开头,这是一个规范
  • 综上,我们现在问题的重点就在 createElement 之上了

React.createElement 源码解析

  • 在上一步的 JSX2JS中,我们的标签,标签里的属性,标签的内容,都会变成各种类型的参数
  • 传到我们调用的 createElement 这个方法里面,这个方法内部如何实现的
  • 在 createElement 函数的内部,返回了一个 React Element, 我们来看看它具体的作用
  • 看源码肯定要从它的入口文件开始看,因为入口文件会给我们很多的信息告诉我们
  • 常用的使用这个包的时候的这些API它都来自于哪里,以及它是如何 export 出来的

1 )React 入口文件 packages/react/src/React.js

/*** Copyright (c) Facebook, Inc. and its affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.*/import ReactVersion from 'shared/ReactVersion';
import {REACT_CONCURRENT_MODE_TYPE,REACT_FRAGMENT_TYPE,REACT_PROFILER_TYPE,REACT_STRICT_MODE_TYPE,REACT_SUSPENSE_TYPE,
} from 'shared/ReactSymbols';import {Component, PureComponent} from './ReactBaseClasses';
import {createRef} from './ReactCreateRef';
import {forEach, map, count, toArray, only} from './ReactChildren';
import {createElement,createFactory,cloneElement,isValidElement,
} from './ReactElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
import forwardRef from './forwardRef';
import memo from './memo';
import {createElementWithValidation,createFactoryWithValidation,cloneElementWithValidation,
} from './ReactElementValidator';
import ReactSharedInternals from './ReactSharedInternals';
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';const React = {Children: {map,forEach,count,toArray,only,},createRef,Component,PureComponent,createContext,forwardRef,lazy,memo,Fragment: REACT_FRAGMENT_TYPE,StrictMode: REACT_STRICT_MODE_TYPE,Suspense: REACT_SUSPENSE_TYPE,createElement: __DEV__ ? createElementWithValidation : createElement,cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,createFactory: __DEV__ ? createFactoryWithValidation : createFactory,isValidElement: isValidElement,version: ReactVersion,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};if (enableStableConcurrentModeAPIs) {React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;React.Profiler = REACT_PROFILER_TYPE;
} else {React.unstable_ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;React.unstable_Profiler = REACT_PROFILER_TYPE;
}export default React;
  • 上面全是 import 其他一些东西,import 进来之后, 它声明了 React 对象
  • 这个对象就是我们在外部去用 React 的时候,给我们提供的API
  • 然后最终它 export default React 把这个对象给它 export 出来
  • 这样的话我们就可以在外部使用
  • 我们回到 createElement 上面来,从上面可知,跟Element相关的一些代码
  • 都放在了 ./ReactElement 这个文件下面

2 )定位到 ReactElement.js 文件中

/*** Copyright (c) Facebook, Inc. and its affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.*/import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';import ReactCurrentOwner from './ReactCurrentOwner';const hasOwnProperty = Object.prototype.hasOwnProperty;const RESERVED_PROPS = {key: true,ref: true,__self: true,__source: true,
};let specialPropKeyWarningShown, specialPropRefWarningShown;function hasValidRef(config) {if (__DEV__) {if (hasOwnProperty.call(config, 'ref')) {const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;if (getter && getter.isReactWarning) {return false;}}}return config.ref !== undefined;
}function hasValidKey(config) {if (__DEV__) {if (hasOwnProperty.call(config, 'key')) {const getter = Object.getOwnPropertyDescriptor(config, 'key').get;if (getter && getter.isReactWarning) {return false;}}}return config.key !== undefined;
}function defineKeyPropWarningGetter(props, displayName) {const warnAboutAccessingKey = function() {if (!specialPropKeyWarningShown) {specialPropKeyWarningShown = true;warningWithoutStack(false,'%s: `key` is not a prop. Trying to access it will result ' +'in `undefined` being returned. If you need to access the same ' +'value within the child component, you should pass it as a different ' +'prop. (https://fb.me/react-special-props)',displayName,);}};warnAboutAccessingKey.isReactWarning = true;Object.defineProperty(props, 'key', {get: warnAboutAccessingKey,configurable: true,});
}function defineRefPropWarningGetter(props, displayName) {const warnAboutAccessingRef = function() {if (!specialPropRefWarningShown) {specialPropRefWarningShown = true;warningWithoutStack(false,'%s: `ref` is not a prop. Trying to access it will result ' +'in `undefined` being returned. If you need to access the same ' +'value within the child component, you should pass it as a different ' +'prop. (https://fb.me/react-special-props)',displayName,);}};warnAboutAccessingRef.isReactWarning = true;Object.defineProperty(props, 'ref', {get: warnAboutAccessingRef,configurable: true,});
}/*** Factory method to create a new React element. This no longer adheres to* the class pattern, so do not use new to call it. Also, no instanceof check* will work. Instead test $$typeof field against Symbol.for('react.element') to check* if something is a React Element.** @param {*} type* @param {*} key* @param {string|object} ref* @param {*} self A *temporary* helper to detect places where `this` is* different from the `owner` when React.createElement is called, so that we* can warn. We want to get rid of owner and replace string `ref`s with arrow* functions, and as long as `this` and owner are the same, there will be no* change in behavior.* @param {*} source An annotation object (added by a transpiler or otherwise)* indicating filename, line number, and/or other information.* @param {*} owner* @param {*} props* @internal*/
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;
};/*** Create and return a new ReactElement of the given type.* See https://reactjs.org/docs/react-api.html#createelement*/
export function createElement(type, config, children) {let propName;// Reserved names are extractedconst 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;// Remaining properties are added to a new props objectfor (propName in config) {if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {props[propName] = config[propName];}}}// Children can be more than one argument, and those are transferred onto// the newly allocated props object.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];}if (__DEV__) {if (Object.freeze) {Object.freeze(childArray);}}props.children = childArray;}// Resolve default propsif (type && type.defaultProps) {const defaultProps = type.defaultProps;for (propName in defaultProps) {if (props[propName] === undefined) {props[propName] = defaultProps[propName];}}}if (__DEV__) {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,);
}/*** Return a function that produces ReactElements of a given type.* See https://reactjs.org/docs/react-api.html#createfactory*/
export function createFactory(type) {const factory = createElement.bind(null, type);// Expose the type on the factory and the prototype so that it can be// easily accessed on elements. E.g. `<Foo />.type === Foo`.// This should not be named `constructor` since this may not be the function// that created the element, and it may not even be a constructor.// Legacy hook: remove itfactory.type = type;return factory;
}export function cloneAndReplaceKey(oldElement, newKey) {const newElement = ReactElement(oldElement.type,newKey,oldElement.ref,oldElement._self,oldElement._source,oldElement._owner,oldElement.props,);return newElement;
}/*** Clone and return a new ReactElement using element as the starting point.* See https://reactjs.org/docs/react-api.html#cloneelement*/
export function cloneElement(element, config, children) {invariant(!(element === null || element === undefined),'React.cloneElement(...): The argument must be a React element, but you passed %s.',element,);let propName;// Original props are copiedconst props = Object.assign({}, element.props);// Reserved names are extractedlet key = element.key;let ref = element.ref;// Self is preserved since the owner is preserved.const self = element._self;// Source is preserved since cloneElement is unlikely to be targeted by a// transpiler, and the original source is probably a better indicator of the// true owner.const source = element._source;// Owner will be preserved, unless ref is overriddenlet owner = element._owner;if (config != null) {if (hasValidRef(config)) {// Silently steal the ref from the parent.ref = config.ref;owner = ReactCurrentOwner.current;}if (hasValidKey(config)) {key = '' + config.key;}// Remaining properties override existing propslet defaultProps;if (element.type && element.type.defaultProps) {defaultProps = element.type.defaultProps;}for (propName in config) {if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {if (config[propName] === undefined && defaultProps !== undefined) {// Resolve default propsprops[propName] = defaultProps[propName];} else {props[propName] = config[propName];}}}}// Children can be more than one argument, and those are transferred onto// the newly allocated props object.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];}props.children = childArray;}return ReactElement(element.type, key, ref, self, source, owner, props);
}/*** Verifies the object is a ReactElement.* See https://reactjs.org/docs/react-api.html#isvalidelement* @param {?object} object* @return {boolean} True if `object` is a ReactElement.* @final*/
export function isValidElement(object) {return (typeof object === 'object' &&object !== null &&object.$$typeof === REACT_ELEMENT_TYPE);
}
  • 在这个文件里面先找到 createElement 这个方法,我们可以看到它接收的三个参数
    • type
      • 就是我们的节点类型,如果是原生的节点,那么它是一个字符串
      • 那如果是我们自己声明的组件,它就是一个class component 或者是一个 functional component
      • 还会有其他的一些情况。比如 使用 React 原生的一些组件
        • 比如说 Fragment、 StrictMode、 Suspense
        • 这些都是 React 提供我们的一些原生组件,
        • 其实,上面三个它们默认就只是一个 Symbol
        • 它没有任何其他的功能,就仅仅是一个标志
    • config
      • 是我们写在这个JSX标签上面的所有的 attributes
      • 它们都会变成key value的形式存到这个config对象里面
      • 我们要从这个对象里面筛选出真正的props的内容
      • 还有特殊的,比如说 key,ref 这些属性
    • children
      • 就是我们标签中间我们放的一些内容
      • 它可能是一个子标签,或者它直接是文字 text
  • 我们来看一下它如何去创建一个 ReactElement, 还是回到 createElement 这个方法
    • 内部声明了一堆变量,找到有没有合理的REF,有没有合理的key
    • 我们把这些都给它读到一个单独的变量里面
    • 忽略 self 和 source 这两个东西,不是特别的重要
    • 接下去,要对剩下的config下面的 props 进行一个处理
      • 判断一下它是否是内建的 props,如果不是的话,就放到一个新建的 props 对象里面
      • 如果是内建的 props,就不放进去了,因为它不属于正常的 props 的范畴
      • 看一下这个内建的 props,它是什么东西
        const RESERVED_PROPS = {key: true,ref: true,__self: true,__source: true,
        };
        
      • key, ref, __self, __source, 这些都不会出现在我们使用class component 的场景下
        • 比如说我们的 this.props里面
        • 因为在处理props的过程当中,就已经把它处理掉了
    • 这边把 props 的属性全部拿出来,放到一个新的对象里面之后
    • 接下去要处理children
      • children 是可以有多个的,在一个节点下面
      • 它的 children 可能有很多的兄弟节点存在
      • 它是作为后续的参数传进来的,虽然在声明 createElement的时候只有三个参数
      • 但是它是可以传 3,4,5,6,7,8,9, … 多个参数的
      • 后续第三个参数之后的参数,我们都认为它们是 children
      • 在react当中把后续 arguments.length - 2 代表剩下的这个长度都是children
      • 然后会一个一个把它读出来,然后变成一个数组
      • 声明一个数组, 存放后续所有的 children 节点对象, 最终再把它放到 props.children
      • 通过 this.props.children 拿到的就是这部分的内容
    • 接下来,就是 defaultProps 的处理
      • 在声明 class Comp 的时候,比如说我们 extends React.Component
      • 我们可以通过 Comp.defaultProps 一个对象,给接收的这些props去设置一些默认值
      • 比如说, 这边它的默认值是 {value:1},当组件在被使用的时候,没有传value这个props
      • 在这里面就会使用 1 作为我们在组件内部 this.props.value 去拿到的这个值
      • 它就是把我们刚才上面处理过的那个props对象上面去读取对应的defaultProps里面的每一个key的值
      • 如果值是 undefined,就把它设置为 defaultProps 里面的属性
      • 如果它是有值的, 我们就不设置了
      • 注意
        • 它的判断条件是 undefined
        • 也就是说 null 也是一个不需要使用默认值的情况
    • 接着,下面 DEV 判断的代码,进行忽略
    • 最终 return了一个 ReactElement
      • 传入刚才处理过的这些内容
  • 关于 ReactElement
    • 它不是一个 class Comp, 而是一个 function
    • 最终会return一个Object, 这个Object也就几个主要的属性
      • $$typeofREACT_ELEMENT_TYPE
        • 是用来标识我们的 element 是什么类型的
        • 在写JSX代码的时候,所有的节点都是通过 createElement 进行创建的
        • 那么,它的 $$typeof 永远都是 REACT_ELEMENT_TYPE
        • 在后续React的更新渲染dom的过程中是经常被用到的
        • 大部分情况下,我们拿到的 $$typeof 都是 REACT_ELEMENT_TYPE
        • 有一些特殊情况是和平台相关
          • 在react-dom里面,它有一个API叫做 React.createPortal, 它返回的对象和这里的类似
          • 但是它的 $$typeofREACT_PORTAL_TYPE
      • type 是之前传进来的那个 type
        • 是在 createElement 的时候接收的那个 type
        • 用于记录节点的类型,是原生组件,还是 class Comp
      • key 就是上面处理过的 key
      • ref 就是 ref
      • props 就是 props
    • 综上,就是一个 ReactElement, 具体的方法,如何去操作,以及最终返回的类型

这篇关于React16源码: JSX2JS及React.createElement源码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/556631

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo