次の React コンポーネントがあります。
export default class MyComponent extends React.Component {
onSubmit(e) {
e.preventDefault();
var title = this.title;
console.log(title);
}
render(){
return (
...
<form className="form-horizontal">
...
<input type="text" className="form-control" ref={(c) => this.title = c} name="title" />
...
</form>
...
<button type="button" onClick={this.onSubmit} className="btn">Save</button>
...
);
}
};
コンソールに次のようなメッセージが表示されましたundefined
。このコードのどこが間違っているのか、何かアイデアはありますか?
ベストアンサー1
ここでは、使用している(または使用しなければならない)React のバージョンと、フックを使用するかどうかに応じて、3 つの答えがあります。
まず最初に:
Reactがどのように動作するかを理解することは重要です。そうすれば、適切に作業を行うことができます(ヒント:実行してみる価値は十分にあります)ReactチュートリアルReact の Web サイトにあります。よく書かれていて、実際にやり方を説明する形ですべての基本事項を網羅しています。ここでの「適切に」とは、Web ページを書いているのではなく、ブラウザーでレンダリングされるアプリケーションのユーザー インターフェイスを書いているということです。実際のユーザー インターフェイスの作業はすべて React で行われ、「Web ページを書くときに慣れているもの」では行われません (これが、React アプリが実際には「アプリ」であり、「Web ページ」ではない理由です)。
React アプリケーションは、次の 2 つの要素に基づいてレンダリングされます。
- コンポーネントのプロパティは、そのコンポーネントのインスタンスを作成する親によって宣言され、親はライフサイクルを通じてそれを変更できます。
- コンポーネント自身の内部状態。コンポーネント自身のライフサイクルを通じて、それを変更することができます。
React を使用するときに明示的に行っていないことは<input>
、HTML 要素を生成してそれを使用することです。たとえば、React に を使用するように指示すると、HTML 入力要素が作成されるのではなく、React アプリを Web 用にコンパイルしたときに HTML 入力要素としてレンダリングされる React 入力オブジェクトを作成するように React に指示し、イベント処理は React によって制御されます。
React を使用する場合、ユーザーに (多くの場合は操作可能な) データを提示するアプリケーション UI 要素を生成し、ユーザーの操作によって定義した方法でアプリケーションの状態を変更します。ユーザーが実行したアクションによってコンポーネントのプロパティまたは状態が更新され、React はそれをシグナルとして使用して変更されたコンポーネントの新しい UI 表現を生成します。これにより、アプリケーション インターフェイスの一部が更新され、新しい状態が反映されることがあります。
このプログラミング モデルでは、アプリの内部状態が最終的な権限であり、「ユーザーが見て操作する UI」ではありません。ユーザーが入力フィールドに何かを入力しようとしても、それを処理するための記述がなければ何も起こりません。UI はアプリケーションの状態を反映したもので、その逆ではありません。事実上、このプログラミング モデルでは、ブラウザー DOM はほとんど後付けです。ブラウザー DOM は、地球全体がアクセスできることがほぼ保証されている非常に便利な UI フレームワークです (ただし、React が使用できるのはブラウザー DOM だけではありません)。
具体例
では、ここまでの説明を踏まえて、React でユーザーが入力要素と対話する仕組みを見てみましょう。まず、ユーザーが対話するための UI 要素を用意する必要があります。
onChange
ユーザー データを処理する関数を使用して、ユーザー向けの文字列データを管理 (つまり、保存と表示の両方) するコンポーネントを作成しました。- コンポーネントのレンダリング コードは、React によってコンポーネント
input
(DOM<input>
要素ではない) を含む仮想 DOM を生成するために使用され、onChange
ハンドラーをそのコンポーネントにバインドして、React イベント データで呼び出すことができるようにします (したがって、これは DOMchange
イベント リスナーではなく、通常の DOM イベント リスナーと同じイベント データを取得しないことに注意してください)。 - 次に、React ライブラリは仮想 DOM をユーザーが操作できる UI に変換し、アプリケーションの状態が変化すると更新されます。ブラウザで実行されるため、HTML 入力要素を構築します。
次に、ユーザーは実際にその入力要素を操作しようとします。
- ユーザーは入力要素をクリックして入力を開始します。
- 入力要素にはまだ何も起こりません。代わりに、入力イベントは React によってインターセプトされ、すぐに終了します。
- React はブラウザ イベントを React イベントに変換し、
onChange
React イベント データを使用して仮想 DOM コンポーネントの関数を呼び出します。 - その関数は、その記述方法に基づいて何かを実行する可能性があります。この場合、ユーザーが入力した内容 (しようとした内容) に基づいてコンポーネントの状態を更新するために記述されたことはほぼ間違いありません。
- 状態の更新がスケジュールされると、React は近い将来にその状態の更新を実行し、更新後にレンダリング パスをトリガーします。
- レンダリング パス中に、状態が実際に異なるかどうかを確認し、異なる場合は一時的な 2 番目の仮想 DOM を生成し、それをアプリケーションの仮想 DOM (の一部) と比較し、新しい一時的なものと同じに見えるようにアプリケーションの仮想 DOM で実行する必要がある追加/更新/削除操作のセットを決定し、それらの操作を適用して一時的な仮想 DOM を再度破棄します。
- 次に、仮想 DOM の現在の外観を反映するように UI を更新します。
- そして、これらすべてが完了すると、ユーザーが実際に見ているページの DOM が更新され、入力要素に入力した内容が表示されます。
したがって、これは通常のブラウザ モデルとはまったく異なります。ユーザーが最初にテキスト ボックスに入力して UI データを更新し、コードが「そのテキスト ボックスの現在の値」を読み取って次に状態を判断するのではなく、React は既に状態を認識しており、イベントを使用して最初に状態を更新し、次にUI を更新します。
そして、これらすべてが事実上瞬時に行われることを覚えておくことが重要です。そのため、ユーザーにとっては、任意の Web ページと同じように入力要素にテキストを入力したように見えますが、内部ではまったく異なる処理が行われていますが、結果は同じです。
さて、それでは、React の要素から値を取得する方法を見てみましょう。
コンポーネント クラスと ES6 (React 16+ および 15.5 移行)
React 16 (および 15.5 のソフト スタート) 以降、createClass
呼び出しはサポートされなくなり、クラス構文を使用する必要があります。これにより、2 つの点が変更されます。明らかなクラス構文と、 "無料で" 実行できるthis
コンテキスト バインディングです。そのため、引き続き機能するようにするには、ハンドラー内のコンテキストを保持する匿名関数createClass
に "ファット アロー" 表記を使用していることを確認してください。次のコードで使用しているのがその例です。this
onWhatever
onChange
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.reset();
}
reset() {
// Always set the initial state in its own function, so that
// you can trivially reset your components at any point.
this.state = {
inputValue: ''
};
}
render() {
return (
// ...
<input value={this.state.inputValue} onChange={evt => this.updateInputValue(evt)}/>
// ...
);
},
updateInputValue(evt) {
const val = evt.target.value;
// ...
this.setState({
inputValue: val
});
}
});
bind
次のように、すべてのイベント処理関数のコンストラクターで が使用されているのを見たことがあるかもしれません。
constructor(props) {
super(props);
this.handler = this.handler.bind(this);
...
}
render() {
return (
...
<element onclick={this.handler}/>
...
);
}
そんなことはしないでください。
を使用する場合、ほとんどの場合bind
、「やり方が間違っている」という格言が当てはまります。クラスは既にオブジェクト プロトタイプを定義しているため、インスタンス コンテキストも既に定義されています。bind
その上に を置かないでください。コンストラクターですべての関数呼び出しを複製するのではなく、通常のイベント転送を使用してください。複製するとバグの発生範囲が広がり、問題がコードの呼び出し場所ではなくコンストラクターにある可能性があるため、エラーの追跡がはるかに困難になります。
「しかし、再レンダリング時に関数を常に作成して破棄しています!」これは本当かもしれませんが、あなたは気付かないかもしれません。ユーザーも同様です。イベント ハンドラーのガベージ コレクションがパフォーマンスのボトルネックになっている場合は、すでに多くの問題が発生しているため、立ち止まって設計を再検討する必要があります。React が非常にうまく機能する理由は、UI 全体を更新するのではなく、変更された部分のみを更新するためです。適切に設計された UI では、UI の大部分が大幅に変更されない時間は、UI の小さな部分の更新に要する時間を大幅に上回ります。
フック付き関数コンポーネント (React 16.8+)
React 16.8では、関数コンポーネント(文字通り引数として何かを取る関数でprops
、クラスを書かずにコンポーネントクラスのインスタンスのように使用できます)にも状態を与えることができます。フック。
完全なクラス コードが必要でなく、単一のインスタンス関数で十分な場合は、フックを使用しuseState
て単一の状態変数とその更新関数を取得できます。これは、上記の例とほぼ同じように動作しますが、「ユニバーサル」setState
関数呼び出しがなく、操作する値ごとに 1 つの専用の状態セッターを使用します。
import { useId, useState } from 'react';
function myFunctionalComponentFunction(props) {
const id = useId();
const [input, setInput] = useState(props?.value ?? '');
return (
<div>
<label htmlFor={id}>Please specify:</label>
<input id={id} value={input} onInput={e => setInput(e.target.value)}/>
</div>
);
}
以前は、クラスと関数コンポーネントの非公式な区別は「関数コンポーネントには状態がない」というものでしたが、もうその区別に隠れることはできません。関数コンポーネントとクラスコンポーネントの違いは、非常によく書かれた数ページにわたって記載されています。反応ドキュメント(都合よく誤解されるような、簡単な一行の説明はありません!) これを読んで、自分が何をしているのかを理解し、抱えている問題を解決するために最善の (それが何を意味するにせよ) ソリューションを選択したかどうかを知る必要があります。
React 15以下、レガシーES5とcreateClass
適切に処理するには、コンポーネントに状態値を設定します。この状態値は入力フィールドで表示され、UI 要素に変更イベントをコンポーネントに送り返すことで更新できます。
var Component = React.createClass({
getInitialState: function() {
return {
inputValue: ''
};
},
render: function() {
return (
//...
<input value={this.state.inputValue} onChange={this.updateInputValue}/>
//...
);
},
updateInputValue: function(evt) {
this.setState({
inputValue: evt.target.value
});
}
});
そこで、React に、updateInputValue
関数を使用してユーザー操作を処理し、を使用して状態の更新をスケジュールするように指示します。また、が を利用するというsetState
ことは、状態の更新後に再レンダリングされると、ユーザーが入力した内容に基づいて更新テキストが表示されることを意味します。render
this.state.inputValue
コメントに基づく補足
UI 入力は状態値を表すと仮定します (ユーザーがタブを途中で閉じてタブが復元された場合に何が起こるかを考えてみましょう。ユーザーが入力したすべての値を復元する必要がありますか? そうであれば、それが状態です)。大きなフォームには数十または 100 の入力フォームが必要であるように感じるかもしれませんが、React は UI を保守しやすい方法でモデル化することです。独立した入力フィールドが 100 個あるのではなく、関連する入力のグループがあるため、各グループをコンポーネントでキャプチャし、グループのコレクションとして「マスター」フォームを構築します。
MyForm:
render:
<PersonalData/>
<AppPreferences/>
<ThirdParty/>
...
これは、巨大な単一のフォーム コンポーネントよりもメンテナンスがはるかに簡単です。グループを状態メンテナンスを備えたコンポーネントに分割し、各コンポーネントが一度にいくつかの入力フィールドを追跡する役割のみを担うようにします。
これらすべてのコードを書くのは「面倒」だと感じるかもしれませんが、それは誤った節約です。将来のあなたを含む、あなた以外の開発者は、すべての入力が明示的に接続されているのを見ることで、コードパスの追跡がはるかに簡単になるので、実際には大きな利益を得ています。ただし、いつでも最適化できます。たとえば、状態リンカーを書くことができます。
MyComponent = React.createClass({
getInitialState() {
return {
firstName: this.props.firstName || "",
lastName: this.props.lastName || ""
...: ...
...
}
},
componentWillMount() {
Object.keys(this.state).forEach(n => {
let fn = n + 'Changed';
this[fn] = evt => {
let update = {};
update[n] = evt.target.value;
this.setState(update);
});
});
},
render: function() {
return Object.keys(this.state).map(n => {
<input
key={n}
type="text"
value={this.state[n]}
onChange={this[n + 'Changed']}/>
});
}
});