具有自定义反应挂钩的陈旧状态

时间:2020-05-28 06:17:33

标签: javascript reactjs react-hooks

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App />,
  rootElement
)

function square(n, timeout = 1000) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(n * n), timeout);
  });
}

function App() {
  const [number, setNumber] = React.useState(0);
  const [loading, result, error, reload] = useAsync(
    () => square(number, 1000),
    [number]
  );
  console.log(number, result);
  return (
    <div className="App">
      <div>
        Decrement{" "}
        <button
          disabled={loading}
          type="button"
          onClick={e => setNumber(number => setNumber(number - 1))}
        >
          -
        </button>
      </div>
      <div>Number: {number}</div>
      <div>Its square: {result} {loading && <span className="fa fa-spinner"></span>}</div>
      <div>
        Increment
        <button
          type="button"
          disabled={loading}
          onClick={e => setNumber(number => setNumber(number + 1))}
        >
          +
        </button>
      </div>
    </div>
  );
}



function useAsync(func, dependencyArray = []) {
  const [state, setState] = React.useState({
    loading: false,
    result: null,
    error: null,
    mounted: true
  });

  const reload = () => {
    function call() {
        setState(state => ({
          ...state,
          loading: true,
          error: null,
          result: null
        }));
        func()
        .then(res=>{
          if (!state.mounted) return;
          setState(state => ({
            ...state,
            result: res,
            loading: false
          }));
        })
        .catch(err=>{
          setState(state => ({
          ...state,
          loading: false,
          result: null,
          error
        }));
        })        
    }
    call();
  };

  React.useEffect(() => {
    reload();
    return () =>
      setState({
        ...state,
        loading: false,
        result: null,
        error: null
      });
  }, dependencyArray);

  React.useEffect(() => {
    setState(state => ({ ...state, mounted: true }));
    return () => setState(state => ({ ...state, mounted: false }));
  }, []);

  return [state.loading, state.result, state.error, reload, setState];
}
.App {
  font-family: sans-serif;
  text-align: center;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-direction: column;
}

button {
  width: 100px;
  height: 40px;
  border-radius: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">

我正在使用一个自定义的react钩子来调用API并在我的react组件中设置状态。钩子接受一个函数,该函数返回promise和依赖数组。挂钩返回[loading, result, error, reload, setState]。我希望该结果将在钩子更改的依赖关系发生后立即重置,但是一个渲染的状态仍然陈旧,这破坏了组件中的逻辑。 重现步骤。 I.假设数字为2,则其平方为4。 二。打开控制台。 三,增量2,数字将变为3,在一秒后变为9。 IV。在控制台中,您会看到类似 2 4 3 4 3空 3 9 V. 3 4 是错误的日志。

2 个答案:

答案 0 :(得分:1)

您会在控制台中看到null,因为每当数字更改时,您都将重置自定义挂钩中的状态,以在重装功能以及useEffect的清除功能中使用result = null

const reload = () => {
    async function call() {
      try {
        setState(state => ({
          ...state,
          loading: true,
          error: null,
          result: null
        }));
        const res = await func();
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          result: res,
          loading: false
        }));
      } catch (error) {
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          loading: false,
          result: null,
          error
        }));
      }
    }
    call();
  };

如果您不希望将值重置为null且仅在计算完成后才更新它,则可以避免重置值,但我建议您继续进行操作,因为它可以更好地处理场景,并且始终将状态加载设置为true,您可以显示加载器,直到获取结果为止。

但是您可以在useEffect的清除功能中删除setState,这不是必需的

const reload = () => {
    async function call() {
      try {
        setState(state => ({
          ...state,
          loading: true,
        }));
        const res = await func();
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          result: res,
          loading: false
        }));
      } catch (error) {
        if (!state.mounted) return;
        setState(state => ({
          ...state,
          loading: false,
          result: null,
          error
        }));
      }
    }
    call();
  };

  React.useEffect(() => {
    reload();
  }, dependencyArray);

答案 1 :(得分:0)

当您多次单击递增或递减按钮时,您将有多个未完成的承诺。然后,result会在解决每个承诺时简单地进行更新。

如果您最关心的是确保用户看不到错误的结果,则可以让传递的函数返回一个带有其原始参数以及答案的数组,然后检查以确保{{ 1}}与之匹配:

number

不太理想,但可以在某些用例中使用。