【React源码分析】模拟实现组件实例化及渲染

#1

引言:
用React有一段时间了,一个东西当你觉得用了一段时间之后,就会想弄明白他的内部机理。最近一直在看React源码(15.6.3版本),想搞明白一个React组件从创建到渲染经历了哪些过程,生命周期函数对应在哪执行,以及创建后一旦状态发生改变,React又是怎样去比较、所谓的diff是如何执行从而完成更新的,为什么说他高效,和vue的diff又有哪些不同。

这篇文章基于我的理解,跟React源码以及许多资料实现

现在我们要实现的功能是:

编写一个组件,初次实例化会成功执行一下生命周期方法:

  • getDefaultProps
  • getInitialState
  • componentWillMount
  • render
  • componentDidMount

重新render后(由于setState未实现,先这样),会执行以下操作:

  • componentWillReceiveProps
  • shouldComponentUpdate // return true 才render
  • render

接下来,按照编写习惯先写好我们的组件:

class HelloWorld extends Component {
  static defaultProps = {
    data: 'props'
  }

  constructor(props) {
    super(props);
    this.state = {
      data: 'state'
    }
  }

  componentWillMount() {
    console.log('componentWillMount’);
  }

  componentDidMount() {
    console.log('componentDidMount’);
  }

  componentWillReceiveProps() {
    console.log('componentWillReceiveProps’);
  }

  shouldComponentUpdate() {
    console.log('shouldComponentUpdate’);
    return true;
  }

  render() {
    return (
      <h1>
        `${this.props.data}`
      </h1>
    );
  }
}

然后执行:

ReactDOM.render(
    <HelloWorld data={'Hello World'} />,
    document.getElementById('root')
);
setTimeout(() => {
    ReactDOM.render(
        <HelloWorld data={'Hello MingYuan'} />,
        document.getElementById('root')
    );
}, 2000)

我们要的效果是页面上先渲染出’Hello World’,2秒后变成‘Hello MingYuan’。

打开控制台,依次输出:

  • getDefaultProps
  • getInitialState
  • componentWillMount
  • render
  • componentDidMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • render

这里有两个问题要注意:

第一个问题:

这里之所以能<HelloWorld />,其实是因为JSX的功劳,关于JSX如果你不太清楚,看一看这里JSX介绍

这里我们当然不会去实现一个JSX语法解析器,这不是本文的重点。我们分析他解析后的样子。

ReactDOM.render(
    React.createElement(HelloWorld, {data: ‘Hello World’}),
    document.getElementById('root')
);

第二个问题:

class Hello World extends Component{}这种写法实际上是会调用React.createClass

所以以上我们编写的组件其实长这样:

const HelloWorld = React.createClass({
    getDefaultProps() {
        console.log('getDefaultProps')
        return {
            props: 'props'
        }
    },

    getInitialState() {
        console.log('getInitialState')
        this.state = {
            state: 'state'
        }
    },

    componentWillMount() {
        console.log('componentWillMount');
    },
    
    componentDidMount() {
        console.log('componentDidMount');
    },
    
    componentWillReceiveProps(nextProps) {
        console.log('componentWillReceiveProps')
    },

    shouldComponentUpdate(nextProps) {
        console.log('shouldComponentUpdate')
        return true
    },

    render() {
        return React.createElement('h1', null, `${this.props.data}`);    
    }
});

了解了这两个问题,我们就知道怎么入手了,接下来我们实现createElementcreateClass方法。

1、createElement、createClass实现

const React = {
    createElement(type, props, children) {
        const element = {
            type,
            props: props || {}
        };

        if (children) {
            element.props.children = children;
        }

        return element;
    },
    
    createClass(spec) {
        function Constructor(props) {
            this.props = props;
            if (this.getInitialState) {
                this.getInitialState()
            }
        }

        // 构造函数的原型对象增加我们createClass时定义的方法
        Object.assign(Constructor.prototype, spec);

        if (spec.getDefaultProps) {
            Constructor.defaultProps = spec.getDefaultProps();
        }

        // 返回构造函数
        return Constructor;
    }, 
};

得到一个ReactElement React.createElement(HelloWorld)和一个Containerdocument.getElementById('root')后,把他们作为参数传入ReactDom.render函数。

首先我们看container是否已经挂载了我们的组件,如果没有,就是首次实例化渲染,有的话就仅仅执行更新。

ReactDom.render:

const ReactDom = {
    render(element, container) {
        const prevComponent = getTopLevelComponentInContainer(container);
        
        // 首次渲染 prevComponent 并不存在
        if (prevComponent) {
            return updateRootComponent(prevComponent, element);
        } else {
            return renderNewRootComponent(element, container);
        }
    }
}

getTopLevelComponentInContainer:

function getTopLevelComponentInContainer(container) {
    return container.__reactComponentInstance;
}

renderNewRootComponent就比较复杂了。

renderNewRootComponent:

function renderNewRootComponent(element, container) {
    const wrapperElement = React.createElement(TopLevelWrapper, element);

    const componentInstance = new ReactCompositeComponentWrapper(wrapperElement);

    const markUp = ReactReconciler.mountComponent(componentInstance, container);

    // _renderedComponent是根据ReactElement的type的不同,生存的ReactComponent
    container.__reactComponentInstance = componentInstance._renderedComponent;

    return markUp;
}

先来分析wrapperElement,我们之前createElement的实现,第一个参数是type,第二个参数是props。这里为什么要把顶层的ReactElement当作props,重新生成一个wrapperElement呢?

可以这样想,一个父亲ReactElement都有一个render方法,render返回一个子ReactElement,如果层级很多,就要不断的render取得子孙辈的ReactElement,然后才能操作渲染。而作为最顶层的祖先,他不是任何一个ReactElementrender方法得来的,一个很好的解决办法就是人工给他造一个父亲,wrapperElement就是人工造的父亲。

TopLevelWrapper的实现:

const TopLevelWrapper = function(props) {
        this.props = props;
};

TopLevelWrapper.prototype.render = function() {
  return this.props;
};

接下来就是包装了许多方法的ReactCompositeComponentWrapper了,先看它的构造函数。

class ReactCompositeComponentWrapper {
    constructor(element) {
            this._element = element;
    }
}

ReactReconciler:

const ReactReconciler = {
    mountComponent(ReactComponent, container) {
        return ReactComponent.mountComponent(container);
    }
};

这里只是进入了ReactCompositeComponentWrappermountComponent方法:

class ReactCompositeComponentWrapper {
    mountComponent(container) {
        // 第一次this._element.type == TopLevelWrapper
        // 第二次this._element.type == createClass的Constructor函数
        const Component = this._element.type;
        const componentInstance = new Component(this._element.props);

        this._instance = componentInstance;

        // 让构造函数的实例的__reactComponentInstance指向它的ReactCompositeComponentWrapper
        componentInstance.__reactComponentInstance = this

        if (componentInstance.componentWillMount) {
            componentInstance.componentWillMount();
        }
        
        const markup = this.performInitialMount(container);
        
        if (componentInstance.componentDidMount) {
            componentInstance.componentDidMount();
        }
        
        return markup;
    }
}

我们在const componentInstance = new ReactCompositeComponentWrapper(wrapperElement);的时候调用了构造函数,所以第一次进来的时候this._elementwrapperElement,将wrapperElement的type作为构造函数得到一个componentInstance实例,此时componentInstance实例没有任何钩子函数。接下来执行performInitialMount

ReactCompositeComponentWrapper.performInitialMount:

class ReactCompositeComponentWrapper {
    performInitialMount(container) {

        // render()返回的就是props对象,我们知道ReactElement曾经被wrapperElement当作props包起来
        const renderedElement = this._instance.render();

        // 根据ReactElement的type的不同实例化
        const child = instantiateReactComponent(renderedElement);

        this._renderedComponent = child;

        // 这里其实是递归调用,实例化了父组件,还要接着实例化子组件
        // 如果child还是一个React自定义组件(ReactCompositeComponent)的话,继续递归
        // 如果child是ReactDOMComponent的话,执行ReactDOMComponent.mountComponent,结束递归
        return ReactReconciler.mountComponent(child, container);
    }
}

performInitialMount里,this._instanceprops保存了我们的顶层ReactElement,通过this._instance的原型方法render得到这个顶层ReactElement。

这其实是做了一个统一,在之后this._instance保存的就是createClass里的Constructor了。

得到renderedElement之后,又继续递归回到mountComponentperformInitialMount,终止条件为ReactElement不再有render,也就是ReactElement是最后一代。

回到mountComponent之后,还是把代码放出来对着看:

class ReactCompositeComponentWrapper {
    mountComponent(container) {
        // 第一次this._element.type == TopLevelWrapper
        // 第二次this._element.type == createClass的Constructor函数
        const Component = this._element.type;
        const componentInstance = new Component(this._element.props);

        this._instance = componentInstance;

        // 让构造函数的实例的__reactComponentInstance指向它的ReactCompositeComponentWrapper
        componentInstance.__reactComponentInstance = this

        if (componentInstance.componentWillMount) {
            componentInstance.componentWillMount();
        }
        
        const markup = this.performInitialMount(container);
        
        if (componentInstance.componentDidMount) {
            componentInstance.componentDidMount();
        }
        
        return markup;
    }
}

这个时候componentInstance已经能在原型对象上找到componentWillMount了,所以执行componentWillMount

之后再次进入performInitialMount,这时renderedElement已经是末代(element.type === ‘string’)了,无力会天,进入ReactDOMComponent直接渲染。之后返回执行componentWillMount方法。

ReactDOMComponent就是负责真实的渲染。

ReactDOMComponent:

class ReactDOMComponent {
    constructor(element) {
        this._currentElement = element;
    }

    mountComponent(container) {
        // 创建dom元素
        const domElement = document.createElement(this._currentElement.type);
        const textNode = document.createTextNode(this._currentElement.props.children);

        domElement.appendChild(textNode);
        container.appendChild(domElement);
        
        this._hostNode = domElement;
        return domElement;
    }
}

首次实例化渲染已经模拟完成,接下来就是简单模拟实现组件的更新。

我们在renderNewRootComponent的时候已经把__reactComponentInstance保存在container里了,所以进入到updateRootComponent,在ReactCompositeComponentWrapperreceiveComponentupdateComponent

class ReactCompositeComponentWrapper {
    updateComponent(prevElement, nextElement) {
        const nextProps = nextElement.props;
        const inst = this._instance;

        // componentWillReceiveProps生命周期在这里执行
        if (inst.componentWillReceiveProps) {
            inst.componentWillReceiveProps(nextProps);
        }

        let shouldUpdate = true;

        if (inst.shouldComponentUpdate) {
            shouldUpdate = inst.shouldComponentUpdate(nextProps);
        }

        // 根据 shouldComponentUpdate 的返回结果 判断是否需要更新
        if (shouldUpdate) {
            this._performComponentUpdate(nextElement, nextProps);
        } else {
            inst.props = nextProps;
        }
    }
}

之后其实就是直接进入render渲染了。

完整代码:git地址

接下来就是研究setState了,然后完善了。

1 Like