网上已经有不少react源码分析文档,但都是分析主流程和主要功能函数,没有一个是从reactDOM.render()入口开始分析源码把流程走通尤其是把复杂重要的细节环节走通直到把组件template编译插入网页生效。
react源码设计太复杂,react的缺点就是把事情搞太复杂了,相比之下vue用简单的编程方法也能实现同样的功能甚至更多的功能,可以说是后起之秀超越了前辈,个人设计超过了专业团队设计。
react源码分布在几十个文件中,找函数代码要在这批文件中搜索出来,这无所谓,其中最重要的源码文件如下:
React.js
ReactClass.js
ReactElement.js
ReactDOM.js
ReactMount.js
instantiateReactComponent.js
ReactComponent.js
ReactDOMComponent.js
ReactCompositeComponent.js
ReactReconciler.js
ReactUpdates.js
Transaction.js
还有react-redux两个文件:
connect.js
provider.js
首先描述一下测试项目基本环境和细节,测试项目采用minimum项目,只有一个/路由组件,显示<div>hello world</div>,如果ajax从后台获取到数据,则显示从后台获取的一个字符串,组件使用redux的store数据。
react项目入口代码:
ReactDOM.render(
<Provider store={store}>
<Router history={history} children={routes} />
</Provider>,
MOUNT_NODE
路由routes.js代码:
import { injectReducer } from 'REDUCER'
import createContainer from 'UTIL/createContainer'
const connectComponent = createContainer(
({ userData, msg }) => ({ userData, msg }),
require('ACTION/msg').default
)
export default {
path: 'http://www.likecs.com/',
getComponent (nextState, cb) {
require.ensure([], (require) => {
injectReducer('msg', require('REDUCER/msg/').default)
cb(null, connectComponent(require('COMPONENT/App').default))
}, 'App')
}
}
/路由组件App.js代码:
import React, { Component } from 'react'
export default class App extends Component {
componentWillMount () {
let _this = this
setTimeout(function() {
_this.props.fetchMsg()
}, 2000)
}
componentWillReceiveProps (nextProps) {
console.log(this)
}
render () {
return (
<div>{ this.props.msg.msgs[0] ? this.props.msg.msgs[0].content : 'hello world' }</div>
)
}
}
react项目入口方法是:
ReactDOM.render(根组件template)
相当于vue 1.0的router.start(app)或vue 2.0的new Vue(app)。
ReactDOM.render方法代码:
function (nextElement, container, callback) {
return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
},
_renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
var nextWrappedElement = ReactElement(TopLevelWrapper, null, null, null, null, null, nextElement); // 构造一个Element,第一次是构造toplevel Element
//React.createElement创建ReactElement对象(vnode),含有type,key,ref,props属性,这个过程中会调用getInitialState()初始化state。
var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
return component;
产生的component就是根组件template中第一个标签provider组件实例,里面有层层子节点,有/路由组件实例,有props属性。
那么是从这里开始层层递归到/路由组件节点的。
_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
//nextelement就是根元素节点provider组件元素
var componentInstance = instantiateReactComponent(nextElement, false); //element -> instance
function instantiateReactComponent(node, shouldHaveDebugID) { //这个方法是根据Element产生warpper instance,再执行instance.mountComponent开始编译组件节点
//instance = new ReactCompositeComponentWrapper(element); //这句相当于是下面一句
instance = new this.construct(element); // 这是instance的构造函数,就是设置一些属性,很普通
var ReactCompositeComponentMixin = { // 这是instace构造函数代码
construct: function (element) {
this.xxx = yyy;
}
}
return instance;
//instantiateReactComponent根据ReactElement的type分别创建ReactDOMComponent, ReactCompositeComponent,ReactDOMTextComponent等对象,
//不同的对象instance有不同的mountComponent方法,所以react源码文件有无数个mountComponent函数,其实html元素节点可以不当做component处理
//instantiateReactComponent被调用执行多次,每次被调用就处理一个元素节点产生一个instance,在本例minimum项目中,产生如下instance:
_instance:TopLevelWrapper
_instance:Provider // provide节点
_instance:Constructor // router节点
_instance:Constructor
_instance:Connect // /路由组件外层
_instance:App //这是/路由组件实例
ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context); //从根元素provider开始编译处理
至此处理结束,根组件template已经插入网页生效,已经从根元素递归处理到最底层元素,所以处理root元素很简单,复杂在于从root元素递归处理子节点再层层返回。
batchedUpdates会调用batchedMountComponentIntoNode,从这个函数开始追踪: