手寫簡易前端框架:function 和 class 元件
上篇文章我們實現了 vdom 的渲染,這是前端框架的基礎。但手寫 vdom 太麻煩,我們又支援了 jsx,用它來寫頁面更簡潔。
jsx 不是直接編譯成 vdom 的,而是生成 render function,執行之後產生 vdom。
中間多加了一層 render function,可以執行一些動態邏輯。別小看這一層 render function,它恰恰是實現元件的原理。
實現元件渲染
支援了 jsx 後,可以執行一些動態邏輯,比如迴圈、比如從上下文中取值:
const list = ['aaa', 'bbb'];
const jsx = <ul className="list">
{
list.map(item => <li className="item">{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 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'));
在瀏覽器跑一下:
我們實現了函式元件!
是不是非常簡單!它其實就是在 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 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 元件渲染成功!
就這樣,我們實現了 class 元件,支援了 props 和 state。
程式碼上傳到了 github:https://github.com/QuarkGluonPlasma/frontend-framework-exercize
總結
上篇文章我們支援了 jsx,它編譯產生 render function,執行之後可以拿到 vdom,然後再渲染。
多了這層 render function 之後,它可以執行很多動態邏輯,比如條件判斷、迴圈,從上下文取值等。
對這些邏輯封裝一下就是元件了:
-
封裝成函式,傳入 props,就是函式元件
-
封裝成 class,傳入 props,設定 state 屬性,就是 class 元件
「元件本質上是對 vdom 的動態渲染邏輯的封裝,class 和 function 是兩種封裝形式」。
實現了 vdom 的渲染之後,支援元件的兩種封裝形式是非常簡單的事情。
至此,我們支援了 vdom 渲染、jsx 編譯、class 和 function 元件,渲染部分基本差不多了,下篇文章我們來實現渲染之後的更新,也就是 patch 的功能。
- 從根上理解 React Hooks 的閉包陷阱
- 談談我這些年對前端狀態管理的理解
- 幾個一看就會的 Chrome Devtools 小技巧
- 理清 HTTP 之下的 TCP 流程,讓你的 HTTP 水平更上一層
- 快速理解 TypeScript 的逆變和協變
- React Hooks 的實現必須依賴 Fiber 麼?
- 什麼?函式型別過載還可以動態生成?
- 編譯 ts 程式碼用 tsc 還是 babel?
- MobX 實現原理揭祕
- 私有屬性的 6 種實現方式,你用過幾種?
- 兩種給 Http 新增狀態的方式,都不完美
- 幾個一看就會的 TypeScript 小技巧
- Nest.js 是如何實現 AOP 架構的?
- React Hooks 的原理,有的簡單有的不簡單
- 位元組一面,面試官問我Vue3原始碼,我說……
- 從 IP 到 IP,聊聊計算機網路中那些“沒用的”知識
- 真實案例說明 TypeScript 型別體操的意義
- 能用 AST 搞明白的正則語法,就不需要看文件
- 粉絲破萬了,講講頭像背後的故事
- SSR 和前端編譯,在這點上是一樣的