首页 > 解决方案 > 使用 Promise Hooks React JS 跟踪组件外部的状态更新

问题描述

我有一个外部函数,它通过refImperativeHandler钩子改变组件内部的状态

const [state, setState] = useState(0);

// inside component
const testFunc = () => {
   setState(1)
}

// outside of component
function *generator {
    yield ...
    yield testFunc()
    // next yield should wait till state is updated, only then it can be executed 
    yield ...
}

但我不知道如何跟踪该状态已被更改,因为setState钩子没有回调选项。如何跟踪外部函数(例如我的generator示例)中状态更改的时刻?

标签: javascriptreactjs

解决方案


由于您正在使用钩子并且更新程序函数没有像类更新程序版本那样接收第二个参数,因此您可能希望自己实现一个带有第二个参数的更新程序:

function useStateWithCallback(initState) {
  const cbRef = useRef(null);

  const [state, setState] = useState(initState);

  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state);
      cbRef.current = null;
    }
  }, [state]);

  const updater = (value, callback) => {
    cbRef.current = typeof callback === "function" ? callback : null;
    setState(value);
  };

  return [state, updater];
}
  • 我们使用相同的签名创建自己useState的函数,一个接受初始状态并返回当前状态元组的函数和一个更新函数,但这次更新函数接受 2 个参数,下一个状态和一个回调。

  • 我们需要一种方法来存储传递给我们的更新程序的回调,我们可以将它存储在一个状态中,useState但是每次更新都会触发一个渲染,所以我们将它存储在一个 ref 中,该 ref 在渲染之间是持久的,但是是可变的,不会触发 re-更新时呈现。我们从一个null值开始。

  • 我们存储和处理值的实际状态useState

  • 我们将一个效果同步到这个状态值并调用存储在我们的 ref 中的回调。我们有条件地这样做,因为效果将在任何状态更新之前的第一次渲染上运行(这就是我们使用 ref 开始的原因null)。

  • 我们创建自己的updater函数,将回调存储在我们的 ref 中(同时验证它确实是一个函数)并触发实际的状态更新。

  • 我们返回带有真实状态和自定义更新器的元组

运行示例:

const {useState, useRef, useEffect} = React;

function useStateWithCallback(initState) {
  const cbRef = useRef(null);

  const [state, setState] = useState(initState);

  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state);
      cbRef.current = null;
    }
  }, [state]);

  const updater = (value, callback) => {
    cbRef.current = typeof callback === "function" ? callback : null;
    setState(value);
  };

  return [state, updater];
}

function App() {
  const [count, setCount] = useStateWithCallback(0);

  const myCallback = currentState => console.log("currentState", currentState);

  const onClick = () => {
    setCount(c => c + 1, myCallback);
  };

  return <button onClick={onClick}>{count}</button>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App />,
  rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root" />


推荐阅读