1. ホーム

react error TypeError: 未定義のプロパティ 'setState' を読み取ることができません。

2022-02-09 16:18:37

コードは次のようになります。

class test extends Component {
    constructor(props) {
        super(props);
        this.state = {
            liked: false
        };
    }
    handleClick(event) {
        this.setState({liked: !this.state.liked});
    }
    render() {
        var text = this.state.liked ? 'liked' : 'disliked';
        return (
            <div onClick={this.handleClick}>
                You<b>{text}</b>me. Tap me to switch states.
            </div>
        );
    }

}
export default test;

正常にページが表示できるようになります。


しかし、ボタンが押されるとエラーが報告されます。


なぜこのようなことが起こるのでしょうか?

なぜなら、ボタンをクリックしたとき、handleClick()メソッドでthisに到達する頃には、もうコンポーネント内ではthisではなくなっているからです。

最初の解決策は、これを手動でバインドすることです。

constructor(props) {
    super(props);
    this.state = {
        liked: false
    };
}

に変更する。

constructor(props) {
    super(props);
    this.state = {
        liked: false
    };
    this.handleClick = this.handleClick.bind(this);//manual binding
}

2つ目の解決策は
handleClick(event) {
        this.setState({liked: !this.state.liked});
}

に変更します。
handleClick= (e) => {
        this.setState({liked: !this.state.liked});
}

この解決策で問題が解決したことで、別の疑問が生まれました。関数がReactコンポーネントのメソッドである場合、矢印関数と通常の関数の違いは何でしょうか?

例えば、以下の2つのaの定義はどう違うのでしょうか?

class App extends Component {
  a() {
    console.log(1)
  }

  a = () => {
    console.log(1)
  }
}

最初のaは、いうまでもなくprototypeメソッドの定義です。ルーズモードでのES5相当は

App.prototype.a = function() {}

2つ目は、ステージ2です。 パブリッククラスフィールド 中の書き方だと、babelの下では クラスプロパティの変換プラグイン でエスケープします。これに相当するのは

class App extends Component {
  constructor (.. .args) {
    super(... .args)
    this.a = () => {
        console.log(1)
    }
  }
}


なぜ第二の書き方が必要なのか?

Reactでは、クラスのprototypeメソッドをpropsで子コンポーネントに渡す場合、従来の書き方ではbind(this)が必要で、そうしないとメソッドを実行した時にthisが見つからない。

<button onClick={this.handleClick.bind(this)}></button>


または

<button onClick={(e) => this.handleClick(e)}></button>


これは醜く、ReactコンポーネントのshouldComponentUpdateの最適化に影響を及ぼします。

これは、ReactがshouldComponentUpdateを提供することで、開発者が不要なレンダリングを避けるための制御を行うためであり、React. PureComponentは、propsとstateの値が同じである限り、コンポーネントを再レンダリングすることはありません。

しかし、bind this を使用すると、親がレンダリングするたびに子に渡される props.onClick が変化し、手動で shouldComponentUpdate を実装しない限り、PureComponent の Shallow Compare は基本的に失敗します。

Public Class Fieldsを使ってこのように書けば、この問題は解決します。他にもプロトタイプメソッドを定義してコンストラクタ内でバインドしたり、デコレータを使ってバインドしたりと、いくつかの方法があります。

class A {
  constructor() {
    this.a = this.a.bind(this)
  }

  a() {}

  // or
  @bindthis
  b() {}
}

そして arrow関数は、それ以外にもコードが少なくなっています。通常の関数との最大の違いは、関数が宣言された時点でこのスコープが定義されていることで、一般的には関数が宣言された時点で暗黙のうちにthisとして定義されています。

var a = ()=>{
    console.log(this)
}
// equivalent to
var a = function(){
    console.log(this)
}.bind(this);

a(); //Window
var b = function(){
    console.log(this)
};
b(); //Window
var obj = { a,b };
obj.a(); //Window
obj.b(); //obj


arrow関数の最大の効果は、これを通常の動的スコープ(実行位置を基準とした値)から静的スコープ(定義位置を基準とした値、すなわちレキシカルスコープ)に変更したことである。
詳しくは、以下の公式ドキュメントをご覧ください。 https://reactjs.org/docs/handling-events.html