web-dev-qa-db-ja.com

イベントハンドラ内でReactインスタンス(this)にアクセスできない

私はES6で(BabelJSを使って)単純なコンポーネントを書いていますが、関数this.setStateは動作していません。

典型的なエラーには、次のようなものがあります。

未定義のプロパティ 'setState'を読み取れません

または

this.setStateは関数ではありません

なぜなのかご存知ですか?これがコードです:

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass
193
user3696212

this.changeContentは、onChange propとして渡される前にthis.changeContent.bind(this)を介してコンポーネントインスタンスにバインドする必要があります。そうしないと、関数本体のthis変数はコンポーネントインスタンスを参照せず、windowを参照します。 Function :: bind を参照してください。

ES6クラスの代わりにReact.createClassを使用すると、コンポーネントに定義されているライフサイクル以外のすべてのメソッドは自動的にコンポーネントインスタンスにバインドされます。 自動バインド を参照してください。

関数をバインドすると新しい関数が作成されることに注意してください。 renderで直接バインドすることができます。つまり、コンポーネントがレンダリングされるたびに新しい関数が作成されるか、コンストラクタでバインドされて1回だけ起動されます。

constructor() {
  this.changeContent = this.changeContent.bind(this);
}

vs

render() {
  return <input onChange={this.changeContent.bind(this)} />;
}

参照はReact.refsではなくコンポーネントインスタンスに設定されます。React.refs.somerefthis.refs.somerefに変更する必要があります。 sendContentメソッドをコンポーネントインスタンスにバインドして、thisがそれを参照するようにする必要もあります。

225

Morhausは正しいですが、これはbindがなくても解決できます。

クラスのプロパティプロポーザル と一緒に arrow function を使用することができます。

class SomeClass extends React.Component {
  changeContent = (e) => {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return <input type="text" onChange={this.changeContent} />;
  }
}

Arrow関数はコンストラクタのスコープ内で宣言されているので、そしてarrow関数は宣言しているスコープからthisを維持しているので、すべてうまくいきます。ここでの欠点は、これらがプロトタイプ上の関数ではなく、それらがすべて各コンポーネントで再作成されることです。ただし、bindは同じ結果になるため、これはあまりマイナス面ではありません。

95
Kyeotic

この問題は、React.createClass()コンポーネント定義構文からES6クラスのReact.Componentを拡張する方法に移行する際に、私たちが最初に経験することの1つです。

これはReact.createClass()extends React.Componentthisコンテキストの違いが原因です。

React.createClass()を使用すると自動的にthisコンテキスト(values)を正しくバインドしますが、ES6クラスを使用する場合はそうではありません。 ES6の方法で(React.Componentを拡張することによって)行う場合、thisコンテキストはデフォルトでnullです。クラスのプロパティはReactクラス(コンポーネント)インスタンスに自動的にはバインドされません。


この問題を解決するためのアプローチ

