手寫簡(jiǎn)易前端框架:Function 和 Class 組件
上篇文章我們實(shí)現(xiàn)了 vdom 的渲染,這是前端框架的基礎(chǔ)。但手寫 vdom 太麻煩,我們又支持了 jsx,用它來(lái)寫頁(yè)面更簡(jiǎn)潔。
jsx 不是直接編譯成 vdom 的,而是生成 render function,執(zhí)行之后產(chǎn)生 vdom。
中間多加了一層 render function,可以執(zhí)行一些動(dòng)態(tài)邏輯。別小看這一層 render function,它恰恰是實(shí)現(xiàn)組件的原理。
實(shí)現(xiàn)組件渲染
支持了 jsx 后,可以執(zhí)行一些動(dòng)態(tài)邏輯,比如循環(huán)、比如從上下文中取值:
const list = ['aaa', 'bbb'];
const jsx = <ul className="list">
{
list.map(item => <li className="item">{item}</li>)
}
</ul>
render(jsx, document.getElementById('root'));
這個(gè)封裝成函數(shù),然后傳入?yún)?shù)不就是組件么?
我們?cè)?render 函數(shù)里處理下函數(shù)組件的渲染:
if (isComponentVdom(vdom)) {
const props = Object.assign({}, vdom.props, {
children: vdom.children
});
const componentVdom = vdom.type(props);
return render(componentVdom, parent);
}
如果是 vdom 是一個(gè)組件,那么就創(chuàng)建 props 作為參數(shù)傳入(props 要加上 children),執(zhí)行該函數(shù)組件,拿到返回的 vdom 再渲染。
判斷組件就是根據(jù) type 是否為 function:
function isComponentVdom(vdom) {
return typeof vdom.type == 'function';
}
就這幾行代碼,我們就實(shí)現(xiàn)了函數(shù)組件。
測(cè)試下效果,聲明兩個(gè)函數(shù)組件,傳入 props:
function Item(props) {
return <li className="item" style={props.style} onClick={props.onClick}>{props.children}</li>;
}
function List(props) {
return <ul className="list">
{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'));
在瀏覽器跑一下:
我們實(shí)現(xiàn)了函數(shù)組件!
是不是非常簡(jiǎn)單!它其實(shí)就是在 jsx 的基礎(chǔ)上封裝成了函數(shù),然后傳入?yún)?shù)而已。
然后再實(shí)現(xiàn)下 class 組件:
class 組件需要聲明一個(gè)類,有 state 的屬性:
class Component {
constructor(props) {
this.props = props || {};
this.state = null;
}
setState(nextState) {
this.state = nextState;
}
}
然后渲染 vdom 的時(shí)候,如果是類組件,單獨(dú)處理下:
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 創(chuàng)建實(shí)例,然后調(diào)用 render 拿到 vdom 再渲染。
還可以加上渲染前后的生命周期函數(shù):
const instance = new vdom.type(props);
instance.componentWillMount();
const componentVdom = instance.render();
instance.dom = render(componentVdom, parent);
instance.componentDidMount();
return instance.dom;
這樣就實(shí)現(xiàn)了 class 組件。
我們測(cè)試下,聲明一個(gè) class 組件,傳入 props,設(shè)置 state:
function Item(props) {
return <li className="item" 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 className="list">
{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 組件渲染成功!
就這樣,我們實(shí)現(xiàn)了 class 組件,支持了 props 和 state。
代碼上傳到了 github:https://github.com/QuarkGluonPlasma/frontend-framework-exercize
總結(jié)
上篇文章我們支持了 jsx,它編譯產(chǎn)生 render function,執(zhí)行之后可以拿到 vdom,然后再渲染。
多了這層 render function 之后,它可以執(zhí)行很多動(dòng)態(tài)邏輯,比如條件判斷、循環(huán),從上下文取值等。
對(duì)這些邏輯封裝一下就是組件了:
- 封裝成函數(shù),傳入 props,就是函數(shù)組件
- 封裝成 class,傳入 props,設(shè)置 state 屬性,就是 class 組件
「組件本質(zhì)上是對(duì) vdom 的動(dòng)態(tài)渲染邏輯的封裝,class 和 function 是兩種封裝形式」。
實(shí)現(xiàn)了 vdom 的渲染之后,支持組件的兩種封裝形式是非常簡(jiǎn)單的事情。
至此,我們支持了 vdom 渲染、jsx 編譯、class 和 function 組件,渲染部分基本差不多了,下篇文章我們來(lái)實(shí)現(xiàn)渲染之后的更新,也就是 patch 的功能。