React16源码: JSX2JS及React.createElement源码实现
2023-12-31 14:33:52
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
- 要做一些代码转化的一个测试,可以直接到这个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 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;
};
/**
* 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 extracted
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;
// Remaining properties are added to a new props object
for (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 props
if (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 it
factory.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 copied
const props = Object.assign({}, element.props);
// Reserved names are extracted
let 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 overridden
let 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 props
let 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 props
props[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 也是一个不需要使用默认值的情况
- 它的判断条件是
- 在声明 class Comp 的时候,比如说我们
- 接着,下面 DEV 判断的代码,进行忽略
- 最终 return了一个
ReactElement
- 传入刚才处理过的这些内容
- 关于
ReactElement
- 它不是一个 class Comp, 而是一个 function
- 最终会return一个Object, 这个Object也就几个主要的属性
$$typeof
是REACT_ELEMENT_TYPE
- 是用来标识我们的 element 是什么类型的
- 在写JSX代码的时候,所有的节点都是通过 createElement 进行创建的
- 那么,它的
$$typeof
永远都是REACT_ELEMENT_TYPE
- 在后续React的更新渲染dom的过程中是经常被用到的
- 大部分情况下,我们拿到的
$$typeof
都是REACT_ELEMENT_TYPE
- 有一些特殊情况是和平台相关
- 在react-dom里面,它有一个API叫做
React.createPortal
, 它返回的对象和这里的类似 - 但是它的
$$typeof
是REACT_PORTAL_TYPE
- 在react-dom里面,它有一个API叫做
type
是之前传进来的那个 type- 是在
createElement
的时候接收的那个 type - 用于记录节点的类型,是原生组件,还是 class Comp
- 是在
key
就是上面处理过的 keyref
就是 refprops
就是 props
- 综上,就是一个 ReactElement, 具体的方法,如何去操作,以及最终返回的类型
文章来源:https://blog.csdn.net/Tyro_java/article/details/135314006
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!