React.Componentのメンバ変数は、再レンダー時に更新されるとは限らない!


ReactでsetState()しても値が更新されなくてハマったので、記事化します。

更新すべき値をメンバ変数に格納した場合

ボタンを押すと値が+1されるアプリケーションを作ります。

以下は間違ったコードです。
Appでカウンターの値を管理し、Counterはその値とカウントボタンを表示します。

app.js
import React from 'react'
import ReactDOM from 'react-dom'
import Counter from './counter'

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {count: 0}
    this.increment = this.increment.bind(this)
  }

  increment() {
    this.setState(
      (state) => {return {count: state.count + 1}}
    )
  }

  render() {
    return (
      <Counter count={this.state.count} onClick={this.increment} />
    )
  }
}

let app = document.getElementById('app')
ReactDOM.render(<App />, app)

counter.js
import React from 'react'

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.count = this.props.count
  }

  render() {
    // 以下の`this.count`は、新しいpropsを反映しない!
    return (
      <div>
        <p>{this.count}</p>
        <button onClick={this.props.onClick}>カウント</button>
      </div>
    )
  }
}

export default Counter

このコードを実行し、ボタンをクリックしても、カウンターの値は増加しないと思います。

レンダーごとに更新する値は、メンバ変数に保存しない

値が更新されない原因は、表示する値がthis.countになっていることです。
counter.jsを以下のように修正すれば、ちゃんとカウント値が増加します。

counter.js
import React from 'react'

class Counter extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <div>
        <p>{this.props.count}</p>
        <button onClick={this.props.onClick}>カウント</button>
      </div>
    )
  }
}

export default Counter

今回は、this.props.countを表示するだけなので、わざわざメンバ変数に格納することはありませんが、表示させるべき値が複雑になる場合、renderメソッドの戻り値に直接式を書かずに、結果を変数に格納したい場合があります。そういう場合は、たとえば関数にすると良いと思います。

counter.js
class Counter extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    let count = this._getCount()
    return (
      <div>
        <p>{count}</p>
        <button onClick={this.props.onClick}>カウント</button>
      </div>
    )
  }

  _getCount() {
    let count;
    // すごく複雑な処理
    return count
  }
}

以上です。