私は全部で4つの一般的なアプローチを知っています。

  1. クラスコンストラクタ内で関数をバインドします。 JSXに触れることをまったく避け、各コンポーネントの再レンダリングで新しい関数を作成しない、ベストプラクティスのアプローチとして多くの人に考えられています。

    class SomeClass extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
      }
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick}></button>
        );
      }
    }
    
  2. 関数をインラインでバインドします。あなたはまだいくつかのtutorials/articles/etcであちこちで使われているこのアプローチを見つけることができるので、あなたがそれを知っていることは重要です。これは#1と同じ概念ですが、関数をバインドすると、再レンダリングごとに新しい関数が作成されることに注意してください。

    class SomeClass extends React.Component {
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick.bind(this)}></button>
        );
      }
    }
    
  3. 太い矢印機能を使用します。矢印関数まで、すべての新しい関数は独自のthis値を定義しました。ただし、arrow関数は独自のthisコンテキストを作成しないため、thisはReactコンポーネントインスタンスの本来の意味を持ちます。したがって、次のことが可能です。

    class SomeClass extends React.Component {
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={ () => this.handleClick() }></button>
        );
      }
    }
    

    または

    class SomeClass extends React.Component {
      handleClick = () => {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick}></button>
        );
      }
    }
    
  4. ユーティリティ関数ライブラリを使用して自動的に関数をバインドします。そこにいくつかのユーティリティライブラリがあり、それは自動的にあなたのために仕事をします。ここでは、いくつか言及するだけで人気のあるものをいくつか紹介します。

    • Autobind Decorator は、メソッドが分離されている場合でも、クラスのメソッドをthisの正しいインスタンスにバインドするNPMパッケージです。パッケージは、メソッドの前に@autobindを使用して、コンポーネントのコンテキストへの正しい参照thisをバインドします。

      import autobind from 'autobind-decorator';
      
      class SomeClass extends React.Component {
        @autobind
        handleClick() {
          console.log(this); // the React Component instance
        }
        render() {
          return (
            <button onClick={this.handleClick}></button>
          );
        }
      }
      

      Autobind Decoratorは、アプローチ#1と同じように、コンポーネントクラス内のすべてのメソッドを一度にバインドできるほど賢いものです。

    • Class Autobind このバインディングの問題を解決するために広く使用されているもう1つのNPMパッケージです。 Autobind Decoratorとは異なり、decoratorパターンは使用しませんが、実際にはコンストラクタ内でコンポーネントのメソッドを正しい参照に自動的にバインドする関数を使用するだけです。 thisの.

      import autobind from 'class-autobind';
      
      class SomeClass extends React.Component {
        constructor() {
          autobind(this);
          // or if you want to bind only only select functions:
          // autobind(this, 'handleClick');
        }
        handleClick() {
          console.log(this); // the React Component instance
        }
        render() {
          return (
            <button onClick={this.handleClick}></button>
          );
        }
      }
      

      PS:他の非常によく似たライブラリは React Autobind です。


勧告

私があなただったら、私はアプローチ#1に固執するでしょう。ただし、クラスコンストラクターで大量のバインドが発生したら、すぐにアプローチ4で説明したヘルパーライブラリのいずれかを探索することをお勧めします。


その他の

それはあなたが抱えている問題とは無関係ですが、あなたは refを使いすぎてはいけません

あなたの最初の傾向はあなたのアプリで "物事を起こさせる"ために参照を使うことかもしれません。その場合は、少し時間をかけて、コンポーネント階層のどこで状態を所有するのかについて、もっと批判的に考えてください。

同様の目的のために、あなたが必要とするものと同じように、 制御されたコンポーネント を使うことが好ましい方法です。 Component state を使用することを検討することをお勧めします。だから、あなたは単にこのような値にアクセスすることができます:this.state.inputContent

48
Kaloyan Kosev

これまでの回答ではソリューションの基本的な概要(つまり、バインディング、矢印関数、これを行うデコレーター)を提供しましたが、実際に説明するwhyこれは必要です。これは混乱の根源であり、不必要な再バインドや他の人がやっていることを盲目的にたどるなどの不必要なステップにつながります。

thisは動的です

この特定の状況を理解するには、thisがどのように機能するかを簡単に紹介します。ここで重要なことは、thisはランタイムバインディングであり、現在の実行コンテキストに依存するということです。したがって、一般に「コンテキスト」と呼ばれるのはなぜですか。現在の実行コンテキストに関する情報を提供し、バインドする必要があるのは、「コンテキスト」を失うためです。しかし、スニペットの問題を説明しましょう:

const foobar = {
  bar: function () {
    return this.foo;
  },
  foo: 3,
};
console.log(foobar.bar()); // 3, all is good!

この例では、予想どおり3を取得します。しかし、次の例を見てください。

const barFunc = foobar.bar;
console.log(barFunc()); // Uh oh, undefined!

3はどこに行きましたか?答えは"context"、またはexecuteの機能にあります。関数の呼び出し方法を比較します。

// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();

違いに注意してください。最初の例では、barメソッドの正確な場所を指定しています1 foobarオブジェクトにあります:

foobar.bar();
^^^^^^

ただし、2番目の方法では、メソッドを新しい変数に格納し、その変数を使用して、メソッドが実際に存在する場所を明示せずにメソッドを呼び出します、したがってコンテキストを失います

barFunc(); // Which object is this function coming from?

