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

问题描述 投票:1回答:2

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
          type="button"
          onClick={e => setNumber(number => setNumber(number - 1))}
        >
          -
        </button>
      </div>
      <div>Number: {number}</div>
      <div>Its square: {result}</div>
      <div>
        Increment
        <button
          type="button"
          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>

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

javascript reactjs react-hooks
2个回答
0
投票

您在控制台中看到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);

0
投票

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

如果您最关心的是确保用户看不到错误的结果,则可以让传递的函数返回带有其原始参数以及答案的数组,然后检查以确保number的当前值匹配它:

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

export default function App() {
  const [number, setNumber] = useState(0);
  const [loading, argAndResult, error, reload] = useAsync(
    () => square(number, 1000),
    [number]
  );
  if (argAndResult && argAndResult[0] === number) 
      { result = argAndResult[1]; }
  else { result = null; }

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

热门问题
推荐问题
最新问题