乐趣区

关于javascript:react函数组件中使用useState改变值后立刻获取最新值

一、前言

知己知彼,能力屡战屡败,所以咱们想学好学深一个货色,肯定要去理解他的由来,写过 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 来达到给组内人复用,解决相似的问题,如果机智的你发现了我的小谬误,还请斧正,最初来句鸡汤“春风得意时布好局,腹背受敌时有进路”,继续学习~

退出移动版