手寫簡易前端框架:function 和 class 組件

上篇文章我們實現了 vdom 的渲染,這是前端框架的基礎。但手寫 vdom 太麻煩,我們又支持了 jsx,用它來寫頁面更簡潔。

jsx 不是直接編譯成 vdom 的,而是生成 render function,執行之後產生 vdom。

中間多加了一層 render function,可以執行一些動態邏輯。別小看這一層 render function,它恰恰是實現組件的原理。

實現組件渲染

支持了 jsx 後,可以執行一些動態邏輯,比如循環、比如從上下文中取值:

const list = ['aaa''bbb'];
const jsx = <ul class>
    {
        list.map(item => <li class>{item}</li>)
    }    
</ul>

render(jsx, document.getElementById('root'));

這個封裝成函數,然後傳入參數不就是組件麼?

我們在 render 函數里處理下函數組件的渲染:

if (isComponentVdom(vdom)) {
    const props = Object.assign({}, vdom.props, {
        children: vdom.children
    });

    const componentVdom = vdom.type(props);
    return render(componentVdom, parent);
}

如果是 vdom 是一個組件,那麼就創建 props 作爲參數傳入(props 要加上 children),執行該函數組件,拿到返回的 vdom 再渲染。

判斷組件就是根據 type 是否爲 function:

function isComponentVdom(vdom) {
    return typeof vdom.type == 'function';
}

就這幾行代碼,我們就實現了函數組件。

測試下效果,聲明兩個函數組件,傳入 props:

function Item(props) {
    return <li class style={props.style} onClick={props.onClick}>{props.children}</li>;
}

function List(props) {
    return <ul class>
        {props.list.map((item, index) ={
            return <Item style={{ background: item.color }} onClick={() => alert(item.text)}>{item.text}</Item>
        })}
    </ul>;
}

const list = [
    {
        text: 'aaa',
        color: 'blue'
    },
    {
        text: 'ccc',
        color: 'orange'
    },
    {
        text: 'ddd',
        color: 'red'
    }
]

render(<List list={list}/>, document.getElementById('root'));

在瀏覽器跑一下:

我們實現了函數組件!

是不是非常簡單!它其實就是在 jsx 的基礎上封裝成了函數,然後傳入參數而已。

然後再實現下 class 組件:

class 組件需要聲明一個類,有 state 的屬性:

class Component {
    constructor(props) {
        this.props = props || {};
        this.state = null;
    }
  
    setState(nextState) {
        this.state = nextState;
    }
}

然後渲染 vdom 的時候,如果是類組件,單獨處理下:

if (isComponentVdom(vdom)) {
    const props = Object.assign({}, vdom.props, {
        children: vdom.children
    });

    if (Component.isPrototypeOf(vdom.type)) {
        const instance = new vdom.type(props);

        const componentVdom = instance.render();
        instance.dom = render(componentVdom, parent);

        return instance.dom;
    } else {
        const componentVdom = vdom.type(props);
        return render(componentVdom, parent);
    }
}

判斷如果 vdom 是 Component,就傳入 props 創建實例,然後調用 render 拿到 vdom 再渲染。

還可以加上渲染前後的生命週期函數:

const instance = new vdom.type(props);

instance.componentWillMount();

const componentVdom = instance.render();
instance.dom = render(componentVdom, parent);

instance.componentDidMount();

return instance.dom;

這樣就實現了 class 組件。

我們測試下,聲明一個 class 組件,傳入 props,設置 state:

function Item(props) {
    return <li class style={props.style} onClick={props.onClick}>{props.children}</li>;
}

class List extends Component {
    constructor(props) {
        super();
        this.state = {
            list: [
                {
                    text: 'aaa',
                    color: 'blue'
                },
                {
                    text: 'bbb',
                    color: 'orange'
                },
                {
                    text: 'ccc',
                    color: 'red'
                }
            ],
            textColor: props.textColor
        }
    }

    render() {
        return <ul class>
            {this.state.list.map((item, index) ={
                return <Item style={{ background: item.color, color: this.state.textColor}} onClick={() => alert(item.text)}>{item.text}</Item>
            })}
        </ul>;
    }
}

render(<List textColor={'pink'}/>, document.getElementById('root'));

瀏覽器跑一下:

class 組件渲染成功!

就這樣,我們實現了 class 組件,支持了 props 和 state。

代碼上傳到了 github:https://github.com/QuarkGluonPlasma/frontend-framework-exercize

總結

上篇文章我們支持了 jsx,它編譯產生 render function,執行之後可以拿到 vdom,然後再渲染。

多了這層 render function 之後,它可以執行很多動態邏輯,比如條件判斷、循環,從上下文取值等。

對這些邏輯封裝一下就是組件了:

「組件本質上是對 vdom 的動態渲染邏輯的封裝,class 和 function 是兩種封裝形式」

實現了 vdom 的渲染之後,支持組件的兩種封裝形式是非常簡單的事情。

至此,我們支持了 vdom 渲染、jsx 編譯、class 和 function 組件,渲染部分基本差不多了,下篇文章我們來實現渲染之後的更新,也就是 patch 的功能。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/T6SvUYdkf1nAaxoYO4wcgg