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的實例。