本文使用的是react 15.6.1的代码
从创建一个组件开始
在我们使用React组件的时候,我们都会写类似这样一段的代码
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
let {text} = this.props;
return <div id="app">
<span>{text}</span>
</div>
}
}
这是一段简单的用ES6+JSX写的代码,抛开这一些语法糖,我们看看经过babel编译后是什么样子的
_createClass(App, [{
key: "render",
value: function render() {
var a = this.props.a;
return _react2.default.createElement(
"div",
{ id: "app" },
_react2.default.createElement(
"span",
null,
a
)
);
}
}]);
因为笔者用的是ES6的代码,所以经过babel编译,看着和react差别比较大,不过对观察源码并无太多影响
首先我们看到,编译后他调用了createClass方法,是不是和用ES5写react很像,ES5写组件是通过React.createClass
来完成,通过编译后我们可以知道,使用 extends React.Component写法,其实内部也是在使用createClass来生成一个React组件,现在我们来看看React.createClass的源代码(先去掉process.env.NODE_ENV !== 'production'里面的代码,该部分只用于调试,对阅读无影响)
代码地址:createClass.js
var {Component} = require('ReactBaseClasses');
var {isValidElement} = require('ReactElement');
var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
var factory = require('create-react-class/factory');
module.exports = factory(Component, isValidElement, ReactNoopUpdateQueue);
通过代码了解createClass会先执行factory方法,并将结果返回出去,
代码地址:factory.js
//仅仅保留核心实现
function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue){
function createClass(spec) {
//构造函数
var Constructor = identity(function(props, context, updater) {
if (this.__reactAutoBindPairs.length) {
bindAutoBindMethods(this);
}
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
this.state = null;
//如果实例存在getInitialState方法,将会调用他获取state
var initialState = this.getInitialState ? this.getInitialState() : null;
this.state = initialState;
});
// 继承ReactClassComponent
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
//如果class中定义了getDefaultProps方法。调用getDefaultProps获取默认的props
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
// 对外暴露的方法,如render,shouldComponentUpdate等,如果在组件中没有实现,则返回null
// 暴露出来的所有方法可以在factory中变量ReactClassInterface看到
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
return Constructor;
}
return createClass;
}
源码中,我们发现其构造函数为identity(),那么identity做了什么呢? 这里面代码非常简单
// 仅仅是创建一个匿名函数
function identity(fn) {
return fn;
}
通过源码,我们了解到react.createClass具有以下功能
- createClass方法执行后会返回一个function Constructor,当组件类真正实例化的时候(new等)会初始化pro同时调用initialState方法获取state,同时,该组件类一旦被声明(非调用),会优化调用getDefaultProps给defaultProps赋值,该defaultProps不属于任何实例独有,所有实例会共用getDefaultProps,类似于Java中的静态变量,当然你也可以直接 App.defaultProps = {text:'hello react'}
- 如本文开头示例代码,定义了render函数,react暴露出来的方法如果未定义,会自动变成null。
class App extends React.Component源码了解了,我们在看看我们自定义组件中重写的render方法,在jsx中,render方法返回的是一个html+js的混合结构,由babel转义可以知道,实际着一些混合结构会先使用React.createElement方法
代码地址:ReactElement.js
ReactElement.createElement = function(type, config, children) {
// type对应的就是jsx中的标签,可能是div这样的html元素,也可能是一个reactElement
// config 对应的是标签上的属性
// 对应孩子节点,可能会有多个
var propName;
// Reserved names are extracted
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
//如果config存在ref
if (hasValidRef(config)) {
//设置ref值等于config中的ref
ref = config.ref;
}
//同理设置key
if (hasValidKey(config)) {
//转为字符串
key = '' + config.key;
}
//初始化self和source
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
//将config中的除了key ref __self __source放入props中
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
//设置props的children属性,children可能是一个对象/数组
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
/**
* 设置props,在这里如果type是ReactElement的话,则将defaultProps中属性填入props中,由上面代码知道
* config对应着标签中的属性,属性上的值也会将除了key ref __self __source的属性放入props中,如果有某个属性同时存在与config以及
* defaultProps中,那么会用config中的属性(也就是标签上的属性)
*/
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
//这时候返回ReactElement对象
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current, //null
props,
);
执行该方法,会返回执行 ReactElement函数
代码地址:ReactElement.js
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// 标明该元素是一个ReactElement
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner, //createElement调用时,为null
};
return element;
};
不难看出,返回了一个对象element,该对象也就是react中描述的virtual dom,需要注意,createElement的第一个参数,在react中,如果render的是原生dom标签(div,span),那么type的值就是 字符串div/span
,如果是一个自定义react组件,那么值就是一个ReactClass,即React.createClass的返回值
网友评论
加入中文注释的代码放在 https://github.com/JackyTianer/react中咯