イベント処理
イベント処理
Reactのイベント処理は DOM 要素のイベントの処理と似ているが、イベント処理文法的な違いがある。
- camelCaseで名付ける
- JSX では関数を渡す
- デフォルト動作の抑止に
preventDefault
を使う
HTML
<button onclick="activeLases()"> Active Lasers </button>
React
<button onClick={activeLases}> Active Lasers </button>
デフォルトの動作を抑止するには、 preventDefault
が必須。新しいページを開くというリンクの動作を例に考えてみる。
React は既存のプロジェクトに徐々に追加していけるのでした。今回は、既存の書き方に React を追加してみる。 HTML
<div id="root"></div> <a href="https://github.com/facebook/react" onclick="console.log('The link was clicked.'); return true" >Click me: Vanilla JavaScript case ture</a > <br /> <a href="https://github.com/facebook/react" onclick="console.log('The link was clicked.'); return false" >Click me: Vanilla JavaScript case false</a > <script type="text/babel"> function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="https://github.com/facebook/react" onClick={handleClick}> Click me: React.js </a> ); } ReactDOM.render(<ActionLink />, document.getElementById('root')); </script>
e
は合成イベント。ブラウザ間の互換性を心配する必要はない。ネイティブのイベントと React のイベントは同様に動作するのではない。
理解できていない。初心者は細かいことを気にせず、 e
を使えばいいだろう。
要素が最初にレンダーされる際にリスナを指定する。
一般的なパターンではイベントハンドラはクラスのメソッドになる。
class Toggle extends React.Component { constructor(props) { super(props); this.state = { isToggleOn: true }; // このバインディングは、コールバックで `this`を機能させるために必要 this.handleClick = this.handleClick.bind(this); // this.handleClick = handleClick; console.log(this); } // Event listener handleClick() { this.setState((prevState) => ({ isToggleOn: !prevState.isToggleOn, })); } render() { // onClick属性にイベントを添付 return <button onClick={this.handleClick}>{this.state.isToggleOn ? 'ON' : 'OFF'}</ button>; } } ReactDOM.render(<Toggle />, document.getElementById('root'));
クラスのメソッドはデフォルトではバインドされない。
bind
呼び出しを回避する方法の1つは、クラスフィールドを使うこと。
class LoggingButton extends React.Component { // This syntax ensures `this` is bound within handleClick. // Warning: this is *experimental* syntax. handleClick = () => { console.log('this is:', this); } render() { return ( <button onClick={this.handleClick}> Click me </button> ); } }
パブリックスタティックフィールドとパブリックインスタンスフィールドは、書き込み可能、列挙可能、設定可能なプロパティです。
を回避する方法の2つめは、コールバック内でアロー関数を使うこと。
class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // This syntax ensures `this` is bound within handleClick return ( <button onClick={() => this.handleClick()}> Click me </button> ); } }
コールバックが props
の下層のコンポーネントに渡される場合、余分に再描画される。コンストラクタでバインドするかクラスフィールド構文を使用して問題を避けること。
useStat を使う
ここまで見てきた this
の問題は hooks
の1つである useState
を使って回避できる。クラスコンポーネントでは、オブジェクトであった状態管理が、 hooks
を使うことによって、変数とこの変数を更新する専用関数になるからです。
const Toggle = () => { const [toggle, setToggle] = React.useState(true); return ( <div> <button onClick={() => setToggle(!toggle)}>{toggle ? 'ON' : 'OFF'}</ button> </div> ); }; ReactDOM.render(<Toggle />, document.getElementById('root'));
私は初心者なので、 hooks
を使えば、本当にパフォーマンスの問題を避けられているのかわからない。
イベントハンドラに引数を渡す
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上記の 2 行は等価である。
React イベントを表す引数 e
は ID
の 2 番目の引数として渡される。 bind
の場合には id
以降の引数は自動的に転送される。
Function.prototype.bind
が使われている下行に e
がないのに引数として渡されることに違和感しかありません。 Rest parameters (残余引数) の形をとっていれば納得もできるのですが...
条件付きレンダー
条件付きレンダーはJavaScript における条件分岐と同じように動作する。
if
や条件演算子を使って現在の状態を表す要素を作成すれば、 React は一致するように UI を高維新する。
以下の 2 つのコンポーネントで考える。
function UserGreeting(props) { return <h1>Welcome back!</h1>; } function GuestGreeting(props) { return <h1>Please sign up.</h1>; }
ユーザがログインしているかどうかによって、これらのコンポーネントの一方だけを表示する Greeting
コンポーネントを作成する。
function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <UserGreeting />; } return <GuestGreeting />; } ReactDOM.render(<Greeting isLoggedIn={true} />, document. getElementById('root')); // ReactDOM.render(<Greeting isLoggedIn={false} />, document. getElementById('root'));
要素変数
要素を保持しておくために変数を使うことができる。出力の他の部分を変えずにコンポーネントの一部を条件付きでレンダーしたい時に役立つ。
ログアウトとログインボタンを表す2つのコンポーネントで考える。
function LoginButton(props) { return ( <button onClick={props.onClick}> Login </button> ); } function LogoutButton(props) { return ( <button onClick={props.onClick}> Logout </button> ); }
LoginControl
というステート付きコンポーネントを作成する。
LoginControl
コンポーネントは、現在の state
によって <LoginButton />
または <LogoutButton />
の一方をレンダーする。前の例の <Greeting />
もレンダーする。
class LoginControl extends React.Component { constructor(props) { super(props); this.handleLoginClick = this.handleLoginClick.bind(this); this.handleLogoutClick = this.handleLogoutClick.bind(this); this.state = { isLoggedIn: false }; } handleLoginClick() { this.setState({ isLoggedIn: true }); } handleLogoutClick() { this.setState({ isLoggedIn: false }); } render() { const isLoggedIn = this.state.isLoggedIn; let button; if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } return ( <div> <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); } } function UserGreeting(props) { return <h1>Welcome back!</h1>; } function GuestGreeting(props) { return <h1>Please sign up.</h1>; } function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <UserGreeting />; } return <GuestGreeting />; } function LoginButton(props) { return <button onClick={props.onClick}>Login</button>; } function LogoutButton(props) { return <button onClick={props.onClick}>Logout</button>; } ReactDOM.render(<LoginControl />, document.getElementById('root'));
条件付きでレンダーの核心部分は以下の通り。
if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; }
JSXでインラインで条件を記述するより短い構文がある。
論理 && 演算子によるインライン If
JSX に式として埋め込むに {}
で囲むこと。条件に応じて要素を含めたいときに便利。
const Mailbox = ({ unreadMessages }) => { const messages = unreadMessages; return ( <div> <h1>Hello!</h1> {messages.length > 0 && <h2>You have {messages.length} unread messages.</h2>} </div> ); }; const messages = ['React', 'Re: React', 'Re:Re: React']; // const messages = []; ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'));
true && expression
--> 必ずexpression
が評価されるfalse && expresiion
--> 必ずfalse
が評価される- falsy な値の場合、
&&
の後の評価はスキップするが、 falsy な値そのものは返される
falsyな値は何があったかな?私を モダンJavaScript の世界に羽ばたかせてくれた JavaScript Primer のここを読み返せば解決です。
インライン If-Else
条件的に要素をレンダーするもうひとつの方法は condition ? true : false
条件演算子を利用すること。
render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. </div> ); }
三項演算子ですね。短く書けるので便利に使っています。
条件が複雑すぎると考えたら、コンポーネントを抽出するタイミングかもしれないとのこと。
重要な事柄がさらっと紹介されている。
コンポーネントのレンダーを防ぐ
自コンポーネントが他のコンポーネントによりレンダーされているが、自コンポーネントを隠すには、レンダー出力の代わりに null
を返す。
WarningBanner
コンポーネントは warn
プロパティの値に応じて、プロパティの値が false
ならレンダーされない。
コンポーネントの render
から null
を返してもライフサイクルに影響はしない。
コードが随分長くなってきました。たまには、スタイリングもしてみましょう。
const button = { height: '40px', width: '200px', }; const warning = { backgroundColor: '#ff0000', textAlign: 'center', width: '100%', padding: '10px', }; function WarningBanner(props) { if (!props.warn) { return null; } return ( <div className="warning" style={warning}> Warning! </div> ); } class Page extends React.Component { constructor(props) { super(props); this.state = { showWarning: true }; this.handleToggleClick = this.handleToggleClick.bind(this); } handleToggleClick() { this.setState((prevState) => ({ showWarning: !prevState.showWarning, })); } render() { return ( <div> <WarningBanner warn={this.state.showWarning} /> <button onClick={this.handleToggleClick} style={button}> {this.state.showWarning ? 'Hide' : 'Show'} </button> </div> ); } } ReactDOM.render(<Page />, document.getElementById('root'));