如何实现自己的react(3)组件

484 阅读3分钟

思路

一开始完全没有任何参照的时候,我自己想着“要如何实现react的组件”,着实有些摸不着头脑。先从比较简单的函数式组件开始: 尝试使用我们之前完成的render来渲染函数式组件:

function Dinner(props) {
    return <div>
        <h1>
            welcome to my restaurant
        </h1>
        <p>
            dinner has {props.menu}
        </p>
    </div>
}

const el = <Dinner menu="chicken, fish and fruits" />

React.render(el, document.getElementById("root"));

在npm run start之后,可以看见控制台的报错和打印信息,目前我们实现的render并不能解析组件:

image.png

函数组件

根据控制台信息,我们可以发现当传入函数组件时,vDom的tag从字符串变成了一个函数,这个函数就是我们自定义的函数组件,而attrs就是我们要传入的props。 所以将createElement从仅传回tag、attrs和children改成可以接受一个函数类型的tag,并将attrs作为参数运行。

function createElement(tag, attrs = {}, ...children) {
    if (typeof tag === 'function') {
        return tag(attrs);
    }
    return {
        tag,
        attrs,
        children // children可能不止一个,所以使用了...运算符
    };
}

然后npm run start后,打开页面,发现我们自定义的组件出现了

image.png

类组件

类组件通常使用Class MyComponent extends React.Component来定义,然后就可以使用react的setState,生命周期等内容。

class Page extends React.MyComponent {
    constructor(props) {
        super(props);
        this.state = {
            temperature: 28,
        };
    }

    render() {
        const { temperature } = this.state;
        return (
            <div>
                今天的温度是{temperature}度
                <br />
                <button onClick={() => {
                    this.setState({
                        temperature: this.state.temperature + 1,
                    });
                }}>增加一度!</button>
            </div>
        )
    }
}

所以我们应该像React.Component一样,定义我们基类,让继承了这个基类的组件成为react的类组件。

// 基类
class MyComponent {
    constructor(props) {
        this.isReactComponent = true;
        this.state = {};
        this.props = props;
    }

    setState(changedState) {
        const {...oldState} = this.state;
        Object.assign(this.state, changedState);
        // setState后触发重新渲染
        this.render(this, this.container);
    }
}

然后createElement时,虽然类组件本质也是一个function,但它需要通过构造函数的方式去初始化,并且调用它自己的render,如果直接使用,会打印如下信息:

image.png 因为我们在mountComponent中,需要判断vDom是否为类组件,类组件需要返回自己的render()中的内容作为渲染的虚拟dom。MyComponent中的isReactComponent就是帮助我们判断。

if (vDom.isReactComponent) {
    const _tempDom = vDom;
    _tempDom._container = container;
    vDom = _tempDom.render();
}

然后运行上方的,发现渲染成功

image.png 但是问题是:点击“增加一度”,并没有触发state变化后的再次渲染,再次触发渲染需要上一次渲染时的container属性,所以我们再将container带入

class MyComponent {
    constructor(props) {
        this.isReactComponent = true;
        this.state = {};
        this.props = props;
    }

    setState(changedState) {
        Object.assign(this.state, changedState);
        // setState后触发重新渲染
        render(this, this._container);
    }
}

function render(vDom, container) {
    // 清空上一次挂载的内容,准备挂载新的内容
    console.log("render start");
    container.innerHTML = "";
    container && container.appendChild(mountComponent(vDom, container));
    console.log("render end");
}

最后尝试,发现成功!

image.png

总结

React组件分为函数组件和类组件: 函数组件需要在初始化时,将props作为参数直接执行此函数,return的内容就是要渲染的虚拟dom。 类组件有自己的render()内容,所以初始化组件时需要区分,然后调用类组件自己的构造函数,并真实render()的是类组件render()后的结果!
之后会开始讨论state和props改变,及相关生命周期。
PS:贴出来的代码会随着不断更新而变更,可能会有部分有问题的情况,等整个系列结束后会贴完整代码^ ^,有疑问的地方也参考了网络上的一些结果,但是也希望自己通过这个过程理解react~