そして、そこに問題があります。変数にメソッドを保存すると、そのメソッドの場所(メソッドが実行されているコンテキスト)に関する元の情報が失われます。この情報がないと、実行時に、JavaScriptインタープリターが正しいthisをバインドする方法がありません。特定のコンテキストがないと、thisは期待どおりに機能しません2

Reactに関連する

以下は、thisの問題に悩まされているReactコンポーネント(簡潔にするために短縮された)の例です。

handleClick() {
  this.setState(({ clicks }) => ({ // setState is async, use callback to access previous state
    clicks: clicks + 1, // increase by 1
  }));
}

render() {
  return (
    <button onClick={this.handleClick}>{this.state.clicks}</button>
  );
}

しかし、なぜ、前のセクションはこれにどのように関連していますか?これは、同じ問題の抽象化に苦しんでいるためです。 Reactがイベントハンドラーを処理する

// Edited to fit answer, React performs other checks internally
// props is the current React component's props, registrationName is the name of the event handle prop, i.e "onClick"
let listener = props[registrationName];
// Later, listener is called

したがって、onClick={this.handleClick}を実行すると、メソッドthis.handleClickが最終的に変数listenerに割り当てられます3。しかし、this.handleClicklistenerに割り当てたため、handleClickの発信元を正確に指定しなくなったため、問題が発生しました。 Reactの観点から見ると、listenerは単なるオブジェクトであり、オブジェクト(この場合はReactコンポーネントインスタンス)に関連付けられていません。コンテキストが失われたため、インタープリターはthisの値を使用してinsidehandleClickを推測できません。

バインディングが機能する理由

インタプリタが実行時にthis値を決定する場合、なぜハンドラをバインドしてdoes work?これは、Function#bindを使用して、実行時にguaranteethis値を使用できるためです。これは、関数に内部thisバインディングプロパティを設定することで行われ、thisを推測しないようにします。

this.handleClick = this.handleClick.bind(this);

おそらくコンストラクターでこの行が実行されると、現在のthisがキャプチャされます(Reactコンポーネントインスタンス)から、まったく新しい関数の内部thisバインディングとして設定され、 Function#bind。これにより、実行時にthisが計算されるときに、インタープリターは何も推測しようとせず、指定されたthis値を使用します。

矢印関数のプロパティが機能する理由

現在、矢印関数クラスのプロパティは、トランスピレーションに基づいてBabelで機能します。

handleClick = () => { /* Can use this just fine here */ }

になる:

constructor() {
  super();
  this.handleClick = () => {}
}

そして、これは、矢印関数がnot独自のthisをバインドするが、それらを囲むスコープのthisを取るという事実のために機能します。この場合、constructorthisは、Reactコンポーネントインスタンスを指し、正しいthisを提供します。4


1 オブジェクトにバインドされることになっている関数を参照するために「メソッド」を使用し、そうでない場合には「関数」を使用します。

2 2番目のスニペットでは、thisがデフォルトのグローバル実行コンテキスト(strictモードでない場合はwindow、または特定のコンテキストを介して決定できない場合はundefined)にデフォルト設定されるため、3ではなくundefinedが記録されます。また、例ではwindow.fooは存在しないため、未定義になります。

3 イベントキュー内のイベントが実行される方法のうさぎの穴を下る場合、リスナーで invokeGuardedCallback が呼び出されます。

4 実際にはもっと複雑です。 Reactは、独自に使用するためにリスナーでFunction#applyを内部的に使用しようとしますが、thisをバインドしないため、矢印関数は機能しません。つまり、矢印関数内のthisが実際に評価されると、モジュールの現在のコードの各実行コンテキストの各字句環境でthisが解決されます。最終的にthisバインディングを持つように解決される実行コンテキストisコンストラクター。現在のReactコンポーネントインスタンスを指すthisがあり、機能することができます。

3
Li357

私のお勧めは、プロパティとして矢印関数を使うことです。

class SomeClass extends React.Component {
  handleClick = () => {
    console.log(this); // the React Component instance
  }
  render() {
    return (
      <button onClick={this.handleClick}></button>
    );
  }
}

と同様に矢印機能を使用しないでください。

class SomeClass extends React.Component {
      handleClick(){
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={()=>{this.handleClick}}></button>
        );
      }
    }

