美文网首页ReactNativeWeb前端之路让前端飞
小型React框架(实现组件实例化及渲染等部分功能)

小型React框架(实现组件实例化及渲染等部分功能)

作者: 陈小俊先生 | 来源:发表于2017-08-29 11:29 被阅读79次

    引言:
    用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时定义的方法
            Constructor.prototype = 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了,然后完善了。

    相关文章

      网友评论

        本文标题:小型React框架(实现组件实例化及渲染等部分功能)

        本文链接:https://www.haomeiwen.com/subject/hglodxtx.html