从异步等待中保存 object 时无限循环

Inifinite loop when saving an object from async await

当我在 async/await 操作中创建并 object 时...

export const getData = async datas => {
  const a1 = await getData1(datas);
  return { a1 };
};

...然后用useState保存...

import { useState, useEffect } from "react";
import { getData } from "./getData";

export const useData = ababab => {
  const [data, setData] = useState();

  useEffect(() => {
    const loadData = async () => {
      const newData = await getData(ababab);
      setData(newData);
    };
    console.log(Date.now().toString());
    loadData();
  }, [ababab]);

  return data;
};

...我得到一个无限循环。我不明白。 如果您注释掉 setData - 它不会循环。 如果你return只是a1,它不会循环。

这里是使用useData的地方:

import React from "react";
import "./styles.css";
import { useAbabab } from "./usaAbabab";
import { useData } from "./useData";

export default function App() {
  const ababab = useAbabab();
  const data = useData(ababab);
  return (
    <div className="App">
      <h1>Hello CodeSandbox {data && data.a1}</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

这里是useAbabab的内容:

import { useState } from "react";

export const useAbabab = () => {
  const [aaa, setAaa] = useState(0);
  const [bbb, setBbb] = useState(5);

  return { aaa, bbb, setAaa, setBbb };
};

Code Sandbox example

您可能已经了解到,无限循环是由 useData 中的 useEffect 引起的,它是由 ababab 的更改触发的(可以通过删除 ababab 来自依赖数组)。

虽然 ababab 实际上只是一个对象中的两个完整 useState 输出,但对象本身在每次渲染时都会重新定义,从而触发 useEffect 到 运行。

我认为解决此问题的最简单方法是将 useAbabab 的 return 值包装在 useMemo 中,如下所示:

import { useState, useMemo } from "react";

export const useAbabab = () => {
  const [aaa, setAaa] = useState(0);
  const [bbb, setBbb] = useState(5);

  return useMemo(() => ({ aaa, bbb, setAaa, setBbb }), [aaa, bbb]);
};

由于 ababa 变量名称,很难准确判断您的代码在做什么,但从我在您的代码中读到的内容来看,您似乎想要一个围绕异步资源的通用挂钩-

const identity = x => x

const useAsync = (runAsync = identity, deps = []) => {
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  const [result, setResult] = useState(null)

  useEffect(_ => { 
    Promise.resolve(runAsync(...deps))
      .then(setResult, setError)
      .finally(_ => setLoading(false))
  }, deps)

  return { loading, error, result }
}

使用我们的自定义钩子 usAsync 看起来像这样 -

function App() {
  const ababab =
    useAbabab()

  const { loading, error, result } = 
    useAsync(getData, [ababab]) // async function, args to function

  if (loading)
    return <p>Loading...</p>

  if (error)
    return <p>Error: {error.message}</p>

  return <div>Got data: {result}</div>
}

useAsync 是一个通用的通用钩子,可以专门用于其他有用的方式 -

const fetchJson = (url = "") =>
  fetch(url).then(r => r.json()) // <-- stop repeating yourself

const useJson = (url = "") =>
  useAsync(fetchJson, [url]) // <-- useAsync

const MyComponent = ({ url = "" }) => {
  const { loading, error, result } =
    useJson(url)                       // <-- dead simple

  if (loading)
    return <pre>loading...</pre>

  if (error)
    return <pre className="error">error: {error.message}</pre>

  return <pre>result: {result}</pre>
}

ReactDOM.render(
  <MyComponent url="https://httpbin.org/get?foo=bar" />,
  document.body
)

运行 下面的代码片段可以看到 useAsyncuseJson 在您自己的浏览器中工作 -

const { useState, useEffect } =
  React

// fake fetch slows response down so we can see loading
const _fetch = (url = "") =>
  fetch(url).then(x =>
    new Promise(r => setTimeout(r, 2000, x)))

const identity = x => x

const useAsync = (runAsync = identity, deps = []) => {
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  const [result, setResult] = useState(null)

  useEffect(_ => { 
    Promise.resolve(runAsync(...deps))
      .then(setResult, setError)
      .finally(_ => setLoading(false))
  }, deps)

  return { loading, error, result }
}

const fetchJson = (url = "") =>
  _fetch(url).then(r => r.json())

const useJson = (url = "") =>
  useAsync(fetchJson, [url])

const MyComponent = ({ url = "" }) => {
  const { loading, error, result } =
    useJson(url)

  if (loading)
    return <pre>loading...</pre>

  if (error)
    return <pre style={{color: "tomato"}}>error: {error.message}</pre>

  return <pre>result: {JSON.stringify(result, null, 2)}</pre>
}

const MyApp = () =>
  <main>
    ex 1 (success):
    <MyComponent url="https://httpbin.org/get?foo=bar" />

    ex 2 (error):
    <MyComponent url="https://httpbin.org/status/500" />
  </main>

ReactDOM.render(<MyApp />, document.body)
pre {
  background: ghostwhite;
  padding: 1rem;
  white-space: pre-wrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>