制御文
繰り返し処理
配列のmapメソッドを使う。
コールバック関数ではJSXをreturnする。
※key属性にユニーク値を指定しないとコンソールに警告が表示される
<div>
{[20, 40, 50].map((num, index) =>
<p key={index}>{num}</p>
)}
</div>
<div>
{[{key: 1, id: 150, label: 'itemB'}, {key: 223, id: 2, label: 'itemA'}].map((item) =>
<Loop {...item}></Loop>
)}
</div>
条件分岐
三項演算子または&&を使う。{ flg ? <Loop id="new" key="new"></Loop> : null}
※trueの場合がReact要素なので、falseの場合は空文字ではなくnullとするべき
trueの場合のみの出力であれば&&演算子を使う。{ flg && <Loop id="new" key="new"></Loop>}
※ 書籍内の説明文が間違っていたので著者のサポートページから報告したところ、以下の返信が届いた。
ご指摘ありがとうございました。
ご指摘の通り、falseの場合にスキップ、となります。
以下のように修正させて戴ければと存じます。
—
「&&」演算子は左辺がtrueの場合に(全体がtrueであることが明らかなので)右辺の実行をスキップします(ショートカット演算)。ここでは、この性質を利用しているわけです。
→
「&&」演算子は左辺がfalseの場合に(全体がfalseであることが明らかなので)右辺の実行をスキップします(ショートカット演算)。ここでは、この性質を利用してtrueの場合にのみ右辺を出力しているわけです。
—
https://wings.msn.to/index.php/-/A-05/WGS-JSF-004/
サポートページにも訂正が掲載されていた。
対応していただいた著者に感謝(^^)
{}
内ではifやswitchなどの制御文を使うことができない。
条件や出力が複雑な場合は、関数やメソッドとして定義する。
(React要素をreturnする)
renderWithCondition(isMorning) {
if (isMorning) {
return <span>GoodMorning</span>
} else {
return <span>Hello</span>
}
}
// と定義して{this.renderWithCondition(true)}で表示。
即時関数を使うこともできるが、ソースの見通しが悪くなる。
{(()=>{
if (true) {
return <span>123456789</span>
}
})()}
イベント
イベント処理
メソッドを定義しonClickやonKeyUpでイベントハンドラとして登録する。
※属性名はキャメルケースでなければならない
ハンドラ内ではthisがイベントソースへの参照となってしまうが、.bind(this)
とすることでクラスへの参照となる。
(bindの戻り値は関数オブジェクト)
メソッド内でthisを利用しないのであればonChange={this.show}
でも問題ないが、thisの有無によってコードを書き分けるのは現実的ではない。
イベントハンドラにはbindでthisをセットする、と機械的に覚えてしまうとよい。
export default class EventTest extends React.Component{
show(e) {
console.log(`msg=${this.props.msg}, value=${e.currentTarget.value}`);
}
render() {
return (
<input type="text" onChange={this.show.bind(this)} />
);
}
}
イベントハンドラの引数に渡されるオブジェクトは、Reactによってブラウザ間の仕様差を吸収したSyntheticEventである。
.currentTargetやpreventDefault()など通常のイベントオブジェクトと同じように使うことができる。
(Reactではfalseを返してもデフォルトで抑止しないため、明示的にpreventDefaultを呼び出す必要がある)
生のイベントオブジェクトが必要な場合は、.nativeEventで取得できる。
HTMLのイベント属性との違い
onClick={this.show()}
のようにHTML属性の感覚で括弧を付けてはいけない。
render()が実行される度に、そのメソッドも実行されてしまうため。 https://blog.katsubemakito.net/react/react1st-8-event
HTMLの属性の場合、ブラウザがハンドラ関数を作成してくれている。<input type="button" id="button" onclick="func()">
は以下と同じ意味となる。button.onclick = function() {
func();
};
https://qiita.com/nb_tomo/items/8fb3f52674bea5a5173e
bind方法
- コンストラクタでbindする
constructor(props) {
super(props);
this.show = this.show.bind(this);
}
としておけばJSXは<input type=”text” onChange={this.show} />と書ける。
※ 筆者のオススメでもあり、通常はこの方法を使う - 属性値にアロー関数で宣言する
<input type="text" onChange={(e)=>this.show(e)} />
アロー関数内のthisはコール時のレシーバではなく、定義時のスコープとなるため。 - イベントハンドラをクラスプロパティで宣言する
export default class App extends React.Component {
show = (e) => {
// 処理
}
}
prop-typesと同様に、クラスプロパティはECMAScriptの実験的機能のため、create-react-appではbabelによって変換される。
※将来的には、この方法が一般的になるかもしれない
コンストラクタでbindするメリット
- bindすべきイベントハンドラが増えてくると、1箇所にまとまっていた方が可読性が上がる。
- 同じイベントハンドラを複数のイベントに登録する場合、JSXでbindすると複数回bindを呼び出すことになる。
(コンストラクタであればbindは1回になる)
複数回の場合はコンストラクタ、1回の場合はJSXでbind、と書き分けるのは混乱のもとなので、コンストラクタで統一するべき。 - JSXでのbindとアロー関数では、レンダリングの度に関数を生成するため、僅かながらオーバーヘッドが大きいという問題もある。
イベントハンドラに引数を渡す
HTML5のカスタムデータ属性を使うのが無難。<input type="text" onChange={this.show} data-hoge="引数"/>
bindメソッドの第2引数以降を利用することもできるが、bindをHTML属性に書くことになるのでやめた方がよい。<input type="text" onChange={this.show.bind(this, '引数')}/>
イベントハンドラでは、イベントオブジェクトが最後の引数となる。show(str, e) {
console.log(`value=${e.currentTarget.value}, str=${str}`);
}
イベントプーリング
パフォーマンス上の理由からSyntheticEventはプーリング(使い回し)される。
イベントハンドラを抜けると中身がクリアされるため、非同期処理で参照することはできない。
show(e) {
console.log(e.currentTarget);
setTimeout(() => {
console.log(e.currentTarget); // null
}, 1000);
}
AJAXやsetTimeout、ReactのsetStateなどでは参照できない!
setStateによるstateの書き換えは非同期処理である。
(setStateが呼ばれたタイミングでstateが書き換えられるわけではない。React内部でまとめられて非同期で行われる。)
イベントハンドラの中でe.persist();
とするとクリアされなくなる。
(またはconst currentTarget = e.currentTarget;
のようにして、自力でイベントオブジェクトのプロパティを変数に代入しておいてもよい)
State
Stateとは
コンポーネントの状態を管理するためのオブジェクト。
- Props
親コンポーネントから値を受け取るための読み取り専用オブジェクト。 - State
コンポーネントの状態を管理するための書き込み可能オブジェクト。
フォームへ入力された値を保持したい場合などはStateを使う。
(リアクティブにする変数は全てStateで管理する)
コンストラクタで初期化、this.setState(オブジェクト)
で更新、this.state.xxx
で参照。this.setState()
は更新したいプロパティだけ指定すればよい。(内部でマージされる)this.setState()
を使わずにthis.state = オブジェクト;
としてもDOMに反映されない。
初期化だけ代入、あとはsetStateと覚えるとよい。
export default class StateTest extends React.Component{
constructor(props) {
super(props);
this.state = {
current: new Date()
};
setInterval(() => {
this.setState({
current: new Date()
});
}, 1000);
}
render() {
return (
<span>{this.state.current.toLocaleString()}</span>
);
}
}
setStateメソッドの引数に関数を渡す
setStateメソッドは通常は引数にオブジェクトを渡すが、関数を渡すこともできる。
関数は第1引数に変更前のState、第2引数にPropsを受け取る。
戻り値として新しくセットするオブジェクトを返却する。
handleCurrentClick() {
this.setState((prevState, props) => {
this.dateForInput = prevState.current;
return {
current: '2000-01-01 00:00:00'
}
});
}
子コンポーネントから親コンポーネントのstateを更新する
stateを更新するメソッドをpropsとして子コンポーネントに渡すことで、子から親への情報伝達が可能となる。
// 親
updateState(state) {
this.setState(state);
}
render() {
return (
<div>
<Ko updateParent={ this.updateState.bind(this) }>ボタン</button>
</div>
);
}
// 子
render() {
return (
<div>
<button onClick={ this.props.updateParent({id: 100}) }>ボタン</button>
</div>
);
}
フォームの値を扱う
Stateを使う。
handleAddressChange(event) {
this.setState({
address: event.currentTarget.value
});
}
render() {
return (
<input type="text" name="address" value={this.state.address} onChange={this.handleAddressChange.bind(this)}/>
);
}
stateのプロパティ名とname属性を合わせておくと、プロパティ名を動的にすることができる。
(ES2015のcomputed property name構文=動的プロパティ
※処理が同じであればハンドラをまとめることができ、要素ごとにハンドラを用意する必要がなくなる。this.setState({
[event.currentTarget.name]: event.currentTarget.value
});
Uncontrolled Component
フォームがStateで管理されたコンポーネントをControlled Componentと呼ぶ。
Stateで管理せず、必要なときに要素から値を取得するコンポーネントをUncontrolled Componentと呼ぶ。
通常はControlled Componentを利用するべきだが、既存コードにReactアプリに変換する場合はUncontrolled Componentを検討してもよい。
(Uncontrolled Componentはあまり多用すべきでないと公式でアナウンスされている)
実装方法
- インスタンス変数を
React.createRef()
で初期化 - ref属性にインスタンス変数をセット
this.xxx.current
で要素オブジェクトを取得
Uncontrolled Componentではinputの初期値をdefaultValue属性(radio, checkboxはdefaultChecked)としなければならない。
※value属性にすると値が変更されなくなってしまう。
constructor(props) {
super(props);
this.hugaRef = React.createRef();
}
hoge() {
console.log(this.hugaRef.current.value);
}
render() {
return (
<input type="text" ref={this.hugaRef} defaultValue="テスト" onChange="{this.hoge.bind(this)}"/>
)
}