ReactJS中的Array.push函数在setState中无效

10

我正在制作一个原始的测验应用,目前有3个问题,全部都是真或假。在我的handleContinue方法中,有一个调用将用户从单选表单中的输入推入userAnswers数组的操作。它可以正常运行第一次handleContinue,之后它会抛出一个错误:Uncaught TypeError: this.state.userAnswers.push is not a function(…)

import React from "react"

export default class Questions extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      questionNumber: 1,
      userAnswers: [],
      value: ''
    }

    this.handleContinue = this.handleContinue.bind(this)
    this.handleChange = this.handleChange.bind(this)
  }

  //when Continue button is clicked
  handleContinue() {
    this.setState({
      //this push function throws error on 2nd go round
      userAnswers: this.state.userAnswers.push(this.state.value),
      questionNumber: this.state.questionNumber + 1
    //callback function for synchronicity
    }, () => {
      if (this.state.questionNumber > 3) {
        this.props.changeHeader(this.state.userAnswers.toString())
        this.props.unMount()
      } else {
        this.props.changeHeader("Question " + this.state.questionNumber)
      }
    })
    console.log(this.state.userAnswers)
  }

  handleChange(event) {
    this.setState({
      value: event.target.value
    })
  }

  render() {
    const questions = [
      "Blargh?",
      "blah blah blah?",
      "how many dogs?"
    ]
    return (
      <div class="container-fluid text-center">
        <h1>{questions[this.state.questionNumber - 1]}</h1>
        <div class="radio">
          <label class="radio-inline">
            <input type="radio" class="form-control" name="trueFalse" value="true" 
            onChange={this.handleChange}/>True
          </label><br/><br/>
          <label class="radio-inline">
            <input type="radio" class="form-control" name="trueFalse" value="false" 
            onChange={this.handleChange}/>False
          </label>
          <hr/>
          <button type="button" class="btn btn-primary" 
          onClick={this.handleContinue}>Continue</button>
        </div>
      </div>
    )
  }
}
6个回答

22

不要直接修改状态!通常情况下,尽量避免改变数据

Array.prototype.push()会对数组进行就地更改。因此,在setState内部向数组中push元素时,实际上是通过使用push来更改原始状态。由于push返回新的数组长度而非实际数组,因此将this.state.userAnswers设置为数字值。这就是为什么第二次运行时会出现Uncaught TypeError: this.state.userAnswers.push is not a function(…)的原因,因为您无法将元素添加到数字上。

您需要使用Array.prototype.concat(),它不会更改原始数组,并返回一个新数组,其中包含新连接的元素。这就是您在setState内部想要做的事情。您的代码应该如下所示:

this.setState({
  userAnswers: this.state.userAnswers.concat(this.state.value),
  questionNumber: this.state.questionNumber + 1
}

6

Array.push不会返回新的数组。尝试使用

this.state.userAnswers.concat([this.state.value])

这将返回新的userAnswers数组。
参考文献:array pusharray concat

3
这个答案解决了问题,但并没有真正解释正在发生的事情。我建议看一下我的答案,它更详细地解释了这个问题。 - p4sh4

6

在处理状态对象时,您应该将其视为不可变的。但是,您需要重新创建数组,使其指向一个新对象,设置新项,然后重置状态。

 handleContinue() {
    var newState = this.state.userAnswers.slice();
    newState.push(this.state.value);

    this.setState({
      //this push function throws error on 2nd go round
      userAnswers: newState,
      questionNumber: this.state.questionNumber + 1
    //callback function for synchronicity
    }, () => {
      if (this.state.questionNumber > 3) {
        this.props.changeHeader(this.state.userAnswers.toString())
        this.props.unMount()
      } else {
        this.props.changeHeader("Question " + this.state.questionNumber)
      }
    })
    console.log(this.state.userAnswers)
  }

另一种替代上述解决方案的方法是使用.concat(),因为它本身返回一个新数组。这相当于创建一个新变量,但代码更短。

this.setState({
  userAnswers: this.state.userAnswers.concat(this.state.value),
  questionNumber: this.state.questionNumber + 1
}

这个可以正常工作,但是每次运行handleContinue时声明一个新变量似乎不是最优的选择。虽然我可能错了,你有什么想法? - Jackson Lenhart
2
你是对的,这确实会浪费内存,但这比直接改变状态可能引发的潜在竞态条件要好。 - Shubham Khatri
1
这不是一个很好的答案。虽然从技术上讲它可以工作,但更简短和清晰的方法是只使用 concat,而且文档也建议这样做。 - p4sh4
这并不总是有效的。我刚遇到了一个问题,同样的方法在一个组件中可以工作,但在另一个具有相同属性的组件中却无法工作。 最好使用concat代替。 - anshuraj

4

在较新的React版本中,建议使用更新器函数来修改状态以防止竞态条件:

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

3

我已经找到了一个解决方案。这个方案适用于splice和其他操作。假设我有一个状态,它是一个汽车数组:

this.state = {
  cars: ['BMW','AUDI','mercedes']
};
this.addState = this.addState.bind(this);

现在,addState是我将用来向数组添加新项的方法。应该如下所示:

  addState(){

let arr = this.state.cars;
arr.push('skoda');
this.setState({cars: arr});
}

我是通过duwalanise的解决方案找到的这个方法。我所要做的就是返回新的数组,以便添加新的项目。 我遇到了很多次这种问题。 我将尝试更多的函数来看看它是否真的适用于所有通常不起作用的功能。 如果有人有更好的想法如何用更干净的代码实现这一点,请随时回复我的帖子。


2

当您想要将某些内容推送到状态时,正确的状态变更方式是执行以下操作。假设我们已经定义了一个状态,如下所示:

const [state, setState] = useState([])

现在我们希望将以下对象推入状态数组中。我们使用concat方法来实现此操作,如下所示:
let object = {a: '1', b:'2', c:'3'}

现在要将这个对象推入状态数组中,您需要执行以下操作:
setState(state => state.concat(object))

您会发现您的状态已经填充了该对象。 concat 能够正常工作但 push 不能是因为以下原因:

Array.prototype.push() adds an element into original array and returns an integer which is its new array length.



Array.prototype.concat() returns a new array with concatenated element without even touching in original array. It's a shallow copy.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接