为什么在 setState 中获取时进行两个网络调用?

Why are two network calls being made, when fetch in setState?

当我在 setState 中使用 fetch 时,该函数发出了两个网络请求,但我期望一个请求。

为什么会发生这种情况以及如何预防?

import React from 'react';

class TestFetch extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {

    this.setState(() => {

      fetch('http://example.com/', {
        mode: 'no-cors'
      })
        .then(data => {
          console.log(data)
        });
      });
  }

  render() {
    return (
      <button onClick={this.handleClick}> Test </button>
    )
  }
}

export default TestFetch

在获取中使用 setState 的另一个版本。现在我有一个网络调用,但是点击后我的状态有两个值:

import React from 'react';

class TestFetch extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      'newItems': []
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {

    fetch('http://example.com/', {
      mode: 'no-cors'
    })
      .then(data => {

        this.setState((state) => {
          state.newItems.push("value")
        })

        console.log(this.state)
      });
  }

  render() {
    return (
      <button onClick={this.handleClick}> Test </button>
    )
  }
}

export default TestFetch

好的,基本上这个例子也是这个效果:

import React from 'react';

class TestFetch extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      'newItems': []
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => {
      state.newItems.push("value")
    })
    console.log(this.state);
  }

  render() {
    return (
      <button onClick={this.handleClick}> Test </button>
    )
  }
}

export default TestFetch

不要在 setState 中执行 api 调用。获取状态变量并在其中存储 api 响应数据,并在需要时使用状态变量。

 import React from 'react';
    
    class TestFetch extends React.Component {
      constructor(props) {
        super(props);
        this.state = {appData: null};
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
    
     fetch('http://example.com/', {
            mode: 'no-cors'
          })
            .then(data => {
              console.log(data)
     this.setState(() => {appData: data});
            });
       
      }
    
      render() {
        return (
          <button onClick={this.handleClick}> Test </button>
        )
      }
    }
    
    export default TestFetch

Why is this happening...

我的猜测是您正在将您的应用渲染到 React.StrictMode 组件中。参见 Detecting unintentional side-effects

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods
  • Class component static getDerivedStateFromProps method
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, or useReducer

换句话说,setState 被 React 调用了两次,以帮助您发现无意的副作用,例如两次获取。

...and how to prevent it?

只是不要在 setState 回调函数中产生副作用。您可能打算执行 fetch 并处于 Promise 链更新状态。

handleClick() {
  fetch('http://example.com/', {
    mode: 'no-cors'
  })
    .then(data => {
      console.log(data);
      this.setState( ......); // <-- update state from response data
    });
}

更新

Another version with setState in the fetch. Now I have one network call, but two values in my state after one click:

在您更新的代码中,您正在改变状态对象。 Array.prototype.push 通过将新元素添加到数组末尾和 return 数组的新长度来更新数组。

Array.prototype.push

this.setState(state => {
  state.newItems.push("value") // <-- mutates the state object
})

我相信您看到添加了 2 个新项目,原因与上述相同。在状态中更新数组时,您需要 return 一个 new 数组引用。

您可以使用Array.prototype.concat添加新值return一个新数组:

this.setState(prevState => {
  newItems: prevState.newItems.concat("value"),
});

另一种常见的模式是将先前的状态数组浅复制到一个新数组中并附加新值:

this.setState(prevState => {
  newItems: [...prevState.newItems, "value"],
});

此外,一旦您整理好状态更新,状态的控制台日志将不起作用,因为 React 状态更新是异步处理的。从 componentDidUpdate 生命周期方法记录更新状态。

componentDidUpdate(prevProps, prevState) {
  if (prevState !== this.state) {
    console.log(this.state);
  }
}