2番目のアプローチでは、実際にはレンダリング呼び出しごとに新しい関数が生成されるため、後でパフォーマンスを気にする場合はReact.PureComponentまたはReact.ComponentをオーバーライドすることができますshouldComponentUpdate(nextProps、nextState)そして小道具が到着したときに浅いチェック

1
Ivan Mjartan

次のようにコンストラクタ内のイベント関数とコンポーネントをバインドする必要があります。

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
    this.changeContent = this.changeContent.bind(this);
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

ありがとう

1
Liju Kuriakose

あなたはこれらのステップに従ってこれを解決することができます

SendContent関数を次のように変更します。

 sendContent(e) {
    console.log('sending input content '+this.refs.someref.value)
  }

でレンダリング機能を変更

<input type="text" ref="someref" value={this.state.inputContent} 
          onChange={(event)=>this.changeContent(event)} /> 
   <button onClick={(event)=>this.sendContent(event)}>Submit</button>
1
azmul hossain

クラス内の関数のインスタンスを取得するには、関数をbindthisにする必要があります。例のように

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

このようにしてthis.stateは有効なオブジェクトになります。

1
Asif J

3つの方法でこれに取り組むことができます

1.次のようにコンストラクタ自体にイベント関数をバインドする

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
    this.changeContent = this.changeContent.bind(this);
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

2.呼び出されたらバインドする

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent.bind(this)}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

3.矢印機能を使って

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={()=>this.sendContent()}>Submit</button>
      </div>
    )
  }
}

export default SomeClass
1
Liju Kuriakose

イベントハンドラで状態や小道具を操作するには、関数にバインディングが必要です。

ES5では、イベントハンドラー関数はコンストラクター内でのみバインドし、renderでは直接バインドしないでください。あなたが直接renderでバインディングをするならば、それはあなたのコンポーネントがレンダリングして再レンダリングする度に新しい関数を作成します。だからあなたは常にコンストラクタでそれをバインドするべきです

this.sendContent = this.sendContent.bind(this)

ES6では、矢印機能を使う

あなたが矢印関数を使うとき、あなたは束縛をする必要はなく、あなたはスコープ関連の問題からも遠ざかることができます

sendContent = (event) => {

}
0
Hemadri Dasari

溶液:

  1. 明示的にバインドしなくても、bindname__にメソッド名を付けると、thisname__のコンテキストを維持する太い矢印関数の構文> {})==を使用できます。
