React 架構的演變 - Hooks 的實現

這是這個系列的最後一篇文章了,終於收尾了🐶 。

React Hooks 能夠說徹底顛覆了以前 Class Component 的寫法,進一步加強了狀態複用的能力,讓 Function Component 也具備了內部狀態,對於我我的來講,更加喜歡 Hooks 的寫法。固然若是你是一個使用 Class Component 的老手,初期上手時會以爲很苦惱,畢竟以前沉澱的不少 HOC、Render Props 組件基本無法用。並且以前的 Function Component 是無反作用的無狀態組件,如今又能經過 Hooks 引入狀態,看起來真的很讓人疑惑。Function Component 的另外一個優點就是能夠徹底告別 this ,在 Class Component 裏面 this 真的是一個讓人討厭的東西😶。react

Hook 如何與組件關聯

在以前的文章中屢次提到,Fiber 架構下的 updateQueueeffectList 都是鏈表的數據結構,而後掛載的 Fiber 節點上。而一個函數組件內全部的 Hooks 也是經過鏈表的形式存儲的,最後掛載到 fiber.memoizedState 上。git

function App() {
  const [num, updateNum] = useState(0)

  return <div
    onClick={() => updateNum(num => num + 1)}
  >{ num }</div>
}

export default App

咱們先簡單看下,調用 useState 時,構造鏈表的過程:github

var workInProgressHook = null
var HooksDispatcherOnMount = {
  useState: function (initialState) {
    return mountState(initialState)
  }
}

function function mountState(initialState) {
  // 新的 Hook 節點
  var hook = mountWorkInProgressHook()
  // 緩存初始值
  hook.memoizedState = initialState
  // 構造更新隊列,相似於 fiber.updateQueue
  var queue = hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedState: initialState
  }
  // 用於派發更新
  var dispatch = queue.dispatch = dispatchAction.bind(
    null, workInProgress, queue
  )
  // [num, updateNum] = useState(0)
  return [hook.memoizedState, dispatch]
}

function mountWorkInProgressHook() {
  var hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  }

  if (workInProgressHook === null) {
    // 構造鏈表頭節點
    workInProgress.memoizedState = workInProgressHook = hook
  } else {
    // 若是鏈表已經存在,在掛載到 next
    workInProgressHook = workInProgressHook.next = hook
  }

  return workInProgressHook
}

Hook

若是此時有兩個 Hook,第二個 Hook 就會掛載到第一個 Hook 的 next 屬性上。數組

function App() {
  const [num, updateNum] = useState(0)
  const [str, updateStr] = useState('value: ')

  return <div
    onClick={() => updateNum(num => num + 1)}
  >{ str } { num }</div>
}

export default App

Hook

Hook 的更新隊列

Hook 經過 .next 彼此相連,而每一個 Hook 對象下,還有個 queue 字段,該字段和 Fiber 節點上的 updateQueue 同樣,是一個更新隊列在,上篇文章 《React 架構的演變-更新機制》中有講到,React Fiber 架構中,更新隊列經過鏈表結構進行存儲。緩存

class App extends React.Component {
  state = { val: 0 }
  click () {
    for (let i = 0; i < 3; i++) {
      this.setState({ val: this.state.val + 1 })
    }
  }
  render() {
    return <div onClick={() => {
      this.click()
    }}>val: { this.state.val }</div>
  }
}

點擊 div 以後,產生的 3 次 setState 經過鏈表的形式掛載到 fiber.updateQueue 上,待到 MessageChannel 收到通知後,真正執行更新操做時,取出更新隊列,將計算結果更新到 fiber.memoizedState 數據結構

setState

hook.queue 的邏輯和 fiber.updateQueue 的邏輯也是徹底一致的。架構

function App() {
  const [num, updateNum] = useState(0)

  return <div
    onClick={() => {
      // 連續更新 3 次
      updateNum(num => num + 1)
      updateNum(num => num + 1)
      updateNum(num => num + 1)
    }}
  >
    { num }
  </div>
}

export default App;
var dispatch = queue.dispatch = dispatchAction.bind(
  null, workInProgress, queue
)
// [num, updateNum] = useState(0)
return [hook.memoizedState, dispatch]

調用 useState 的時候,返回的數組第二個參數爲 dispatch,而 dispatchdispatchAction bind 後獲得。異步

