1. 程式人生 > >手寫React的Fiber架構,深入理解其原理

手寫React的Fiber架構,深入理解其原理

熟悉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 = (

Title

Jump

Article

); ``` 經過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 對於`

Title

`這樣一個簡單的節點,原生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.