React 16.7-alpha中新增了新功能:Hooks
。總結他的功能就是:讓FunctionalComponent
具備ClassComponent
的功能。前端
import React, { useState, useEffect } from 'react' function FunComp(props) { const [data, setData] = useState('initialState') function handleChange(e) { setData(e.target.value) } useEffect(() => { subscribeToSomething() return () => { unSubscribeToSomething() } }) return ( <input value={data} onChange={handleChange} /> ) } 複製代碼
按照Dan的說法,設計Hooks
主要是解決ClassComponent
的幾個問題:react
bind
,this
指向不明確這些確實是存在的問題,好比咱們若是用了react-router
+redux
+material-ui
,極可能隨便一個組件最後export
出去的代碼是醬紫的:git
export default withStyle(style)(connect(/*something*/)(withRouter(MyComponent))) 複製代碼
這就是一個4層嵌套的HOC
組件github
同時,若是你的組件內事件多,那麼你的constructor
裏面可能會醬紫:編程
class MyComponent extends React.Component { constructor() { // initiallize this.handler1 = this.handler1.bind(this) this.handler2 = this.handler2.bind(this) this.handler3 = this.handler3.bind(this) this.handler4 = this.handler4.bind(this) this.handler5 = this.handler5.bind(this) // ...more } } 複製代碼
雖然最新的class
語法能夠用handler = () => {}
來快捷綁定,但也就解決了一個聲明的問題,總體的複雜度仍是在的。redux
而後還有在componentDidMount
和componentDidUpdate
中訂閱內容,還須要在componentWillUnmount
中取消訂閱的代碼,裏面會存在不少重複性工做。最重要的是,在一個ClassComponent
中的生命週期方法中的代碼,是很難在其餘組件中複用的,這就致使了了代碼複用率低的問題。數組
還有就是class
代碼對於打包工具來講,很難被壓縮,好比方法名稱。markdown
更多詳細的你們能夠去看ReactConf
的視頻,我這裏就很少講了,這篇文章的主題是從源碼的角度講講Hooks
是如何實現的前端工程師
首先useState
是一個方法,它自己是沒法存儲狀態的react-router
其次,他運行在FunctionalComponent
裏面,自己也是沒法保存狀態的
useState
只接收一個參數initial value
,並看不出有什麼特殊的地方。因此React在一次從新渲染的時候如何獲取以前更新過的state
呢?
在開始講解源碼以前,你們先要創建一些概念:
JSX
翻譯過來以後是React.createElement
,他最終返回的是一個ReactElement
對象,他的數據解構以下:
const element = { ?typeof: REACT_ELEMENT_TYPE, // 是不是普通Element_Type // Built-in properties that belong on the element type: type, // 咱們的組件,好比`class MyComponent` key: key, ref: ref, props: props, // Record the component responsible for creating this element. _owner: owner, }; 複製代碼
這其中須要注意的是type
,在咱們寫<MyClassComponent {...props} />
的時候,他的值就是MyClassComponent
這個class
,而不是他的實例,實例是在後續渲染的過程當中建立的。
每一個節點都會有一個對應的Fiber
對象,他的數據解構以下:
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // Instance this.tag = tag; this.key = key; this.elementType = null; // 就是ReactElement的`?typeof` this.type = null; // 就是ReactElement的type this.stateNode = null; // Fiber this.return = null; this.child = null; this.sibling = null; this.index = 0; this.ref = null; this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.firstContextDependency = null; // ...others } 複製代碼
在這裏咱們須要注意的是this.memoizedState
,這個key
就是用來存儲在上次渲染過程當中最終得到的節點的state
的,每次執行render
方法以前,React會計算出當前組件最新的state
而後賦值給class
的實例,再調用render
。
因此不少不是很清楚React原理的同窗會對React的ClassComponent
有誤解,認爲state
和lifeCycle
都是本身主動調用的,由於咱們繼承了React.Component
,它裏面確定有不少相關邏輯。事實上若是有興趣能夠去看一下Component
的源碼,大概也就是100多行,很是簡單。因此在React中,class
僅僅是一個載體,讓咱們寫組件的時候更容易理解一點,畢竟組件和class
都是封閉性較強的
在知道上面的基礎以後,對於Hooks
爲何可以保存無狀態組件的原理就比較好理解了。
咱們假設有這麼一段代碼:
function FunctionalComponent () { const [state1, setState1] = useState(1) const [state2, setState2] = useState(2) const [state3, setState3] = useState(3) } 複製代碼
先來看一張圖
在咱們執行functionalComponent
的時候,在第一次執行到useState
的時候,他會對應Fiber
對象上的memoizedState
,這個屬性原來設計來是用來存儲ClassComponent
的state
的,由於在ClassComponent
中state
是一整個對象,因此能夠和memoizedState
一一對應。
可是在Hooks
中,React並不知道咱們調用了幾回useState
,因此在保存state
這件事情上,React想出了一個比較有意思的方案,那就是調用useState
後設置在memoizedState
上的對象長這樣:
{
baseState,
next,
baseUpdate,
queue,
memoizedState
}
複製代碼
咱們叫他Hook對象。這裏面咱們最須要關心的是memoizedState
和next
,memoizedState
是用來記錄這個useState
應該返回的結果的,而next
指向的是下一次useState
對應的`Hook對象。
也就是說:
hook1 => Fiber.memoizedState
state1 === hook1.memoizedState
hook1.next => hook2
state2 === hook2.memoizedState
hook2.next => hook3
state3 === hook2.memoizedState
複製代碼
每一個在FunctionalComponent
中調用的useState
都會有一個對應的Hook
對象,他們按照執行的順序以相似鏈表的數據格式存放在Fiber.memoizedState
上
重點來了:就是由於是以這種方式進行state
的存儲,因此useState
(包括其餘的Hooks)都必須在FunctionalComponent
的根做用域中聲明,也就是不能在if
或者循環中聲明,好比
if (something) { const [state1] = useState(1) } // or for (something) { const [state2] = useState(2) } 複製代碼
最主要的緣由就是你不能確保這些條件語句每次執行的次數是同樣的,也就是說若是第一次咱們建立了state1 => hook1, state2 => hook2, state3 => hook3
這樣的對應關係以後,下一次執行由於something
條件沒達成,致使useState(1)
沒有執行,那麼運行useState(2)
的時候,拿到的hook
對象是state1
的,那麼整個邏輯就亂套了,因此這個條件是必需要遵照的!
上面講了Hooks
中state
是如何保存的,那麼接下去來說講如何更新state
咱們調用的調用useState
返回的方法是醬紫的:
var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue); return [workInProgressHook.memoizedState, dispatch]; 複製代碼
調用這個方法會建立一個update
var update = { expirationTime: _expirationTime, action: action, callback: callback !== undefined ? callback : null, next: null } 複製代碼
這裏的action
是咱們調用setState1
傳入的值,而這個update
會被加入到queue
上,由於可能存在一次性調用屢次setState1
的清空(跟React的batchUpdate有關,之後有機會講。)
在收集完這全部update
以後,會調度一次React
的更新,在更新的過程當中,確定會執行到咱們的FunctionalComponent
,那麼就會執行到對應的useState
,而後咱們就拿到了Hook
對象,他保存了queue
對象表示有哪些更新存在,而後依次進行更新,拿到最新的state
保存在memoizedState
上,而且返回,最終達到了setState
的效果。
其實本質上跟ClassComponent
是差很少的,只不過由於useState
拆分了單一對象state
,因此要用一個相對獨特的方式進行數據保存,並且會存在必定的規則限制。
可是這些條件徹底不能掩蓋Hooks
的光芒,他的意義是在是太大了,讓React
這個 函數式編程範式的框架終於擺脫了要用類來建立組件的尷尬場面。事實上類的存在乎義確實不大,好比PuerComponent
如今也有對應的React.memo
來讓函數組件也能達到相同的效果。
最後,由於真的要把源碼攤開來說,就會涉及到一些其餘的源碼內容,好比workInProgress => current
的轉換,expirationTime
涉及的調度等,反而會致使你們沒法理解本篇文章的主體Hooks
,因此我在寫完完整源碼解析後又總結概括了這篇文章來單獨發佈。但願能幫助各位童鞋更好得理解Hooks
,並能大膽用到實際開發中去。
由於:真的很好用啊!!!
目前react-hot-loader
不能和hooks
一塊兒使用,詳情,因此你能夠考慮等到正式版再用。
我是Jocky,一個專一於React技巧和深度分析的前端工程師,React絕對是一個越深刻學習,越能讓你以爲他的設計精巧,思想超前的框架。關注我獲取最新的React動態,以及最深度的React學習。更多的文章看這裏