一、前言

知己知彼,能力屡战屡败,所以咱们想学好学深一个货色,肯定要去理解他的由来,写过react的人大部分都应该理解redux,所以咱们明天就来聊聊react hooks;请收下我这个包袱,学习是干燥的,所以时不时的给本人找点乐子。为什么我要忽然加这么一个梗也是有起因的,我记得redux是一位叫Dan的人写的,忘了记没记错,咱们就先叫他Dan(诞)总吧,随着redux位置的晋升,Dan总也顺其自然的进了facebook,负责react-hooks的开发,你看我这个梗是不是连上了;

二、为什么说redux和react-hooks有些渊源?

其实redux很好了解,基于公布订阅模式,在外部实现一个状态(鸡),而后加一些约定和限度,内部不能够间接扭转这个状态,只能通过redux提供的一个代理办法去触发这个状态的扭转,使用者能够通过redux提供的订阅办法订阅事件,当状态扭转的时候,redux帮忙咱们公布这个事件,使得各个订阅者取得最新状态,是不是很简略,大道至简~

上面提供了个简版的redux代码,有趣味的能够看一下

/** * * @param {*} reducer 解决状态办法 * @param {*} preloadState 初始状态 */function createStore(reducer, preloadState) {  /** 存储状态 */  let state = preloadState;  /** 存储事件 */  const listeners = [];  /** 获取状态办法,返回state */  const getState = () => {    return state;  };  /**   * 订阅事件,将事件存储到listeners中   * @param {*} listener 事件   */  const subscribe = (listener) => {    listeners.push(listener);  };  /**   * 派发action,公布事件   * @param {*} action 动作   */  const dispatch = (action) => {    state = reducer(state, action);    listeners.forEach((listener) => listener());  };  /** 状态机 */  const store = {    getState,    subscribe,    dispatch,  };  return store;}export default createStore;

为什么说react-hooks跟redux有渊源,因为Dan总把redux的这种思维带到了react-hooks中,通过创立一个公共hookStates来存储所有的hooks,通过增加一个索引hookIndex来程序执行每一个hook,所以react-hooks规定hooks不能应用在if语句和for语句中,来放弃hooks按程序执行,在react-hooks中有一个叫useReducer的hooks,正是按redux的这个思维实现的,而咱们罕用的useState只是useReducer的一个语法糖,有人可能会说这个hookStates会不会很大,所有的hooks都存在里边,毕竟一个我的项目中有那么多组件,我说的只是简略的实现原理,咱们晓得react在16的几个版本之后就退出了fiber的概念,在react外部实现中,会把每个组件的hookStates寄存到fiber节点上,来保护各个节点的状态,react每次render之后都会把hookIndex置为0,来保障下次hooks执行是从第一个开始执行,每个hooks执行完也会把hookIndex++,上面是useReducer的简略实现:

/** @param hookStates 存储所有hooks */const hookStates = [];/** @param hookIndex 索引 */let hookIndex = 0;/** * * useReducer * @param {*} reducer 状态处理函数 * @param {*} initialState 初始状态,能够传入函数实现懒加载 * @returns */function useReducer(reducer, initialState) {  hookStates[hookIndex] =    hookStates[hookIndex] || typeof initialState === "function"      ? initialState()      : initialState;  /**   *   * dispatch   * @param {*} action 动作   * @returns   */  const dispatch = (action) => {    hookStates[hookIndex] = reducer      ? reducer(hookStates[hookIndex], action)      : action;    /** React外部办法实现从新render */    scheduleUpdate();  };  return [hookStates[hookIndex++], dispatch];}

前文说过useState是useReducer的语法糖,所以上面是useState的实现

/** * * useState 间接将reducer传为null,调用useReducer并返回 * @param {*} initialState 初始化状态 * @returns */function useState(initialState) {  return useReducer(null, initialState);}

二、如何实现useState扭转值之后立即获取最新的状态?

之前铺垫了一堆,终于到正题了,本想直奔主题,一不小心变成了介绍react-hooks的文章,不过多理解一点究竟不会亏什么,react-hooks的思维其实是同步逻辑,然而在react的合成事件中状态更新是异步的,看一下上面这个场景,应用useState申明了一个状态state,又别离申明了两个函数setT和func,在setT中,调用setstate更新状态,而后调用func函数,在func中间接打印以后状态state

function App() {  const [state, setstate] = useState(0);  const setT = () => {    setstate(2);    func();  };  const func = () => {    console.log(state);  };  return (    <div className="App">      <header className="App-header">        <button onClick={setT}>set 2</button>      </header>    </div>  );}

当咱们点击set 2 按钮时,控制台打印的还是上一次的值0,而不是最新的2

因为在react合成事件中扭转状态是异步的,出于缩小render次数,react会收集所有状态变更,而后比对优化,最初做一次变更,在代码中能够看出,func的调用和setstate在同一个宏工作中,这是react还没有render,所以间接应用state获取的必定是上一次闭包里的值0

