前言

首先应用create-react-app新建个我的项目,而后在index.js写咱们的代码,浏览本文前须要晓得罕用 React Hooks 的根本用法。

useState

import React from 'react'import ReactDOM from 'react-dom'let memorizedStateconst useState = initialState => {  memorizedState = memorizedState || initialState // 初始化  const setState = newState => {    memorizedState = newState    render() // setState 之后触发从新渲染  }  return [memorizedState, setState]}const App = () => {  const [count1, setCount1] = useState(0)  return (    <div>      <div>        <h2>useState: {count1}</h2>        <button          onClick={() => {            setCount1(count1 + 1)          }}        >          增加count1        </button>      </div>    </div>  )}const render = () => {  ReactDOM.render(<App />, document.getElementById('root'))}render()

但到这里会有一个问题,就是当减少第二个 useState 的时候会发现扭转两个 state 都是改同一个值,同步变动,于是,咱们通过应用数组的形式下标来形式来辨别。

import React from 'react'import ReactDOM from 'react-dom'let memorizedState = [] // 通过数组模式存储无关应用hook的值let index = 0 // 通过下标记录 state 的值const useState = initialState => {  let currentIndex = index  memorizedState[currentIndex] = memorizedState[index] || initialState  const setState = newState => {    memorizedState[currentIndex] = newState    render() // setState 之后触发从新渲染  }  return [memorizedState[index++], setState]}const App = () => {  const [count1, setCount1] = useState(0)  const [count2, setCount2] = useState(10)  return (    <div>      <div>        <h2>          useState: {count1}--{count2}        </h2>        <button          onClick={() => {            setCount1(count1 + 1)          }}        >          增加count1        </button>        <button          onClick={() => {            setCount2(count2 + 10)          }}        >          增加count2        </button>      </div>    </div>  )}const render = () => {  index = 0  ReactDOM.render(<App />, document.getElementById('root'))}render()

这样就实现成果了:

剖析:

  • 第一次页面渲染的时候,依据 useState 程序,申明了 count1 和 count2 两个 state,并按下标程序顺次存入数组中
  • 当调用setState的时候,更新 count1/count2 的值,触发从新渲染的时候,index 被重置为 0。而后又从新按 useState 的申明程序,顺次拿出最新 state 的值;由此也可见为什么当咱们应用 hook 时,要留神点 hook 不能在循环、判断语句外部应用,要申明在组件顶部。

memorizedState这个数组咱们上面实现局部 hook 都会用到,当初memorizedState数组长度为 2,顺次寄存着两个应用useState后返回的 state 值;

0: 01: 10

每次更改数据后,调用render办法,App函数组件从新渲染,又从新调用useState,但内部变量memorizedState之前曾经顺次下标记录下了 state 的值,故从新渲染是间接赋值之前的 state 值做初始值。晓得这个做法,上面的useEffect,useCallback,useMemo也是这个原理。

当然理论源码,useState是用链表记录程序的,这里咱们只是模仿成果。

useReducer

useReducer 承受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的以后值,第二个成员是发送 action 的 dispatch 函数。

let reducerStateconst useReducer = (reducer, initialArg, init) => {  let initialState  if (init !== undefined) {    initialState = init(initialArg) // 初始化函数赋值  } else {    initialState = initialArg  }  const dispatch = action => {    reducerState = reducer(reducerState, action)    render()  }  reducerState = reducerState || initialState  return [reducerState, dispatch]}const init = initialNum => {  return { num: initialNum }}const reducer = (state, action) => {  switch (action.type) {    case 'increment':      return { num: state.num + 1 }    case 'decrement':      return { num: state.num - 1 }    default:      throw new Error()  }}const App = () => {  const [state, dispatch] = useReducer(reducer, 20, init)  return (    <div>      <div>        <h2>useReducer:{state.num}</h2>        <button onClick={() => dispatch({ type: 'decrement' })}>-</button>        <button onClick={() => dispatch({ type: 'increment' })}>+</button>      </div>    </div>  )}

useEffect

对于 useEffect 钩子,当没有依赖值的时候,很容易想到雏形代码:

const useEffect = (callback, dependencies) => {  if (!dependencies) {    // 没有增加依赖项则每次执行,增加依赖项为空数组    callback()  }}

但如果有依赖 state 值,即是咱们应用 useState 后返回的值,这部分咱们就须要用上方定义的数组 memorizedState 来记录

let memorizedState = [] // 寄存 hooklet index = 0 // hook 数组下标地位/** * useState 实现 */const useState = initialState => {  let currentIndex = index  memorizedState[currentIndex] = memorizedState[index] || initialState  const setState = newState => {    memorizedState[currentIndex] = newState    render() // setState 之后触发从新渲染  }  return [memorizedState[index++], setState]}/** * useEffect 实现 */const useEffect = (callback, dependencies) => {  if (memorizedState[index]) {    // 不是第一次执行    let lastDependencies = memorizedState[index] // 依赖项数组    let hasChanged = !dependencies.every(      (item, index) => item === lastDependencies[index]    ) // 循环遍历依赖项是否与上次的值雷同    if (hasChanged) {      // 依赖项有扭转就执行 callback 函数      memorizedState[index++] = dependencies      setTimeout(callback) // 设置宏工作,在组件render之后再执行    } else {      index++ // 每个hook占据一个下标地位,避免程序错乱    }  } else {    // 第一次执行    memorizedState[index++] = dependencies    setTimeout(callback)  }}const App = () => {  const [count1, setCount1] = useState(0)  const [count2, setCount2] = useState(10)  useEffect(() => {    console.log('useEffect1')  }, [count1, count2])  useEffect(() => {    console.log('useEffect2')  }, [count1])  return (    <div>      <div>        <h2>          useState: {count1}--{count2}        </h2>        <button          onClick={() => {            setCount1(count1 + 1)          }}        >          增加count1        </button>        <button          onClick={() => {            setCount2(count2 + 10)          }}        >          增加count2        </button>      </div>    </div>  )}const render = () => {  index = 0  ReactDOM.render(<App />, document.getElementById('root'))}render()

程序第一次执行结束后,memorizedState 数组值如下

0: 01: 102: [0, 10]3: [0]

上述代码回调函数执行,原本咱们能够用callback()执行即可,但因为useEffect在渲染时是异步执行,并且要等到浏览器将所有变动渲染到屏幕后才会被执行;

因为是异步且等页面渲染结束才执行,依据对 JS 事件循环的了解,咱们想要它异步执行工作,就在此创立一个宏工作setTimeout(callback)让它进入宏工作队列期待执行,当然这其中具体的渲染过程我这里就不细说了。

还有一个 hook 是useLayoutEffect,除了执行回调的两处中央代码实现不同,其余代码雷同,callback这里我用微工作Promise.resolve().then(callback),把函数执行退出微工作队列。

因为useLayoutEffect在渲染时是同步执行,会在所有的 DOM 变更之后同步调用,个别能够应用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 将被同步刷新。

怎么证实呢?如果你在useLayoutEffect加了死循环,而后从新关上网页,你会发现看不到页面渲染的内容就进入死循环了;而如果是useEffect的话,会看到页面渲染实现后才进入死循环。

useLayoutEffect(() => {  while (true) {}}, [])

useCallback

useCallbackuseMemo会在组件第一次渲染的时候执行,之后会在其依赖的变量产生扭转时再次执行;并且这两个 hooks 都返回缓存的值,useMemo 返回缓存计算数据的值,useCallback返回缓存函数的援用。

const useCallback = (callback, dependencies) => {  if (memorizedState[index]) {    // 不是第一次执行    let [lastCallback, lastDependencies] = memorizedState[index]    let hasChanged = !dependencies.every(      (item, index) => item === lastDependencies[index]    ) // 判断依赖值是否产生扭转    if (hasChanged) {      memorizedState[index++] = [callback, dependencies]      return callback    } else {      index++      return lastCallback // 依赖值不变,返回上次缓存的函数    }  } else {    // 第一次执行    memorizedState[index++] = [callback, dependencies]    return callback  }}

useMemo

useMemo实现与useCallback也很相似,只不过它返回的函数执行后的计算返回值,间接把函数执行了。

const useMemo = (memoFn, dependencies) => {  if (memorizedState[index]) {    // 不是第一次执行    let [lastMemo, lastDependencies] = memorizedState[index]    let hasChanged = !dependencies.every(      (item, index) => item === lastDependencies[index]    )    if (hasChanged) {      memorizedState[index++] = [memoFn(), dependencies]      return memoFn()    } else {      index++      return lastMemo    }  } else {    // 第一次执行    memorizedState[index++] = [memoFn(), dependencies]    return memoFn()  }}

useContext

代码出其不意的少吧...

const useContext = context => {  return context._currentValue}

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内放弃不变。

let lastRefconst useRef = value => {  lastRef = lastRef || { current: value }  return lastRef}

前面几个例子我就没展现出 Demo 了,附上 Github 地址:上述hooks实现和案例

  • ps: 集体技术博文 Github 仓库,感觉不错的话欢送 star,给我一点激励持续写作吧~