网上已经有不少 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: '/',
- 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, 从这个函数开始追踪:
function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) { // 从根元素 provider 开始
//batchedMountComponentIntoNode 以 transaction 事务的形式调用 mountComponentIntoNode
transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReu // 从根元素 provider 开始
var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, //mountComponent 相当于 compile, 从根节点开始编译
- //Reconciler.mountComponent 就是执行 instance 里面的 mountComponent, 在执行 performInitialMount 时会递归调用自己.
- //mountComponent 的目的是解析得到每个节点的 HTML 代码(最后插入网页生效),react 叫做 markup, 是类似 vue 的 vnode 对象.
- Reconciler.mountComponent: function (internalInstance, transaction,
- var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
- // 对于 provider 这样的控制组件标签来说, 产生的 html 代码就是一个不可见的 comment 文本
- return markup;
- // internalInstance 是不同的 component 实例, 最典型的 component 节点类型是 html 元素节点比如 < div > 和组件元素节点比如 < App>
Reconciler.mountComponent 会递归调用自己完成从根元素递归到最底层元素 < div>, 是 react 源码的最核心最关键最牛的代码, 因为前端代码要递归 html 元素 tree, 这是与后台代码不同的, 也是非常复杂的, 一旦递归元素 tree, 就要开始晕菜了, 但代码效率巨高, 一个递归就完事了, 递归也是最体现程序牛的地方, 人工智能自我学习肯定也是要用程序递归技术的.
不同的 instance 有不同的 mountComponent 方法, 我们先来看组件元素的 mountComponent 方法:
- mountComponent: function (transaction, hostParent, hostContainerInfo, context) { //mountComponent 其实就是编译节点的意思, react 把一切节点视为 component
- var publicProps = this._currentElement.props;
- var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
- //inst 就是 new 组件实例, 那么是在 mountComponent 阶段初始化组件实例的, new 组件实例之后, 执行其 render()方法就又产生 Element, 就又需要递归循环 element -> instance -> instance.mountComponent -> inst(组件实例) -> render() -> element 如此递归到子节点
- _constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {
- return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
- _constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
- var Component = this._currentElement.type; //type 就是组件构造函数(组件定义代码)
- return new Component(publicProps, publicContext, updateQueue); //App 组件外套 connect 组件, 这就是 new Connect()组件实例的位置, 找到这个位置在分析 react 源码的道路上就前进了一大步, 因为组件定义代码无外乎就是定义一些属性, 框架肯定准备了一个组件基类, 到时一合并, 再 new 实例, 这是 js 唯一的机制, 不可能有其它方法, 找到这个位置, 再前后去追踪, 就能看懂框架到底是如何初始化组件实例的, 我们定义的组件代码到底到底是如何被执行的.
- inst.props = publicProps; // double set this.props
- this._instance = inst; //new 组件实例保存在 instance 中, 只要执行 instance 的方法, 就可以从 this 取回 inst 实例, 再执行 inst 实例里面的 render()方法产生一个 Element 递归下去
- markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
- //inst 保存在 this 实例中, 调用 performInitialMount 时无需传递 inst,renderedElement 是空的
- return markup; //markup 就是编译产生的结果, 相当于 vnode, 含 html 代码
- performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
- renderedElement = this._renderValidatedComponent(); // 执行 inst.render()产生 Element, 每种组件 inst 有自己的 render 方法, provder/router/connect/app 组件都有自己的 render 方法, app 的 render 方法是应用写的, 系统组件的 render 方法都是事先设计好的, 比如 connect 的 render 方法, 还有一个 router-context 组件
- //app 的 render 方法里面是 jsx 语法, 编译时每个节点已经转换为 createElement(), 所以 render 方法就是返回一个根元素 Element, 它里面有多少子元素再递归处理
- var child = this._instantiateReactComponent(renderedElement, // 根据元素类型生成 instance
- this._renderedComponent = child;
- var markup = ReactReconciler.mountComponent(child, // 在这里递归调用 Reconciler.mountComponent, 处理下一个子节点 child, 是前面根据 Element 生成的
- return markup;
这是组件 component 的 mountComponet 编译方法, 再来看 html 元素 component 的 mountComponent 编译方法:
- //ReactDOMComponent 是针对 html 元素, 在这个 minimum 项目中, 根组件 template 中只有一个 < div > 元素节点
- ReactDOMComponent.Mixin = {
- mountComponent: function (transaction, hostParent, hostContainerInfo, context) { // 只针对 < div > 执行一次
- var tagContent = this._createContentMarkup(transaction, props, context);
- _createContentMarkup: function (transaction, props, context) {
- var mountImages = this.mountChildren(childrenToUse, transaction, context);
- mountChildren: function (nestedChildren, transaction, context) {
- var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
- _reconcilerInstantiateChildren: function (nestedChildren, transaction, context) {
- return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context);
- instantiateChildren: function (nestedChildNodes, transaction, context, ) {
- return instantiateChild(childInsts, child, name, selfDebugID);
- function instantiateChild(childInstances, child, name, selfDebugID) {
- childInstances[name] = instantiateReactComponent(child, true);
再回到 mountComponentIntoNode 看 Reconciler.mountComponent 从根元素 provider 开始递归编译子节点之后再执行什么:
function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReu
var markup = ReactReconciler.mountComponent(wrapperInstance, transaction,
ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
// 这是递归完成之后执行的方法, 就是把编译结果插入网页生效, 编译结果就是一个 < div > 字符串</div>, 具体的方法无非是用 innerHTML/Context 方法, 或者用 appendChild 方法, 本文忽略
至此可以小结一下从根 Element 开始的处理流程:
Element -> wrapper instance -> instance.mountComponent(compile) -> new instance._currentElement.type()组件实例 -> 组件实例. render()产生 Element -> 递归子节点
元素树结构:
provider->router->connect->app
每层元素根据类型创建 instance, 执行其 mountComponent, 再 new 组件实例 inst, 再执行组件实例 inst 的 render()方法产生一个 element, 再递归.
connect 的 render 方法:
- Connect.prototype.render = function render() {
- this.renderedElement = (0, _react.createElement)(WrappedComponent, this.mergedProps);
当 store 中的属性变化触发执行 connect 组件的 render 方法时, 可以看到, 它产生的 Element 是 App 组件元素, 那么递归编译处理就是编译 App 组件, 就是更新 App 组件, connect 是 App 的代理组件, App 组件并没有定义 props 属性, 也没有定义如何更新.
router 的 render 方法:
- render: function render(props) {
- return _react2.default.createElement(_RouterContext2.default, props);
- }
router 也会创建一个 element, 带路由参数.
来源: https://www.cnblogs.com/pzhu1/p/8884807.html