React Hooks 原理探究

问题

  1. 什么是 Hooks?
  2. 常见的 Hooks 有哪些,以及使用方法?
  3. 实现一个 useState?
  4. 实现一个 useEffect?

什么是 Hooks?

hooks 官网简介open in new window

常见的 Hooks 有哪些,以及使用方法?

9 分钟掌握 React Hooks 正确认知open in new window

自变量、因变量与响应式更新

这个小学知识就是:自变量与因变量。对于如下等式:

2x + 1 = y
1

x 是自变量,y 的值受 x 影响,是因变量。

const [count, setCount] = useState(0)

useEffect(() => {
  // 副作用部分
  document.title = `You clicked ${count} times`
}, [])

// useCallback:返回一个回调函数。
const memoizedCallback = useCallback(() => {
  doSomething(a, b)
}, [a, b])

// useMemo:返回一个值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • useState 定义自变量
  • useMemo useCallback 定义没有副作用的因变量
  • useEffect 定义有副作用的因变量
  • useReducer 是把多个 state 合并为一个,本质上是自变量。
  • useContext 解决跨组件层级传递自变量
  • useRef 在自变量和视图之间做标记状态,返回的 ref 对象在组件的整个生命周期内持续存在

csr

实现一个 useState?

let memoizedState = []
let currentCursor = 0

function useState(initialState) {
  memoizedState[currentCursor] = memoizedState[currentCursor] || {
    state: initialState,
    action: null
  }
  const cursor = currentCursor
  currentCursor++

  if (memoizedState[cursor].action) {
    memoizedState[cursor].state = memoizedState[cursor].action(memoizedState[cursor].state)
    memoizedState[cursor].action = null
  }
  return [memoizedState[cursor].state, dispatchAction.bind(null, cursor)]
}

function dispatchAction(cursor, action) {
  if (memoizedState[cursor].action === null) {
    memoizedState[cursor].action = action
  }
  schedule()
}

function App(params) {
  const [count, updateCount] = useState(0)
  const [count2, updateCount2] = useState(0)

  return (
    <div>
      <button onClick={() => updateCount(e => e + 10)}>{count}</button>
      <button onClick={() => updateCount2(e => e + 20)}>{count2}</button>
    </div>
  )
}

function schedule() {
  currentCursor = 0
  render()
}

schedule()

function render() {
  ReactDOM.render(App(), document.getElementById("root"))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React Hook Test</title>
    <script src="https://unpkg.com/react/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
  </head>
  <style>
    button {
      width: 100px;
      height: 38px;
      font-size: 20px;
      cursor: pointer;
      border-radius: 5px;
      border: 1px solid #ccc;
      line-height: 38px;
      text-align: center;
      background-color: #fff;
      padding: 0;
    }
  </style>

  <body>
    <div id="root"></div>
  </body>
  <script src="hooks.js" type="text/babel"></script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

接近 hooks 源码 90 行实现

let isMount = true
let workInProgressHook = null

const fiber = {
  stateNode: App,
  memoizedState: null
}

function useState(initialState) {
  let hook
  if (isMount) {
    hook = {
      memoizedState: initialState,
      next: null, // 链表
      queue: {
        // 存储action的环形队列
        pending: null
      }
    }
    if (!fiber.memoizedState) {
      fiber.memoizedState = hook
    } else {
      workInProgressHook.next = hook
    }
    workInProgressHook = hook
  } else {
    // console.log('~~~ workInProgressHook', workInProgressHook)
    hook = workInProgressHook
    workInProgressHook = workInProgressHook.next
  }
  // 执行 action
  let baseState = hook.memoizedState
  if (hook.queue.pending) {
    let firstUpdate = hook.queue.pending.next
    do {
      const action = firstUpdate.action
      baseState = action(baseState)
      firstUpdate = firstUpdate.next
    } while (firstUpdate !== hook.queue.pending.next)
    hook.queue.pending = null
  }
  hook.memoizedState = baseState
  // return [baseState, dispatchAction.bind(null, hook.queue)];
  return [baseState, action => dispatchAction(hook.queue, action)]
}

function dispatchAction(queue, action) {
  const update = {
    action,
    next: null
  }
  if (queue.pending === null) {
    // u0 -> u0
    update.next = update
  } else {
    // u0 -> u1 ; u1 -> u0
    update.next = queue.pending.next
    queue.pending.next = update
  }
  queue.pending = update
  schedule()
}

// 调度器
function schedule() {
  workInProgressHook = fiber.memoizedState
  render()
  isMount = false
}

function App(params) {
  const [count, updateCount] = useState(0)
  const [count2, updateCount2] = useState(1)
  const [count3, updateCount3] = useState(2)
  console.log("isMount", isMount)
  console.log("count", count)
  console.log("count2", count2)

  return (
    <div>
      <button onClick={() => updateCount(e => e + 10)}>{count}</button>
      <button onClick={() => updateCount2(e => e + 20)}>{count2}</button>
    </div>
  )
}

schedule()

function render() {
  ReactDOM.render(fiber.stateNode(), document.getElementById("root"))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

小型 render

const atom = (function () {
  const createElement = (tag, attrs, ...childs) => {
    return { tag, attrs, childs }
  }
  // 将 vdom 转换为真实 dom
  const render = (vdom, root) => {
    if (typeof vdom === "string" || typeof vdom === "number") {
      // 子元素如果是字符串,直接拼接字符串
      root.innerText += vdom
      return
    }
    const dom = document.createElement(vdom.tag)
    if (vdom.attrs) {
      for (let attr in vdom.attrs) {
        const value = vdom.attrs[attr]
        setAttribute(dom, attr, value)
      }
    }
    // 遍历子节点
    vdom.childs && vdom.childs.forEach(child => render(child, dom))
    // 将子元素挂载到其真实 DOM 的父元素上
    root.appendChild(dom)
  }

  // 设置 dom 节点属性
  const setAttribute = (dom, attr, value) => {
    if (attr === "className") {
      attr = "class"
    }
    // 处理事件
    if (/on\w+/.test(attr)) {
      attr = attr.toLowerCase()
      dom[attr] = value || ""
    } else if (attr === "style" && value) {
      // 处理 style 样式,可以是个字符串或者对象
      if (typeof value === "string") {
        dom.style.cssText = value
      } else if (typeof value === "object") {
        for (let styleName in value) {
          dom.style[styleName] =
            typeof value[styleName] === "number" ? value[styleName] + "px" : value[styleName]
        }
      }
    } else {
      // 其他属性
      dom.setAttribute(attr, value)
    }
  }

  const React = {
    createElement
  }

  const ReactDOM = {
    render: (vdom, root) => {
      root.innerText = ""
      render(vdom, root)
    }
  }
  return {
    React,
    ReactDOM
  }
})()
const { React, ReactDOM } = atom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Last Updated:
Contributors: rumengkai