1、createElementjavascript
上一章咱们讲到了全部jsx语法都会被转成createElement。java
那么createElement的实现是怎样的呢?react
首先咱们从github克隆下来react的源码库,咱们先来分析下react源码库的文件布局。git
react工程根目录下有packages文件夹,其间放置的是react的各个包,咱们暂时把着力点放于react目录下。内部是react源码实现。github
抛出去一些非必要的检测,和warn代码,核心的react代码其实只有几百行。react源码自己并不复杂,负责渲染的react-dom才是最复杂的。数组
react目录的src,就是react的核心实现了。数据结构
createElement方法位于ReactElement.js文件内,实现以下:dom
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,
);
}
复制代码
这里面有一些开发环境下检测,和外部调用方法,可能会使阅读者精力分散,咱们来稍微改动精简下代码,使功能一致,同时更好阅读:函数
export function createElement(type, config, ...children) {
const {ref = null, key = null} = config || {};
const {current} = ReactCurrentOwner;
const {defaultProps} = type || {};
const props = assignProps(config, defaultProps, children);
return new ReactElement({
type,
key: '' + key,
ref,
current,
props,
});
}
复制代码
通过精简和简化后,createElement仅有30行代码。咱们来逐行解析下。布局
/** * * @param type {string | function | object} * 若是type是字符串,那就是原生dom元素,好比div * 若是是function或者是Component的子类 则是React组件 * object 会是一些特殊的type 好比fragment * @param config {object} * props 和key 还有ref 其实都是在config里了 * @param children * 就是由其余嵌套createElement方法返回的ReactElement实例 * @returns {ReactElement} * */
export function createElement(type, config, ...children) {
// 给config设置一个空对象的默认值
// ref和key 默认为null
const {ref = null, key = null} = config || {};
// ReactCurrentOwner负责管理当前渲染的组件和节点
const {current} = ReactCurrentOwner;
// 若是是函数组件和类组件 是能够有defaultProps的
// 好比
// function A({age}) {return <div>{age}</div>}
// A.defaultProps = { age:123 }
const {defaultProps} = type || {};
// 把defaultProps和props 合并一下
const props = assignProps(config, defaultProps, children);
// 返回了一个ReactElement实例
return new ReactElement({
type,
key: '' + key,
ref,
current,
props,
});
}
复制代码
ref和key不用多说,你们都知道是干啥的。以前有个同事问过我,key明明传的是数字,为啥最后成了字符串,症结就在上面的ReactELement构造函数传参的key那里了,key:''+key
。
assignProps是我抽象了一个方法,合并defaultProps和传入props的方法,稍后提供代码,其实在cloneElement方法里,也有一段相似代码,可是react并无抽象出来,相对来讲,会有代码冗余,暂且提炼出来。
重点在new ReactElement()。
react的代码里,ReactElement是个工厂函数,返回一个对象。可是我我的以为比较奇怪。
第1、工厂函数生成实例,这个工厂函数不应大写开头。
第2、使用构造函数或者类来声明ReactElement难道不是一个更好,更符合语义的选择?
在这里,为了便于理解,把ReactElement从工厂函数,改变成了一个类,createElement返回的就是一个ReactElement类的实例。
下面看下asssignProps的实现,该方法在cloneElement也能够复用:
const RESERVED_PROPS = ['key', 'ref', '__self', '__source'];
export function assignProps(config, defaultProps, children) {
const props = {
children,
};
config = config || {};
for (const propName in config) {
if (
config.hasOwnProperty(propName) &&
!RESERVED_PROPS.includes(propName)
) {
props[propName] = config[propName];
if (
props[propName] === undefined &&
defaultProps &&
defaultProps[propName] !== undefined
) {
props[propName] = defaultProps[propName];
}
}
}
return props;
}
复制代码
2、ReactElement
create返回的是个ReactElement实例,那么ReactElement又是啥呢?
抛出去dev时的代码,精简后以下:
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};
return element;
};
复制代码
能够看到,其实就是返回了一个对象,咱们如今能够简单而浮夸的想象下,react的render机制其实就是读取这些数据结构,而后根据结构树,层层根据原生dom方法渲染而成。(暂时这样想象)
通过用类改造后的代码为:
export class ReactElement {
constructor(elementParams) {
const {type, key, ref, current, props} = elementParams || {};
// 若是是原生标签好比h1 那就是字符串
// 若是是组件 则是组件的引用
this.type = type;
// key
this.key = key;
// ref
this.ref = ref;
// 延后再讲
this._owner = current;
// props
this.props = props;
// 类型标识 新版本中的React里是symbo
this.$$typeof = REACT_ELEMENT_TYPE;
}
}
复制代码
3、总结
本章的重点在于,在react中,jsx标签的本质就是ReactElement,createElement会对组件或者dom的type和props通过一层封装处理,最后返回了ReactElement的实例。