import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      inputContent: 'startValue'
    }
  }

  sendContent = (e) => {
    console.log('sending input content ',this.state.inputContent);
  }

  changeContent = (e) => {
    this.setState({inputContent: e.target.value},()=>{
      console.log('STATE:',this.state);
    })
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" value={this.state.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

その他の解決策:

  1. クラスコンストラクターで関数をバインドします。

  2. JSXテンプレート内の関数を中括弧を使用してバインドする{} {this.methodName.bind(this)}

0
khizer

Alexandre Kirszenbergは正しいですが、注意を払うべきもう一つの重要なことは、あなたがあなたの束縛を置くところです。私は何日もの間状況にとどまっていましたが(おそらく私は初心者だからです)、他の人とは違って、私はバインドについて知っていたので(私はすでにこれを適用しました)エラーバインドの順序が間違っていたことがわかりました。

もう1つの理由は、 "this.state"内で関数を呼び出していたことです。これは、バインド行を超えているためにバインドを認識していませんでした。

以下は私が持っていたものです(ところで、これは私の初めての投稿ですが、私は他に解決策を見つけることができなかったので、私はそれが非常に重要だと思いました):

constructor(props){
    super(props);

       productArray=//some array

    this.state={ 
        // Create an Array  which will hold components to be displayed
        proListing:productArray.map(product=>{return(<ProRow dele={this.this.popRow()} prodName={product.name} prodPrice={product.price}/>)})
    }

    this.popRow=this.popRow.bind(this);//This was the Issue, This line //should be kept above "this.state"
0
Ebe

ES6を使用しているので、関数は "this"コンテキストに自動的にはバインドされません。手動で関数をコンテキストにバインドする必要があります。

constructor(props) {
  super(props);
  this.changeContent = this.changeContent.bind(this);
}
0
Niraj Raskoti

バインドをコンストラクタ構文で保持したい場合は、 Proposal-bind-operator を使用して、コードを次のように変換します。

constructor() {
  this.changeContent = ::this.changeContent;
}

の代わりに :

constructor() {
  this.changeContent = this.changeContent.bind(this);
}

はるかに単純で、bind(this)fatArrowは不要です。

0
Ze Rubeus

this.changeContentonClick={this.sendContent}は、コンポーネントのインスタンスの this にバインドされていないため、この問題は発生しています。

ES6のarrow関数を使用して周囲のコードと同じ字句スコープを共有し、 this を維持するための別の解決策(constructor()でbind()を使用することに加えて)があります。 render()は次のようになります。

render() {
    return (

        <input type="text"
          onChange={ () => this.changeContent() } /> 

        <button onClick={ () => this.sendContent() }>Submit</button>

    )
  }
0
Bassam Rubaye

この問題はreact15.0以降に発生します。このイベントハンドラはコンポーネントに自動バインドしませんでした。そのため、イベントハンドラが呼び出されるたびにこれを手動でコンポーネントにバインドする必要があります。


この問題を解決する方法はいくつかあります。しかし、あなたはどの方法が最善か、そしてその理由を知る必要がありますか?一般的に、クラスコンストラクタ内であなたの関数を束縛するか、あるいは矢印関数を使うことをお勧めします。

// method 1: use a arrow function
    class ComponentA extends React.Component {
      eventHandler = () => {
        console.log(this)
      }
      render() {
        return ( 
        <ChildComponent onClick={this.eventHandler} /> 
        );
      }

// method 2: Bind your functions in the class constructor.
    class ComponentA extends React.Component {
      constructor(props) {
        super(props);
        this.eventHandler = this.eventHandler.bind(this);
      }
      render() {
        return ( 
        <ChildComponent onClick={this.eventHandler} /> 
        );
      }

これら2つのメソッドは、コンポーネントが毎回レンダリングされるときに新しい関数を作成することはありません。だから私たちのChildComponentは新しい関数の小道具の変更のためにreRenderしないでしょう、あるいはパフォーマンスの問題を引き起こすかもしれません。

0
jack.lin

bind(this)はこの問題を修正できますが、最近ではbindを使用したくない場合、これを達成するために別の2つの方法を使用できます。

1)従来の方法として、コンストラクターでbind(this)を使用できるため、関数をJSXコールバックとして使用する場合、thisのコンテキストはクラス自体になります。

class App1 extends React.Component {
  constructor(props) {
    super(props);
    // If we comment out the following line,
    // we will get run time error said `this` is undefined.
    this.changeColor = this.changeColor.bind(this);
  }

  changeColor(e) {
    e.currentTarget.style.backgroundColor = "#00FF00";
    console.log(this.props);
  }

  render() {
    return (
      <div>
        <button onClick={this.changeColor}> button</button>
      </div>
    );
  }
}

2)関数を矢印関数を持つクラスの属性/フィールドとして定義する場合、bind(this)を使用する必要はありません。

class App2 extends React.Component {
  changeColor = e => {
    e.currentTarget.style.backgroundColor = "#00FF00";
    console.log(this.props);
  };
  render() {
    return (
      <div>
        <button onClick={this.changeColor}> button 1</button>
      </div>
    );
  }
}

3)JSXコールバックとして矢印関数を使用する場合、bind(this)も使用する必要はありません。さらに、パラメーターを渡すことができます。よさそうですね。しかし、その欠点はパフォーマンスの問題です。詳細については ReactJS doco を参照してください。

class App3 extends React.Component {
  changeColor(e, colorHex) {
    e.currentTarget.style.backgroundColor = colorHex;
    console.log(this.props);
  }
  render() {
    return (
      <div>
        <button onClick={e => this.changeColor(e, "#ff0000")}> button 1</button>
      </div>
    );
  }
}

そして、これらのコードスニペットをデモするために Codepen を作成しました。

0
Eric Tan

関数呼び出しを自分で束縛することを気にしたくない場合は、こんにちは。 'class-autobind'を使用してそれをインポートすることができます

import autobind from 'class-autobind';

class test extends Component {
  constructor(props){
  super(props);
  autobind(this);
}

それが動作しませんので、スーパーコールの前に自動バインドを書かないでください

0
Florent Giraud