function dispatchAction(fiber, queue, action) {
  var update = {
    next: null,
    action: action,
    // 省略調度相關的參數...
  };

  var pending = queue.pending
  if (pending === null) {
    update.next = update
  } else {
    update.next = pending.next
    pending.next = update
  }
  queue.pending = update

  // 執行更新
  scheduleUpdateOnFiber()
}

能夠看到這裏構造鏈表的方式與 fiber.updateQueue 一模一樣。以前咱們經過 updateNumnum 連續更新了 3 次,最後造成的更新隊列以下:函數

更新隊列

函數組件的更新

前面的文章分享過,Fiber 架構下的更新流程分爲遞(beginWork)、歸(completeWork)兩個步驟,在 beginWork 中,會依據組件類型進行 render 操做構造子組件。this

function beginWork(current, workInProgress) {
  switch (workInProgress.tag) {
    // 其餘類型組件代碼省略...
    case FunctionComponent: {
      // 這裏的 type 就是函數組件的函數
      // 例如,前面的 App 組件,type 就是 function App() {}
      var Component = workInProgress.type
      var resolvedProps = workInProgress.pendingProps
      // 組件更新
      return updateFunctionComponent(
        current, workInProgress, Component, resolvedProps
      )
    }
  }
}

function updateFunctionComponent(
    current, workInProgress, Component, nextProps
) {
  // 構造子組件
  var nextChildren = renderWithHooks(
    current, workInProgress, Component, nextProps
  )
  reconcileChildren(current, workInProgress, nextChildren)
  return workInProgress.child
}

看名字就能看出來,renderWithHooks 方法就是構造帶 Hooks 的子組件。

function renderWithHooks(
    current, workInProgress, Component, props
) {
  if (current !== null && current.memoizedState !== null) {
    ReactCurrentDispatcher.current = HooksDispatcherOnUpdate
  } else {
    ReactCurrentDispatcher.current = HooksDispatcherOnMount
  }
  var children = Component(props)
  return children
}

從上面的代碼能夠看出,函數組件更新或者首次渲染時,本質就是將函數取出執行了一遍。不一樣的地方在於給 ReactCurrentDispatcher 進行了不一樣的賦值,而 ReactCurrentDispatcher 的值最終會影響 useState 調用不一樣的方法。

根據以前文章講過的雙緩存機制,current 存在的時候表示是更新操做,不存在的時候表示首次渲染。

function useState(initialState) {
  // 首次渲染時指向 HooksDispatcherOnMount
  // 更新操做時指向 HooksDispatcherOnUpdate
  var dispatcher = ReactCurrentDispatcher.current
  return dispatcher.useState(initialState)
}

HooksDispatcherOnMount.useState 的代碼前面已經介紹過,這裏再也不着重介紹。

// HooksDispatcherOnMount 的代碼前面已經介紹過
var HooksDispatcherOnMount = {
  useState: function (initialState) {
    return mountState(initialState)
  }
}

咱們重點看看 HooksDispatcherOnMount.useState 的邏輯。

var HooksDispatcherOnUpdateInDEV = {
  useState: function (initialState) {
    return updateState()
  }
}

function updateState() {
  // 取出當前 hook
  workInProgressHook = nextWorkInProgressHook
  nextWorkInProgressHook = workInProgressHook.next

  var hook = nextWorkInProgressHook
  var queue = hook.queue
  var pendingQueue = queue.pending

  // 處理更新
  var first = pendingQueue.next
  var state = hook.memoizedState
  var update = first

  do {
    var action = update.action
    state = typeof action === 'function' ? action(state) : action

    update = update.next;
  } while (update !== null && update !== first)


  hook.memoizedState = state

  var dispatch = queue.dispatch
  return [hook.memoizedState, dispatch]
}

若是有看以前的 setState 的代碼,這裏的邏輯實際上是同樣的。將更新對象的 action 取出,若是是函數就執行,若是不是函數就直接對 state 進行替換操做。

總結

React 系列的文章終於寫完了,這一篇文章應該是最簡單的一篇,若是想拋開 React 源碼,單獨看 Hooks 實現能夠看這篇文章:《React Hooks 原理》。Fiber 架構爲了可以實現循環的方式更新,將全部涉及到數據的地方結構都改爲了鏈表,這樣的優點就是能夠隨時中斷,爲異步模式讓路,Fiber 樹就像一顆聖誕樹,上面掛滿了各類彩燈(alternateEffectListupdateQueueHooks)。

推薦你們能夠將這個系列從頭至尾看一遍,相信會特別有收穫的。

image

相關文章
相關標籤/搜索