React 初始化并与后端同步状态

React Initializing and synchronizing state with backend

我想知道什么是最好的 way/pattern 来初始化状态并保持它与服务器同步。在过去的几天里,我已经阅读并尝试了很多,但没有找到任何解决我问题的方法。

这个例子真的很简单。有一个状态——为了简单起见,它在示例中是一个数字,尽管在现实生活中它是一个对象——我需要从服务器检索它。检索后,我希望它与服务器同步。 getValueFromServer 是一个模拟 returns 在 setTimeout 中等待一段随机时间后的随机值。

因此,为了初始化我的状态,我在空数组上使用 useEffect 作为依赖项,为了保持同步,我使用 useEffect 作为依赖项使用状态。

问题是它试图保存一个未定义的值。日志如下。

1 -> 第一次初始化

2 -> API 调用保存在服务器值:undefined

3 -> API 调用保存在服务器值:6.026930847574949

我得到的:

1:按预期运行安装。

2:这个我没想到。我猜它是因为 "useState".

而触发的

3:一旦我们从服务器得到响应就运行。有点明显但很痛苦,因为我到底为什么要保存它。

最好的方法是什么?在具有依赖性的 useEffect 中使用类似 "isInitialized" 标志的东西感觉有点被黑而且不专业。

下面的代码,您也可以在这里找到它:https://codesandbox.io/s/optimistic-rgb-uce9f

import React, { useState, useEffect } from "react";
import { getValueFromServer } from "./api";

export default function App() {
  const [value, setValue] = useState();

  useEffect(() => {
    async function initialize() {
      console.log("Initializing for first time");
      let serverValue = await getValueFromServer();
      setValue(serverValue);
    }
    initialize();
  }, []);

  useEffect(() => {
    console.log("API call to save in server value: ", value);
  }, [value]);

  const handleClick = () => {
    setValue(value + 1);
  };

  return (
    <div className="App">
      <h1>Value: {value}</h1>
      <button onClick={handleClick}>Add 1 to value</button>
    </div>
  );
}

您的应用在忙碌时会做其他事情await在您的第一个 useEffect 挂钩函数中发生一些事情。 "other stuff" 在这种情况下是 "executing your second useEffect hook function"。 serverValue,因此 value,此时未定义,因此您的 console.log 打印 undefined。一旦你的 await promise 解析为一个值并且 setValue 被调用,你的第二个 useEffect 钩子函数的依赖关系就会改变,导致你的函数再次成为 运行 (并打印出您期望的价值)。

如果你必须有两个 useEffect 钩子,那么当 value 未定义时,只需退出第二个钩子:

  useEffect(() => {
    if (value === undefined) {
      return;
    }

    console.log('value is:', value);
  }, [ value ]);

What would the best approach be here? Using something like a "isInitialized" flag in the useEffect with dependency feels kind of hacked and not professional.

您可以在 useState

中使用标志或使用默认值初始化对象

const { Fragment, useState, useEffect } = React;

const getValueFromServer = () => new Promise((resolve, reject) => setTimeout(() => resolve(Math.random())), 1000)

const App = () => {
  const [value, setValue] = useState(null);
  const [isLoading, setLoading] = useState(true);

  useEffect(() => {
    let isUnmounted = false;
  
    getValueFromServer().then(serverValue => {
      console.log("Initializing for first time");
      if(isUnmounted) {
        return;
      }
      setValue(serverValue);
      setLoading(false);
    })
    
    return () => {
      isUnmounted = true;
    }
  }, []);

  useEffect(() => {
    if(!value) {
      return () => {}
    }
  
    console.log("API call to save in server value: ", value);
    setTimeout(() => setLoading(false), 50);
  }, [value]);

  const handleClick = () => {
    setLoading(true);
    setValue(value + 1);
  };

  return <div className="App">
    {isLoading ? <Fragment>
      <span>Loading...</span>
    </Fragment> : <Fragment>
      <h1>Value: {value}</h1>
      <button onClick={handleClick}>Add 1 to value</button>
    </Fragment>}
  </div>
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.10.1/polyfill.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>