有的人可能会说,间接将最新的值当作参数传递给func不就行了吗,对,这也是一种解决办法,然而有时不只是一个状态,可能要传递的参数很多,再有也是出于对react-hooks的深入研究,所以我抉择通过自定义hooks实现在useState扭转值之后立即获取到最新的值,咱们先看下实现的成果,代码变更如下:

function App() {  const [state, setstate] = useState(0);  const setT = () => {    setstate(2);    func();  };  /** 将func的办法传递给useSyncCallback而后返回一个新的函数 */  const func = useSyncCallback(() => {    console.log(state);  });  return (    <div className="App">      <header className="App-header">        <button onClick={setT}>set 2</button>      </header>    </div>  );}export default App;

成果如下:

能够看出当咱们点击set 2按钮之后,控制台输入的间接是2,所以达到了在setstate之后立即获取了最新状态

三、如何实现useSyncCallback?

先把代码放到这里,大家能够看一下

/* * @lastTime: 2021-03-05 15:29:11 * @Description: 同步hooks */import { useEffect, useState, useCallback } from 'react'const useSyncCallback = callback => {    const [proxyState, setProxyState] = useState({ current: false })    const Func = useCallback(() => {        setProxyState({ current: true })    }, [proxyState])    useEffect(() => {        if (proxyState.current === true) setProxyState({ current: false })    }, [proxyState])    useEffect(() => {        proxyState.current && callback()    })    return Func}export default useSyncCallback/* * @lastTime: 2021-02-26 15:29:11 * @param: callback为回调函数 * @Description: 用法 const newFunc = useSyncCallback(yourCallback) */

在这里还须要介绍一个知识点useEffect,useEffect会在每次函数式组件render之后执行,能够通过传递第二个参数,配置依赖项,当依赖项变更useEffect才会更新,如果第二个参数传递一个空数组[],那么useEffect就会只执行一次,react-hooks正是应用useEffect来模仿componentDidMount和componentDidUpdate两个生命周期,也能够通过在useEffect中return一个函数模仿componentWillUnmount,那么useEffect是如何实现在render之后调用的呢,原理就是在useEffect hooks中会封装一个宏工作,而后把传进来的回调函数放到宏工作中去执行,上面实现了一个简版的useEffect:

/** @param hookStates 存储所有hooks */const hookStates = [];/** @param hookIndex 索引 */let hookIndex = 0;/** * * useReducer * @param {*} callback 副作用函数 * @param {*} dependencies 依赖项 * @returns */function useEffect(callback, dependencies) {  /** 判断以后索引下hookStates是否存在值 */  if (hookStates[hookIndex]) {    /** 如果存在就取出,并构造出destroyFunc销毁函数,和上一次依赖项 */    const [destroyFunc, lastDep] = hookStates[hookIndex];    /** 判断以后依赖项中的值和上次依赖项中的值有没有变动 */    const isSame =      dependencies &&      dependencies.every((dep, index) => dep === lastDep[index]);    if (isSame) {      /** 如果没有变动就把索引加一,hooks向后遍历 */      hookIndex++;    } else {      /** 如果有变动,并且存在销毁函数,就先调用销毁函数 */      destroyFunc && destroyFunc();      /** 创立一个新的宏工作,调用callback获取最新的销毁函数,并将销毁函数和依赖项存入hookStates中 */      setTimeout(() => {        const destroyFunction = callback();        hookStates[hookIndex++] = [destroyFunction, dependencies];      });    }  } else {    /** 如果hookStates中不存在,证实是首次执行,间接创立一个宏工作,调用callback获取最新的销毁函数,并将销毁函数和依赖项存入hookStates中 */    setTimeout(() => {      const destroyFunction = callback();      hookStates[hookIndex++] = [destroyFunction, dependencies];    });  }}

我曾经将useEffect的实现步骤写到了正文里,有趣味的能够看一下,接下来咱们就来应用useEffect实现咱们想要的后果,因为useEffect是在react组件render之后才会执行,所以在useEffect获取的状态肯定是最新的,所以利用这一点,把咱们写的函数放到useEffect执行,函数里获取的状态就肯定是最新的

  useEffect(() => {        proxyState.current && callback()    })

首先,在useSyncCallback中创立一个标示proxyState,初始的时候会把proxyState的current值赋成false,在callback执行之前会先判断current是否为true,如果为true就容许callback执行,若果为false,就跳过不执行,因为useEffect在组件render之后,只有依赖项有变动就会执行,所以咱们无奈掌控咱们的函数执行,在useSyncCallback中创立一个新的函数Func,并返回,通过这个Func来模仿函数调用,

 const [proxyState, setProxyState] = useState({ current: false })

在这个函数中咱们要做的就是变更prxoyState的current值为true,来使得让callback被调用的条件成立,同时触发react组件render这样外部的useEffect就会执行,随后调用callback实现咱们想要的成果。

四、小结

因为在工作中遇到了这个场景,所以写了此次分享,通过实现一个自定义hooks来达到给组内人复用,解决相似的问题,如果机智的你发现了我的小谬误,还请斧正,最初来句鸡汤 “春风得意时布好局,腹背受敌时有进路”,继续学习~