【问题标题】:State returning empty array状态返回空数组
【发布时间】:2019-06-27 06:13:33
【问题描述】:

我正在尝试从 useState 挂钩访问状态,但即使我修改了它,它也会给我初始状态。

const quotesURL = "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";


function QuoteGenerator() {
  const [quotes, setQuotes] = useState([]);
  const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });

  useEffect(() => {
    axios(quotesURL)
      .then(result => {
        console.log(result);
        setQuotes(result.data);
      })
      .then(() => {

        console.log(quotes);
      });
    }, []);

console.log(quotes) 返回的是空数组而不是对象数组

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    你应该这样做:

    const quotesURL = "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
    
    
    function QuoteGenerator() {
      const [quotes, setQuotes] = useState([]);
      const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
    
      useEffect(() => {         // THIS WILL RUN ONLY AFTER YOUR 1ST RENDER
        axios(quotesURL)
          .then(result => {
            console.log(result);
            setQuotes(result.data);  // HERE YOU SET quotes AND IT WILL TRIGGER A NEW RENDER
          })
        }, []);                 // BECAUSE YOU'VE SET IT WITH '[]'
    
      useEffect(() => {         // THIS WILL RUN WHEN THERE'S A CHANGE IN 'quotes'
         if (quotes.length) {
           setSomeOtherState();   // YOU CAN USE IT TO SET SOME OTHER STATE
         }
      },[quotes]);
    
    }
    

    这段代码的工作原理:

    • 第一次渲染:您只是初始状态。 useEffects 尚未运行。
    • 第一次渲染后:两种效果都将运行(按此顺序)。第一个将触发 axios 请求。第二个不会做任何事情,因为quotes 还没有length
    • Axios 请求完成:then子句将运行,setQuotes 将被调用以设置新的quotes 值。这将触发重新渲染。
    • 第二次渲染:现在quotes 状态已更新为新值。
    • 第二次渲染后:只有第二个 useEffect 会运行,因为它正在“侦听”刚刚发生变化的 quotes 变量的变化。然后你可以用它来设置一些你说的状态。

    【讨论】:

    • 我同意这在语义上是正确的;但是,我不建议这样做。在初始加载 quotes 时设置某些状态与如上所示每次设置 quotes 之间存在巨大的差异。一个简单的例子是如果quotes 被清除,例如。
    • 但是 OP 在您回答的 cmets 中说他想“使用quotes 数组设置要在页面中显示的另一个状态”。你会建议什么来做到这一点?我认为如果quotes 被清除,您也可以在useEffect 中处理这种情况,对吧?我发布的是我常用的方法,但我想学习其他方法。谢谢!
    • 好吧,如果 quotes 数组在这里仅用作中介(无论如何,为了设置其他状态),我认为最好在一个单个useEffect 而不是两个。我将在下面用我认为在这种情况下应该做的事情来更新我的答案。 :)
    【解决方案2】:

    这是意料之中的。以下是您的代码的工作方式:

    1. quotessetQuotesuseState 函数返回。
    2. useEffect 在您的组件安装后第一次运行。 quotes(空数组)和setQuotes在此函数中可用。
    3. 当你的 axios 请求完成时,你 setQuotes。但是,有两件事:1 - 这不会立即更新状态的值。 2 - 在 useEffect 的上下文中,quotes 仍然是一个空数组 - 当您执行 setQuotes(result.data) 时,您正在创建一个新数组,并且在此上下文中将无法直接访问。
    4. 因此,console.log(quotes); 将给出一个空数组。

    取决于您尝试使用 quotes 的目的。为什么不直接使用result.data

    更新:我在想可能是这样的:

    function QuoteGenerator() {
      const [quotes, setQuotes] = useState([]);
      const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
    
      useEffect(() => {
        axios(quotesURL).then(result => {
          console.log(result);
          setQuotes(result.data);
          setSomeOtherState(); // why not do it here?
        });
      }, []);
    }
    

    通过这种方式,您可以更密切地控制数据,而无需将其交给生命周期方法。

    【讨论】:

    • 我想使用quotes数组来设置另一个将在页面中显示的状态
    • @JibinThomas 在这种情况下,与公认的答案相反,我建议您直接使用第一个 .then 回调中的状态。添加第二个 useEffect 钩子会增加复杂性,但不会增加太多好处。
    【解决方案3】:

    另一种重构代码的方式:

    const quotesURL = "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
    
    
    function QuoteGenerator = ({ quote }) => {
      const [quotes, setQuotes] = useState([]);
      const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
    
      const fetchQuote = async quote => {
        const result = await axios.get(quotesURL);
        setQuotes(result.data);
      };
    
        useEffect(() => {
          fetchQuote(quote);
        }, [quote]);
    };
    

    所以现在你的QuoteGenerator 函数组件中有一个函数,称为fetchQuoteuseEffect 钩子允许我们使用类似生命周期方法的东西,有点像结合 componentDidMountcomponentDidUpdate 生命周期方法。在这种情况下,我调用了useEffect,并在每次该组件最初呈现到屏幕时以及组件更新时运行一个函数。

    您在其他答案中看到,第二个参数作为空数组传递。我将quote 作为该空数组中的第一个元素,因为它在我的示例中作为道具传递,但在其他示例中不是,因此他们有一个空数组。

    如果你想了解为什么我们使用空数组作为第二个参数,我认为最好的解释方式是引用 Hooks API:

    如果你想运行一个效果并且只清理一次(在挂载和卸载时),你可以传递一个空数组([])作为第二个参数。这告诉 React 你的效果不依赖于任何来自 props 或 state 的值,所以它永远不需要重新运行。这不是作为特殊情况处理的——它直接遵循依赖项数组的工作方式。

    如果您传递一个空数组 ([]),则效果中的 props 和 state 将始终具有它们的初始值。而将 [] 作为第二个参数传递更接近于熟悉的 componentDidMount 和 componentWillUnmount 心理模型...

    我们调用setQuotes 代替setState,这用于更新引号列表,我传入了新的引号数组result.data

    所以我传入fetchQuote,然后将提供给quote组件的prop传递给它。

    useEffect 中空数组的第二个参数非常强大,并且不容易立即为每个人解释和/或理解。例如,如果您在没有空数组作为第二个参数的情况下执行类似 useEffect(() => {}) 的操作,则该 useEffect 函数将不间断地向 JSON 服务器端点或其他请求发出请求。

    如果您将useEffect(() => {}, []) 与空数组一起使用,它只会被调用一次,这与在基于类的组件中使用componentDidMount 相同。

    在我上面给出的示例中,我正在检查以限制 useEffect 被调用的频率,我传入了 props 的值。

    我没有将异步函数放在 useEffect 中的原因是因为我的理解是,如果我们传递异步函数或返回 Promise 的函数,至少根据错误,我们不能使用 useEffect我以前见过。

    话虽如此,有一个解决该限制的方法,如下所示:

    useEffect(
      () => {
        (async quote => {
           const result = await axios.get(quotesURL);
           setQuotes(result.data);
         })(quote);
      },
      [quote]
    );
    

    这是一个更令人困惑的语法,但它应该有效,因为我们正在定义一个函数并立即调用它。类似于这样:

    (() => console.log('howdy'))()

    【讨论】:

      猜你喜欢
      • 2019-12-05
      • 2017-03-26
      • 2020-11-14
      • 2020-10-22
      • 1970-01-01
      • 2021-02-20
      • 1970-01-01
      • 2020-03-11
      • 1970-01-01
      相关资源
      最近更新 更多