React 架构的演变 - Hooks 的实现

开发 架构
React Hooks 可以说完全颠覆了之前 Class Component 的写法,进一步增强了状态复用的能力,让 Function Component 也具有了内部状态,对于我个人来说,更加喜欢 Hooks 的写法。

[[348911]]

React Hooks 可以说完全颠覆了之前 Class Component 的写法,进一步增强了状态复用的能力,让 Function Component 也具有了内部状态,对于我个人来说,更加喜欢 Hooks 的写法。当然如果你是一个使用 Class Component 的老手,初期上手时会觉得很苦恼,毕竟之前沉淀的很多 HOC、Render Props 组件基本没法用。而且之前的 Function Component 是无副作用的无状态组件,现在又能通过 Hooks 引入状态,看起来真的很让人疑惑。Function Component 的另一个优势就是可以完全告别 this ,在 Class Component 里面 this 真的是一个让人讨厌的东西?? 。

Hook 如何与组件关联

在之前的文章中多次提到,Fiber 架构下的 updateQueue、effectList 都是链表的数据结构,然后挂载的 Fiber 节点上。而一个函数组件内所有的 Hooks 也是通过链表的形式存储的,最后挂载到 fiber.memoizedState 上。

  1. function App() { 
  2.   const [num, updateNum] = useState(0) 
  3.  
  4.   return <div 
  5.     onClick={() => updateNum(num => num + 1)} 
  6.   >{ num }</div> 
  7.  
  8. export default App 

我们先简单看下,调用 useState 时,构造链表的过程:

  1. var workInProgressHook = null 
  2. var HooksDispatcherOnMount = { 
  3.   useState: function (initialState) { 
  4.     return mountState(initialState) 
  5.   } 
  6.  
  7. function function mountState(initialState) { 
  8.   // 新的 Hook 节点 
  9.   var hook = mountWorkInProgressHook() 
  10.   // 缓存初始值 
  11.   hook.memoizedState = initialState 
  12.   // 构造更新队列,类似于 fiber.updateQueue 
  13.   var queue = hook.queue = { 
  14.     pending: null
  15.     dispatch: null
  16.     lastRenderedState: initialState 
  17.   } 
  18.   // 用于派发更新 
  19.   var dispatch = queue.dispatch = dispatchAction.bind( 
  20.     null, workInProgress, queue 
  21.   ) 
  22.   // [num, updateNum] = useState(0) 
  23.   return [hook.memoizedState, dispatch] 
  24.  
  25. function mountWorkInProgressHook() { 
  26.   var hook = { 
  27.     memoizedState: null
  28.     baseState: null
  29.     baseQueue: null
  30.     queue: null
  31.     nextnull 
  32.   } 
  33.  
  34.   if (workInProgressHook === null) { 
  35.     // 构造链表头节点 
  36.     workInProgress.memoizedState = workInProgressHook = hook 
  37.   } else { 
  38.     // 如果链表已经存在,在挂载到 next 
  39.     workInProgressHook = workInProgressHook.next = hook 
  40.   } 
  41.  
  42.   return workInProgressHook 

如果此时有两个 Hook,第二个 Hook 就会挂载到第一个 Hook 的 next 属性上。

  1. function App() { 
  2.   const [num, updateNum] = useState(0) 
  3.   const [str, updateStr] = useState('value: '
  4.  
  5.   return <div 
  6.     onClick={() => updateNum(num => num + 1)} 
  7.   >{ str } { num }</div> 
  8.  
  9. export default App 

Hook

Hook 的更新队列

Hook 通过 .next 彼此相连,而每个 Hook 对象下,还有个 queue 字段,该字段和 Fiber 节点上的 updateQueue 一样,是一个更新队列在,上篇文章 《React 架构的演变-更新机制》中有讲到,React Fiber 架构中,更新队列通过链表结构进行存储。

  1. class App extends React.Component { 
  2.   state = { val: 0 } 
  3.   click () { 
  4.     for (let i = 0; i < 3; i++) { 
  5.       this.setState({ val: this.state.val + 1 }) 
  6.     } 
  7.   } 
  8.   render() { 
  9.     return <div onClick={() => { 
  10.       this.click() 
  11.     }}>val: { this.state.val }</div> 
  12.   } 

点击 div 之后,产生的 3 次 setState 通过链表的形式挂载到 fiber.updateQueue 上,待到 MessageChannel 收到通知后,真正执行更新操作时,取出更新队列,将计算结果更新到 fiber.memoizedState。

setState

而 hook.queue 的逻辑和 fiber.updateQueue 的逻辑也是完全一致的。

  1. function App() { 
  2.   const [num, updateNum] = useState(0) 
  3.  
  4.   return <div 
  5.     onClick={() => { 
  6.       // 连续更新 3 次 
  7.       updateNum(num => num + 1) 
  8.       updateNum(num => num + 1) 
  9.       updateNum(num => num + 1) 
  10.     }} 
  11.   > 
  12.     { num } 
  13.   </div> 
  14.  
  15. export default App; 
  16. var dispatch = queue.dispatch = dispatchAction.bind( 
  17.   null, workInProgress, queue 
  18. // [num, updateNum] = useState(0) 
  19. return [hook.memoizedState, dispatch] 

调用 useState 的时候,返回的数组第二个参数为 dispatch,而 dispatch 由 dispatchAction bind 后得到。

  1. function dispatchAction(fiber, queue, action) { 
  2.   var update = { 
  3.     nextnull
  4.     actionaction
  5.     // 省略调度相关的参数... 
  6.   }; 
  7.  
  8.   var pending = queue.pending 
  9.   if (pending === null) { 
  10.     update.next = update 
  11.   } else { 
  12.     update.next = pending.next 
  13.     pending.next = update 
  14.   } 
  15.   queue.pending = update 
  16.  
  17.   // 执行更新 
  18.   scheduleUpdateOnFiber() 

可以看到这里构造链表的方式与 fiber.updateQueue 如出一辙。之前我们通过 updateNum 对 num 连续更新了 3 次,最后形成的更新队列如下:

更新队列

函数组件的更新

前面的文章分享过,Fiber 架构下的更新流程分为递(beginWork)、归(completeWork)两个步骤,在 beginWork 中,会依据组件类型进行 render 操作构造子组件。

  1. function beginWork(current, workInProgress) { 
  2.   switch (workInProgress.tag) { 
  3.     // 其他类型组件代码省略... 
  4.     case FunctionComponent: { 
  5.       // 这里的 type 就是函数组件的函数 
  6.       // 例如,前面的 App 组件,type 就是 function App() {} 
  7.       var Component = workInProgress.type 
  8.       var resolvedProps = workInProgress.pendingProps 
  9.       // 组件更新 
  10.       return updateFunctionComponent( 
  11.         current, workInProgress, Component, resolvedProps 
  12.       ) 
  13.     } 
  14.   } 
  15.  
  16. function updateFunctionComponent( 
  17.  current, workInProgress, Component, nextProps 
  18. ) { 
  19.   // 构造子组件 
  20.   var nextChildren = renderWithHooks( 
  21.     current, workInProgress, Component, nextProps 
  22.   ) 
  23.   reconcileChildren(current, workInProgress, nextChildren) 
  24.   return workInProgress.child 

看名字就能看出来,renderWithHooks 方法就是构造带 Hooks 的子组件。

  1. function renderWithHooks( 
  2.  current, workInProgress, Component, props 
  3. ) { 
  4.   if (current !== null && current.memoizedState !== null) { 
  5.     ReactCurrentDispatcher.current = HooksDispatcherOnUpdate 
  6.   } else { 
  7.     ReactCurrentDispatcher.current = HooksDispatcherOnMount 
  8.   } 
  9.   var children = Component(props) 
  10.   return children 

从上面的代码可以看出,函数组件更新或者首次渲染时,本质就是将函数取出执行了一遍。不同的地方在于给 ReactCurrentDispatcher 进行了不同的赋值,而 ReactCurrentDispatcher 的值最终会影响 useState 调用不同的方法。

根据之前文章讲过的双缓存机制,current 存在的时候表示是更新操作,不存在的时候表示首次渲染。

  1. function useState(initialState) { 
  2.   // 首次渲染时指向 HooksDispatcherOnMount 
  3.   // 更新操作时指向 HooksDispatcherOnUpdate 
  4.   var dispatcher = ReactCurrentDispatcher.current 
  5.   return dispatcher.useState(initialState) 

HooksDispatcherOnMount.useState 的代码前面已经介绍过,这里不再着重介绍。

  1. // HooksDispatcherOnMount 的代码前面已经介绍过 
  2. var HooksDispatcherOnMount = { 
  3.   useState: function (initialState) { 
  4.     return mountState(initialState) 
  5.   } 

我们重点看看 HooksDispatcherOnMount.useState 的逻辑。

  1. var HooksDispatcherOnUpdateInDEV = { 
  2.   useState: function (initialState) { 
  3.     return updateState() 
  4.   } 
  5.  
  6. function updateState() { 
  7.   // 取出当前 hook 
  8.   workInProgressHook = nextWorkInProgressHook 
  9.   nextWorkInProgressHook = workInProgressHook.next 
  10.  
  11.   var hook = nextWorkInProgressHook 
  12.   var queue = hook.queue 
  13.   var pendingQueue = queue.pending 
  14.  
  15.   // 处理更新 
  16.   var first = pendingQueue.next 
  17.   var state = hook.memoizedState 
  18.   var update = first 
  19.  
  20.   do { 
  21.     var action = update.action 
  22.     state = typeof action === 'function' ? action(state) : action 
  23.  
  24.     update = update.next
  25.   } while (update !== null && update !== first
  26.  
  27.  
  28.   hook.memoizedState = state 
  29.  
  30.   var dispatch = queue.dispatch 
  31.   return [hook.memoizedState, dispatch] 

如果有看之前的 setState 的代码,这里的逻辑其实是一样的。将更新对象的 action 取出,如果是函数就执行,如果不是函数就直接对 state 进行替换操作。

总结

React 系列的文章终于写完了,这一篇文章应该是最简单的一篇,如果想抛开 React 源码,单独看 Hooks 实现可以看这篇文章:《React Hooks 原理》。Fiber 架构为了能够实现循环的方式更新,将所有涉及到数据的地方结构都改成了链表,这样的优势就是可以随时中断,为异步模式让路,Fiber 树就像一颗圣诞树,上面挂满了各种彩灯(alternate、EffectList、updateQueue、Hooks)。

本文转载自微信公众号「 更了不起的前端  」,可以通过以下二维码关注。转载本文请联系更了不起的前端  公众号。

 

责任编辑:武晓燕 来源: 更了不起的前端
相关推荐

2020-09-24 08:45:10

React架构源码

2020-10-13 08:36:30

React 架构机制

2020-09-30 09:15:24

React架构递归

2022-04-16 20:10:00

React Hookfiber框架

2022-08-21 09:41:42

ReactVue3前端

2023-11-06 08:00:00

ReactJavaScript开发

2022-03-31 17:54:29

ReactHooks前端

2024-06-04 14:17:26

2019-08-20 15:16:26

Reacthooks前端

2020-09-19 17:46:20

React Hooks开发函数

2023-05-29 13:56:00

JSReact

2021-03-18 08:00:55

组件Hooks React

2022-02-10 19:15:18

React监听系统模式

2021-05-11 08:48:23

React Hooks前端

2023-05-08 07:52:29

JSXReactHooks

2020-08-10 06:31:01

React Hooks前端开发

2021-11-05 10:36:19

性能优化实践

2022-03-16 22:24:50

ReactstateHooks

2022-11-15 17:31:35

边缘计算架构人工智能

2022-06-23 09:04:14

ReactHooks项目
点赞
收藏

51CTO技术栈公众号