手寫React的Fiber架構,深入理解其原理
阿新 • • 發佈:2020-06-23
熟悉React的朋友都知道,React支援jsx語法,我們可以直接將HTML程式碼寫到JS中間,然後渲染到頁面上,我們寫的HTML如果有更新的話,React還有虛擬DOM的對比,只更新變化的部分,而不重新渲染整個頁面,大大提高渲染效率。到了16.x,React更是使用了一個被稱為`Fiber`的架構,提升了使用者體驗,同時還引入了`hooks`等特性。那隱藏在React背後的原理是怎樣的呢,`Fiber`和`hooks`又是怎麼實現的呢?本文會從`jsx`入手,手寫一個簡易版的React,從而深入理解React的原理。
本文主要實現了這些功能:
> 簡易版Fiber架構
>
> 簡易版DIFF演算法
>
> 簡易版函式元件
>
> 簡易版Hook: `useState`
>
> 娛樂版`Class`元件
本文程式碼地址:[https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/fiber-and-hooks](https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/fiber-and-hooks)
本文程式跑起來效果如下:
![Jun-19-2020 17-01-28](https://user-gold-cdn.xitu.io/2020/6/22/172d9dd53dceb40b?w=338&h=608&f=gif&s=2380361)
## JSX和creatElement
以前我們寫React要支援JSX還需要一個庫叫`JSXTransformer.js`,後來JSX的轉換工作都整合到了babel裡面了,babel還提供了[線上預覽的功能](https://babeljs.io/repl),可以看到轉換後的效果,比如下面這段簡單的程式碼:
```jsx
const App =
(
);
```
經過babel轉換後就變成了這樣:
![image-20200608175937104](https://user-gold-cdn.xitu.io/2020/6/22/172d9dda414edc68?w=1117&h=246&f=png&s=63611)
上面的截圖可以看出我們寫的HTML被轉換成了`React.createElement`,我們將上面程式碼稍微格式化來看下:
```javascript
var App = React.createElement(
'div',
null,
React.createElement(
'h1',
{
id: 'title',
},
'Title',
),
React.createElement(
'a',
{
href: 'xxx',
},
'Jump',
),
React.createElement(
'section',
null,
React.createElement('p', null, 'Article'),
),
);
```
從轉換後的程式碼我們可以看出`React.createElement`支援多個引數:
> 1. type,也就是節點型別
> 2. config, 這是節點上的屬性,比如`id`和`href`
> 3. children, 從第三個引數開始就全部是children也就是子元素了,子元素可以有多個,型別可以是簡單的文字,也可以還是`React.createElement`,如果是`React.createElement`,其實就是子節點了,子節點下面還可以有子節點。這樣就用`React.createElement`的巢狀關係實現了HTML節點的樹形結構。
讓我們來完整看下這個簡單的React頁面程式碼:
![image-20200608180112829](https://user-gold-cdn.xitu.io/2020/6/22/172d9dfce30d7f82?w=350&h=365&f=png&s=33209)
渲染在頁面上是這樣:
![image-20200608180139663](https://user-gold-cdn.xitu.io/2020/6/22/172d9e01d9b3ef69?w=153&h=156&f=png&s=4578)
這裡面用到了React的地方其實就兩個,一個是JSX,也就是`React.createElement`,另一個就是`ReactDOM.render`,所以我們手寫的第一個目標就有了,就是`createElement`和`render`這兩個方法。
### 手寫createElement
對於` `這樣一個簡單的節點,原生DOM也會附加一大堆屬性和方法在上面,所以我們在`createElement`的時候最好能將它轉換為一種比較簡單的資料結構,只包含我們需要的元素,比如這樣:
```javascript
{
type: 'h1',
props: {
id: 'title',
children: 'Title'
}
}
```
有了這個資料結構後,我們對於DOM的操作其實可以轉化為對這個資料結構的操作,新老DOM的對比其實也可以轉化為這個資料結構的對比,這樣我們就不需要每次操作都去渲染頁面,而是等到需要渲染的時候才將這個資料結構渲染到頁面上。這其實就是虛擬DOM!而我們`createElement`就是負責來構建這個虛擬DOM的方法,下面我們來實現下:
```javascript
function createElement(type, props, ...children) {
// 核心邏輯不復雜,將引數都塞到一個物件上返回就行
// children也要放到props裡面去,這樣我們在元件裡面就能通過this.props.children拿到子元素
return {
type,
props: {
...props,
children
}
}
}
```
上述程式碼是React的`createElement`簡化版,對原始碼感興趣的朋友可以看這裡:[https://github.com/facebook/react/blob/60016c448bb7d19fc989acd05dda5aca2e124381/packages/react/src/ReactElement.js#L348](https://github.com/facebook/react/blob/60016c448bb7d19fc989acd05dda5aca2e124381/packages/react/src/ReactElement.js#L348)
### 手寫render
上述程式碼我們用`createElement`將JSX程式碼轉換成了虛擬DOM,那真正將它渲染到頁面的函式是`render`,所以我們還需要實現下這個方法,通過我們一般的用法`ReactDOM.
Title
JumpArticle