共计 7637 个字符,预计需要花费 20 分钟才能阅读完成。
一、JSX 语法转换到 Js 语法
从 JSX 转换到 JS 会用到React.createElement()
,所以先熟悉下 JSX 到 JS 的转换。
这边是 JSX 语法:
<div id='one' class='two'>
<span id="spanOne">this is spanOne</span>
<span id="spanTwo">this is spanTwo</span>
</div>
这边是转化成的 js 语法:
React.createElement(
"div",
{id: "one", class: "two"},
React.createElement("span", { id: "spanOne"}, "this is spanOne"),
React.createElement("span", { id: "spanTwo"}, "this is spanTwo")
);
React.createElement("标签名","Object,包含 div 的 props",'children 子节点 1','children 子节点 2','...')
这边是 JSX 语法:
function Div(){}
<Div id='one' class='two'>
<span id="spanOne">this is spanOne</span>
<span id="spanTwo">this is spanTwo</span>
</Div>
这边是转化成的 js 语法:
React.createElement(Div, {} , xxx );
如果标签名大写,则表示组件 Div(也就是function
),小写表示 html 的标签 <div>
也就是说:自定义的组件必须大写字母开头
二、React.createElement()
源码地址:https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactElement.js
作用:
创建 React.Element
,示例请看 一、JSX 语法转换到 Js 语法
源码:
// 注意:react 只写了 3 个参数,实际上,从第三个参数往后都是 children
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;
// 赋给标签的 props 不为空时
if (config != null) {if (hasValidRef(config)) {ref = config.ref;}
if (hasValidKey(config)) {
// 防止是 Number
key = '' + config.key;
}
//__self、__source 暂时不知道是干啥用的属性
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) {
// 如果 config 中的属性不是标签原生属性,则放入 props 对象中
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);
// 依次将 children push 进 array 中
for (let i = 0; i < childrenLength; i++) {childArray[i] = arguments[i + 2];
}
// 如果是 development 环境的话
if (__DEV__) {
// 冻结 array
// 未在微信发表
//https://www.jianshu.com/p/91e5dc520c0d?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=weixin-friends&from=singlemessage&isappinstalled=0
if (Object.freeze) {Object.freeze(childArray);
}
}
// 开发中写的 this.props.children 就是子元素的集合
props.children = childArray;
}
// Resolve default props
// 为传入的 props 设置默认值,比如://class Comp extends React.Component{
// static defaultProps = {
// aaa: 'one',
// bbb: () => {},
// ccc: {},
// };
//
// }
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
// 如果 props 数组中未设值,则设置默认值(注意:null 也算设置了值)if (props[propName] === undefined) {props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
// 一旦 ref 或 key 存在
if (key || ref) {
// 如果 type 是组件的话,赋值 displayName
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
// 可不看
if (key) {defineKeyPropWarningGetter(props, displayName);
}
if (ref) {defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(
type, //'div'
key, //null
ref, //null
self, //null
source, //null
ReactCurrentOwner.current, //null 或 Fiber
props, // 自定义的属性、方法,注意:props.children=childArray
);
}
解析:
(1)hasValidRef()
作用:
判断是否设置了 ref 的属性,true
有,false
没有
源码:
// 判断是否设置了 ref 的属性,true 有,false 没有
function hasValidRef(config) {
// 如果是 development 环境的话
if (__DEV__) {
// 如果 config 中存在 ref 属性的话
// 在 jQuery 中 .call/.apply 的更大作用是绑定 this
if (hasOwnProperty.call(config, 'ref')) {//Object.getOwnPropertyDescriptor() es5
//Object.getOwnPropertyDescriptors() es6
//https://blog.csdn.net/qq_30100043/article/details/53424963
// 返回对象 config 的属性 ref 的 get 对象
const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
// 如果 isReactWarning,则忽略 ref 属性,返回 false
if (getter && getter.isReactWarning) {return false;}
}
}
//<div ref={this.optionsTEchart} ></div>
return config.ref !== undefined;
}
① 注意:__DEV__
表示测试环境,是供 React
内部测试的,可以不看,我简单地解释了下
② 在 jQuery
中fn.call(xxx,a1,a2,...)
或 fn.apply(xxx,array)
的更大作用是绑定this
③ Object.getOwnPropertyDescriptor()
的作用是返回某个对象属性的描述对象(descriptor)
比如:
var obj = {p: 'a'};
Object.getOwnPropertyDescriptor(obj, 'p')
// 返回
// Object { value: "a",
// writable: true,
// enumerable: true,
// configurable: true
}
关于 Object.getOwnPropertyDescriptor()
和Object.getOwnPropertyDescriptors()
的区别,请看:https://blog.csdn.net/qq_30100043/article/details/53424963
(2)hasValidKey
作用:
判断是否设置了key
,同hasValidRef
,不解释了
源码:
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;
}
(3)虽然 React.createElement()
只传三个参数,但从第三个参数开始,利用 arguments
来获取剩下的参数
(4)Object.freeze()
使用 Object.freeze()
冻结的对象是最严格的防篡改级别,既不可扩展,也是密封的,不可修改属性。
对于 JS 库作者而言,冻结对象可 防止 有人修改库的核心对象。
关于 JS 冻结对象的方法,请看:JS 红皮书解读之防篡改对象
(5)最后是 return 了 ReactElement()
方法,注意 props
中的 children
属性就是 React
组件的children
react 组件的 children 属性不会被覆盖:
父组件:
return(<DashBoard children={'bbbb'}>
aaaa
</DashBoard>
)
子组件:
console.log(this.props)
结果:
三、ReactElement()
源码地址:https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactElement.js
作用:
通过工厂模式创建 React.Element
对象,你打印一个 React
组件的话,会是下面这个样子:
源码:
/**
* 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 {*} props
* @param {*} key
* @param {string|object} ref
* @param {*} owner
* @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.
* @internal
*/
// type, //'div'
// key, //null
// ref, //null
// self, //null
// source, //null
// ReactCurrentOwner.current, //null 或 Fiber
// props, // 自定义的属性、方法,注意:props.children=childArray
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
// 标识 element 的类型
// 因为 jsx 都是通过 createElement 创建的,所以 ReactElement 的类型固定: 为 REACT_ELEMENT_TYPE
// 重要!因为 react 最终渲染到 DOM 上时,需要判断 $$typeof===REACT_ELEMENT_TYPE
$$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.
// 记录创建 react.element 的组件(this?)_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.
// 验证 flag 是不固定的. 我们将其放置在一个 store 上,从而能冻结整个 object
// 这样一旦它们被用在开发环境时,用 WeakMap 代替
//WeakMap
// http://es6.ruanyifeng.com/#docs/set-map
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;
};
解析:
(1)通过 $$typeof
确保是 React.Element
类型,从而渲染到真正的 DOM
树上
(2)__DEV__
注释中有提到 WeakMap
,
简单说下 WeakMap
的作用:
你往 WeakMap
上的对象 a 添加数据,对象 b 引用 对象 a,之后对象 b 不引用 对象 a,a 就被垃圾回收,不用 WeakMap
的话,即使对象 b 以后不引用对象 a 了,a 也不会被垃圾回收,因为 强引用 是不会触发 垃圾回收机制 的,需要手动删除,很麻烦。
想更详细地了解的话,可以参考下这篇文章:
http://es6.ruanyifeng.com/#docs/set-map
关于垃圾回收机制,请看:浅谈下垃圾回收机制(1)
(3)该方法比较简单,就是初始化了一个对象,并将其标记为 React.Element
对象($$typeof=REACT_ELEMENT_TYPE
)
(完)