目次
React
[Node.js]
スケルトン作成
- React 開発の全体像を把握しつつ開発環境を整える
- React の単純なサンプルに React Router を適用する
- React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する
- React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む
- React環境->React Router->Redux-Saga->SuperAgent に Bootstrapを適用する
導入
- https://facebook.github.io/react/docs/installation.html
- https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#table-of-contents
- ReactのSPAを作成するのによい方法
npm install -g create-react-app
PS C:\workspaces\vscode\reactlesson> create-react-app react-lesson Creating a new React app in C:\workspaces\vscode\reactlesson\react-lesson. Installing packages. This might take a couple minutes. Installing react, react-dom, and react-scripts... :
- アプリケーションが作成されたら実行
PS C:\workspaces\vscode\reactlesson> cd react-lesson PS C:\workspaces\vscode\reactlesson> npm start
- 実行された
- /src/App.js を書き換えてみる
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; const element = (<h1>Hello,world</h1>); class App extends Component { render() { return ( element ); } } export default App;
- 反映された
- リリース準備ができたら、以下を実行することで、buildフォルダ以下に最適化されたアプリケーションを作成する
npm run build
クイックスタート
JSX
const element = <h1>Hello,world!</h1>;
- 文字列でも、HTMLでもなく、JSX
- JavaScriptの拡張文法
- テンプレートと思われるかもしれないが、完全なJavaScript
- JSXはReactの要素を生成する
JSX表現
- どのようなJavaScriptの表現も、中括弧で囲むことでJSXに埋め込むことができる
- const element = (<h1>Hello, {formatName(user)}!</h1>);
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World</title> <script src="https://unpkg.com/react@latest/dist/react.js"></script> <script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> function formatName(user) { return user.firstName + ' ' + user.lastName; } const user = {firstName:'Hiroto', lastName:'Yagi'}; const element = (<h1>Hello, {formatName(user)}!</h1>); ReactDOM.render( element, document.getElementById('root') ); </script> </body> </html>
- コンパイルされたJSXは通常のJavaScriptオブジェクト
- JSXをif文やforループ、変数への割り当て、引数や戻り値に利用できる
function greeting(user) { var now = new Date(); if (5 <= now.getHours() && now.getHours() <= 12) { return <h1> Good morning {user.firstName + ' ' + user.lastName}.</h1>; } else { return <h1> Hello {user.firstName + ' ' + user.lastName}.</h1>; } }
属性に利用
- 中括弧を利用して、二重引用符なし(使用すると文字列リテラルとして扱われる)で直接利用できる。
<div id="root"></div> <script type="text/babel"> function greeting(user) { var now = new Date(); if (5 <= now.getHours() && now.getHours() <= 12) { return <h1> Good morning <a href={user.webpageUrl}>{user.firstName + ' ' + user.lastName}</a>.</h1>; } else { return <h1> Hello <a href={user.webpageUrl}>{user.firstName + ' ' + user.lastName}</a>.</h1>; } } const user = {firstName:'Hiroto', lastName:'Yagi', webpageUrl:'http://typea.info'}; const element = (<h1>{greeting(user)}</h1>); ReactDOM.render( element, document.getElementById('root') );
子要素
- 子要素がない場合、XML同様 /> で閉じる
- 子要素を含む
- JSXはHTMLよりJavaScriptにより近い。ReactDOMはキャメルケースプロパティ(HTMLでは、class がclassName、HTMLではtabindexがtabIndexなど)を持つ
function greeting(user) { return <div> <h1> Good morning {user.firstName + ' ' + user.lastName}.</h1> <h2><a href={user.webpageUrl}>webpage</a></h2> </div> ; } const user = {firstName:'Hiroto', lastName:'Yagi', webpageUrl:'http://typea.info'}; const element = (greeting(user)); ReactDOM.render( element, document.getElementById('root') );
インジェクション攻撃の予防
function greeting(user, title) { return <div> <h1>{title}</h1> <h2> Good morning {user.firstName + ' ' + user.lastName}.</h2> <h2><a href={user.webpageUrl}>webpage</a></h2> </div> ; } const title = "<input type='button'>"; const user = {firstName:'Hiroto', lastName:'Yagi', webpageUrl:'http://typea.info'}; const element = (greeting(user,title)); ReactDOM.render( element, document.getElementById('root') );
JSX Represent オブジェクト
- Babelは、React.createElement()を呼び出しコンパイルを行う
- 以下の2つは同じ意味
const element1 = (<h1 className='greeting'>hello</h1>); const element2 = React.createElement('h1',{className:'greeting'},'hello');
- React.createElement()は、バグを防ぐ手助けをするが、本質的には以下のようなオブジェクトを生成する
// 簡易表現 const element = {type:'h1',props:{className:'greeting',children:'hello'}};
Elementsのレンダリング
- Elements はReact アプリケーションの最小のビルディングブロック
- 1つの要素は、画面上に表示するものを表現する
- ブラウザのDOM要素と異なり、React Elements はプレーンなオブジェクトで生成のコストは小さい
- React DOMは、DOMをReact Elementsに一致するように取り扱う
1つの要素をDOMの中にレンダリング
<div id="root"></div>
React要素の更新
function tick() { const element = (<div>{(new Date()).toLocaleTimeString()}</div>); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick,1000);
Reactは必要なもののみ更新する
毎tick()の呼び出しで、すべてのUIツリーを生成するよう記述してるが、変更が発生したテキストノードのみReact DOMにより更新されている。
コンポーネントと Props
- コンポーネントはUIを独立し再利用可能な部分に分割する。
- 概念的にコンポーネントはJavaScriptの関数のようなもの。
- コンポーネントはpropsと呼ばれる任意の入力を受け付け、React Elementを返す。
コンポーネントの機能とクラス
- JavaScriptの関数としてコンポーネントを定義する
- この関数は有効なReactコンポーネント、なぜなら、単一のprops引数を引数として取り、React Elementを返す。
- このようなコンポーネントを"functional"と呼ぶ。
function Welcome(props) { return <h1>Hello,{props.name}</h1>; }
- ES6のクラスをコンポーネントの定義として利用できる
class Welcome extends React.Component { render() { return <h1>Hello,{props.name}</h1>; } }
- 上記2つのコンポーネントはReactの視点からは同じ
コンポーネントのレンダリング
- Elementは、ユーザー定義コンポーネントも表すことができる
function Welcome(props) { return <h1>Hello,{props.name}</h1>; } const element = <Welcome name='Hiroto' /> ReactDOM.render( element, document.getElementById('root') );
- Reactがユーザー定義コンポーネントを表示するときに、JSXの属性からコンポーネントへ"props"としてオブジェクトが渡される。
コンポーネント名はいつも大文字から始める。DOM タグは、<div /> だが、<Welcome /> はコンポーネントを表現する。Welcomeがスコープに存在すること。
コンポーネントの構成
- コンポーネントはその出力において、他のコンポーネントに影響を与えることができる
- 同じコンポーネントをどんなレベルの詳細にも抽象的に利用できる、ボタン、フォーム、ダイアログ、スクリーン
- Reactでこれらは、一般にコンポーネントで表現される
function Welcome(props) { return <h1>Hello,{props.name}</h1>; } function App() { return ( <div> <Welcome name="Yagi"/> <Welcome name="Kaela"/> <Welcome name="Hiroto"/> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
- 一般的に新しいReactアプリケーションは、一つのAppコンポーネントを最上位に持つ。既存のアプリケーションにReactを統合する場合は、小さなコンポーネント、例えばButtonなど、ボトムアップから開始し、徐々にView階層の最上位にいたる。
コンポーネントは、一つのroot要素を返さなければならない。上記で、Welcome要素を div に含めたのはこのため
Propsは読み取り専用
- Reactはかなりフレキシブルだが、1つ厳格なルールがある。function だろうと class だろうとコンポーネントはpropsを編集できない。
状態とライフサイクル
- 次のClockコンポーネントを再利用可能にカプセル化する
function Clock(props) { return ( <div> <h2>{props.date.toLocaleTimeString()}</h2> </div> ); } function tick() { ReactDOM.render( <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(tick, 1000);
- 実装のために、Clockコンポーネントにstateを追加する。
- 状態は、propsと同様だが、private かつ、コンポーネントから完全に制御される
- クラス宣言したコンポーネントにはいくつかの追加的な機能があるが、local state はこれにあたる
関数をクラスに変更する
- React.Component を継承して同名のES6クラスを作成する
- render()メソッドを作成し、処理を移動、functionを削除
- propsをthis.propsに変更
class Clock extends React.Component { render() { return ( <div> <h2>{this.props.date.toLocaleTimeString()}</h2> </div> ); } }
クラスにローカル状態を追加する
- date を props から state へ移動する
- render()メソッドの中の、this.props.date を this.state.date に変更
- クラスにコンストラクタを追加し、this.stateの初期状態を記述する
- <Clock date={new Date()} /> から dateを削除
class Clock extends React.Component { constructor(props) { super(props); this.state = {date : new Date()}; } render() { return ( <div> <h2>{this.state.date.toLocaleTimeString()}</h2> </div> ); } }
クラスにライフサイクルメソッドを追加する
- 多くのコンポーネントを使用するアプリケーションでは、破棄されたコンポーネントのリソースの解放が重要
- ClockがDOMに最初にレンダリングされるときには、必ずタイマーをセットアップしたい。これをReactでは、"mounting" という
- また、ClockがDOMから取り除かれるときには、タイマーをクリアしたい。これをReactでは、"unmounting"という
- コンポーネントが、mount/unmount されるときに実行される特別なメソッドをクラスに宣言できる
- componentDidMount()、componentWillUnmount() これらのメソッドはライフサイクルフックと呼ばれる
- どのようにtimer IDを thisに保存するか
- this.propsがReactによりセットアップされる間、this.stateは特別な意味を持つ
- 表示に使用しないならば、クラスに自由にフィールドを追加できる
- render()の中では、それらは利用できないし、ステートも持たない
- 最後に、毎秒実行されるtick()メソッドを実装する。
- this.setState()でローカルコンポーネントの状態を更新する
class Clock extends React.Component { constructor(props) { super(props); this.state = {date : new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(),1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h2>{this.state.date.toLocaleTimeString()}</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
状態を正しく使う
- stateを直接操作しない
// まちがい this.state.comment = 'Hello';
- 上記の代わりに、setState() を利用する
// 正しい this.setSatate({comment:'Hello'});
this.stateを割り当てることができるのは、コンストラクタのみ
- stateは、非同期に更新される
- Reactは、複数のsetState() をパフォーマンスのためにまとめて一度に処理する。
- このため、this.propsとthis.stateは非同期に更新される
- これらの値を信頼して、次の状態への計算を行うべきではない
// まちがい this.setState({ counter: this.state.counter + this.props.increament, });
- 修正するには、オブジェクトではなく関数を受け取る、setState()を使用する
- 関数は、ひとつ前の状態(state)を最初の引数として、更新時のpropsを2つ目の引数として取る
// 正しい this.setState((prevState, props) => ({ counter: prevState.counter + prpos.increament }));
- 通常の関数でもよい
// 正しい this.setState(function(prevState, props) { return { counter: prevState.counter + props.increament }; });
- 状態の更新はマージされる
- setState()を呼び出すと、Reactは、現在のstateに提供したオブジェクトをマージする
- 例えば、いくつかの独立した変数を含む場合
constructor(props) { super(props); this.state = { posts: [] comments: [] }; }
- これらを別のsetState() で更新
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ commentss: response.comments }); }); }
- マージはシャローなので、setState({comment}) は、this.state.posts を損なわずに、this.state.comments を置き換える
データは下へ流れる
- 親だけでなく子コンポーネントも確実にステートフルかステートレスか知ることはできない。
- 状態はしばしばローカルから呼び出されるかカプセル化されていて、他からアクセスできない。
- コンポーネントは、自身の状態をpropsを通して子コンポーネントに渡す
<h2>It is {this.state.date.toLocalTimeString()}.</h2>
- これは、ユーザー定義コンポーネントでも動作する
<FormattedDate date={this.state.date} />
- FormattedDateコンポーネントは、propsにdateを受け取り、Clockの状態が何かは知らない。
function FormatterDate(props) { return <h2>It is {props.date.toLocaleTimeString()}.</h2>; }
- これは一般的に、トップダウン、もしくは、ユニディレクショナル データフローという
- 状態は常に特定のコンポーネントに属する。
- データやUIは状態に由来し、ツリーのより下のコンポーネントにのみ影響を与えることができる
イベントの処理
- React 要素のイベント処理は、DOMでの処理ととても似ているが、いくつかシンタックスの差異がある。
- Reactイベントは、ロウアーケースではなく、キャメルケース
- JSXでは、stringではなく、関数をイベントハンドラーとして渡す。
<button onclick="test()" />
<button onClick={test} />
- もう一つの違いは、Reactでは、falseを返しデフォルトのふるまいを防止することができない。
- 明示的に、preventDefault を呼び出す必要がある。
<a href="#" onclick="console.log('hoge'); return false" >Click</a>
function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('hoge'); } return ( <a href="#" onClick={handleClick} >Click</a> ) }
- e は、本物ではないイベント。Reactでは、W3Cに合わせるために定義している。
- なので、ブラウザの互換性を心配する必要はない。リファレンスを参照
- Reactでは、addEventListenerを通常呼び出す必要はない。要素が最初にレンダリングされるときに提供される
- ES6のクラスを使用するときの一般的なパターン、例えば、Toggle ボタン
class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(prevStae => ({ isToggleOn: !prevStae.isToggleOn })); } render() { return ( <button onClick={this.handleClick} > {this.state.isToggleOn?"ON":"OFF"} </button> ); } } ReactDOM.render( <Toggle />, document.getElementById('root') );
- この文法の問題点は、LoggingButtonがレンダリングされるたびに、異なったコールバックが生成されること
- 多くのケースで、これは問題ないが、このコールバックが下位のコンポーネントにpropとして渡される場合、コンポーネントは余分にレンダリングすることになる。
- コンストラクタでバインドするか、プロパティイニシャライザでバインドすることを推奨する。
条件付きのレンダリング
- Reactでは、必要なら、振る舞いがカプセルかされた他と異なったコンポーネントを作成できる。
- Reactの条件付きレンダリングは、JavaScriptの条件が動作するのと同じ方法で動作する。
- JavaScriptのifのような演算子もしくは、条件演算子は、現在の状態を表した要素を作成するための、ReactがUIをそれに一致させることを許す。
- 以下の2つのコンポーネントについて考える
- ユーザーがログインしているか否かによって使い分けたい。
- isLoggedIn により、異なるレンダリングがなされる。
function UserGreeting(props){ return <h1>Welcome to back!</h1> } function GuestGreeting(props) { return <h1>Please Sign up!</h1> } function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <UserGreeting /> } else { return <GuestGreeting /> } } ReactDOM.render( <Greeting isLoggedIn={true} />, document.getElementById('root') );
要素変数
- 要素を保持するのに変数を利用できる。
- これは、出力結果が変わらない間に、条件によりコンポーネントの一部をレンダリングするのを助ける。
function LoginButton(props) { return ( <button onClick={props.onClick} > Login </button> ); } function LogoutButton(props) { return ( <button onClick={props.onClick} > Logout </button> ); } 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 = null; if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} /> } else { button = <LoginButton onClick={this.handleLoginClick} /> } return (<div>{button}</div>); } }
ReactDOM.render( <LoginControl />, document.getElementById('root') );
インラインif と 論理 && 演算子
- true && 演算子の場合評価され、false && の場合、falseとなりReactは無視する
function MailBox(props) { const unreadMessages = props.unreadMessages; return ( <div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div> ); } const messages = ['message1','message2']; ReactDOM.render( <MailBox unreadMessages={messages} />, document.getElementById('root') );
インライン if-else とコンディション演算子
- 条件 ? true : false
function UserGreeting(props){ return <h1>Welcome to back!</h1> } function GuestGreeting(props) { return <h1>Please Sign up!</h1> } function Greeting(props) { const isLoggedIn = props.isLoggedIn; return ( <div> {isLoggedIn ? ( <UserGreeting /> ) : ( <GuestGreeting /> ) } </div> ); } ReactDOM.render( <Greeting isLoggedIn={false} />, document.getElementById('root') );
コンポーネントのレンダリングさせない
- 他のコンポーネントからレンダリングされたとしてもコンポーネントを隠したい場合、render出力の代わりにnullを返す
function WarningBanner(props){ if(!props.warn) { return null; } return ( <div>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}> {this.state.showWarning ? 'Hide' : 'Show'} </button> </div> ); } } ReactDOM.render( <Page />, document.getElementById('root') );
リストとキー
- JavaScriptでリストの変換には、map()を利用する
const numbers = [1,2,3,4,5]; const doubled = numbers.map((number)=> number * 2); console.log(doubled) > [2, 4, 6, 8, 10]
- React では同様に配列をリストに変換する
複数のコンポーネントをレンダリングする
const fruits = ['apple','orange','grape','banana']; const listItems = fruits.map((fruit) => <li>{fruit}</li>); ReactDOM.render( <ol>{listItems}</ol>, document.getElementById('root') );
基本的なリストコンポーネント
- 通常リストはコンポーネントに含まれる
- Keys
- keyを指定しないと警告がでる
- keyは、Reactが、どのアイテムが登録、変更、削除されたのか特定するのに役立つ
- 文字列で兄弟間でユニークな値を設定するのが望ましい
function FruitList(props) { const fruits = props.fruits; const listItems = fruits.map((fruit) => <li key={fruit}>{fruit}</li>); return ( <ol>{listItems}</ol> ); } const fruits = ['apple','orange','grape','banana']; ReactDOM.render( <FruitList fruits={fruits}/>, document.getElementById('root') );
コンポーネントをKeyと取り出す
- Keyが意味を持つのは、配列を囲むコンテキストにおいて
- 兄弟間で同じKeyを持つとエラーとなる
function ListItem(props) { // この<li> にKeyを設定するのは、ここがルートになるため間違い return <li>{props.value}</li> } function FruitList(props) { const fruits = props.fruits; // <ListItem>の配列となるため、ここでKeyを指定するのが正しい const listItems = fruits.map((fruit) => <ListItem key={fruit} value={fruit} />); return ( <ol>{listItems}</ol> ); } const fruits = ['apple','orange','grape','banana']; ReactDOM.render( <FruitList fruits={fruits}/>, document.getElementById('root') );
JSXでmap()を埋め込む
- 中括弧のインラインにmap()を置くことができる
function ListItem(props) { return <li>{props.value}</li> } function FruitList(props) { const fruits = props.fruits; return ( <ol>{fruits.map((fruit) => <ListItem key={fruit} value={fruit} />)}</ol> );
} const fruits = ['apple','orange','grape','banana']; ReactDOM.render( <FruitList fruits={fruits}/>, document.getElementById('root') );
Form
コントロールされたコンポーネント
- HTMLでFormは、自身の状態を維持しユーザー入力に基づいて状態を更新する。
- Reactでは、変化可能な状態はコンポーネントのstateプロパティに保持し、setState()からのみ更新される。
- "single source of truth"としてReact stateを作成することにより2つを結びつけることができる。
- ReactコンポーネントはFormまたコントロールでユーザー入力により、何が起きたかを描画する。
- input Form 要素の値は、Reactによりこの方法で制御される。
- これは、「コントロールされたコンポーネント」と呼ばれる。
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value : ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value : event.target.value }); } handleSubmit(event) { alert('A name was submitted ' + this.state.value); event.preventDefault(); } render() { return( <form onSubmit={this.handleSubmit}> <label>Name: <input type="text" value={this.state.value} onChange={this.handleChange} /></label> <input type="submit" value="Submit"/> </form> ); } } ReactDOM.render( <NameForm />, document.getElementById('root') );
- valueが、Form要素に設定されると、表示されるvalueは、常にthis.state.valueとなる
- すべてのキーストロークで、handleChangeが起動し、Reactのstateを更新する
- 表示されるvalueも更新される
handleChange(event) { this.setState({value : event.target.value.toUpperCase() }); }
textarea/select タグ
class SelectParts extends React.Component { constructor(props){ super(props); this.state = {value: 'coconuts'}; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } render(){ return( <label>Select: <select value={this.state.value} onChange={this.handleChange}> <option value="grapefruit">Grape Fruit</option> <option value="lime">Lime</option> <option value="coconuts">Coconuts</option> <option value="mango">Mango</option> </select> </label> ); } } class TextareaParts extends React.Component { constructor(props){ super(props); this.state = { value : 'なにか書いて' }; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value : event.target.value}); } render(){ return ( <label>Textarea:<textarea value={this.state.value} onChange={this.handleChange} /></label> ); } } ReactDOM.render( <div> <SelectParts /> <TextareaParts /> </div>, document.getElementById('root') );
複数入力項目の処理
- 複数のコントロールされた入力要素を処理したい場合、name属性を各要素に追加する
- ハンドラー関数で、event.target.name の値に基づいて、どの要素か判定
- ES6 計算されたプロパティ名構文をkey状態を更新するのに使用
class Reservation extends React.Component { constructor(props) { super(props); this.state = { isGoing: true, numberOfGuests: 2 }; this.handleInputChange = this.handleInputChange.bind(this); } handleInputChange(event) { const target = event.target; const value = (target.type == 'checkbox') ? target.checked : target.value; const name = target.name; this.setState({[name]: value}); } render(){ return ( <form> <label>Is going:<input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange}/></label> <br /> <label>Number of guests:<input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange}/></label> </form> ); } } ReactDOM.render( <div> <Reservation /> </div>, document.getElementById('root') );
コントロールされたコンポーネント以外の方法
- コントロールされたコンポーネントを利用するのが、冗長な場合がある。
- 入力フォームを実装するための、コントロールされないコンポーネントを参照。
状態を持ち上げる
- いくつかのコンポーネントは同じ変更をデータに反映させることを必要とする。
- 共有状態をそれらの、最も近い共通の祖先に持ち上げることを推奨する。
- 水が沸騰しているかどうかの計算を行う。
function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p> } class Calculator extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temprature: ''}; } handleChange(e){ this.setState({temprature: e.target.value}); } render(){ const temprature = this.state.temprature; return ( <fieldset> <legend>Enter temprature in Celsius:</legend> <input value={temprature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temprature)} /> </fieldset> ); } } ReactDOM.render( <div> <Calculator /> </div>, document.getElementById('root') );
2つ目の入力を追加する
- 華氏の入力を追加し同期させる
- Reactでstateを共有するには、共通の祖先に移動させることで実現でき、「状態の持ち上げ」という
- TempratureInputのローカルstateをCalcuratorへ移動させる
- Calucratorの共有stateは、「真の情報源」となる
- TempratureInputにとって、tempuratureがローカルの場合、propsは読み取り専用となるので、this.setState()とするしかないが、tempratureが、親のpropsなので、管理する必要はなくなる。
const scaleName = { c: '摂氏', f: '華氏' }; function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p> } function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5/9; } function toFahrenheit(celsius) { return (celsius * 9/5) + 32; } function tryConvert(temprature, convert) { const input = parseFloat(temprature); if (Number.isNaN(input)) { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return rounded.toString(); } class TempratureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temprature: ''}; } handleChange(e){ // before:this.setState({temprature: e.target.value}); this.props.onTempratureChange(e.target.value); } render(){ // before:const temprature = this.state.temprature; const temprature = this.props.temprature; const scale = this.props.scale; return ( <fieldset> <legend>Enter temprature in {scaleName[scale]}:</legend> <input value={temprature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temprature)} /> </fieldset> ); } } class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiumChange = this.handleCelsiumChange.bind(this); this.handlFahrenheitChange = this.handlFahrenheitChange.bind(this); this.state = {scale:'c', temprature:''}; } handleCelsiumChange(temprature) { this.setState({scale:'c', temprature}); } handlFahrenheitChange(temprature) { this.setState({scale:'f', temprature}); } render() { const scale = this.state.scale; const temprature = this.state.temprature; const celsius = scale == 'f' ? tryConvert(temprature, toCelsius) : temprature; const fahrenheit = scale == 'c' ? tryConvert(temprature, toFahrenheit) : temprature; return ( <div> <TempratureInput scale="c" temprature={celsius} onTempratureChange={this.handleCelsiumChange} /> <TempratureInput scale="f" temprature={fahrenheit} onTempratureChange={this.handlFahrenheitChange}/> <BoilingVerdict celsius={parseFloat(celsius)} /> </div> ); } } ReactDOM.render( <div> <Calculator /> </div>, document.getElementById('root') );
コンポジションと継承
- Reactは強力なコンポジションモデルをもつ。コンポーネントの再利用には継承よりコンポジションを推奨する
封じ込める
- 一部のコンポーネントは自身の子供を事前に知ることができない
- これは、特にサイドバーやダイアログなどボックスを表現するコンポーネント共通
- このようなコンポーネントには特別な子供propを使用し直接その要素に出力する
- <FancyBorder>の内側に何があっても、JSXタグはFancyBorderコンポーネントをprops.children として経由する
<style type="text/css"> div.FancyBorder { border : 2px dashed; } div.FancyBorder-blue { border-color: blue; } </style>
function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); } function WelcomDialog() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title">Welcome</h1> </FancyBorder> ); } ReactDOM.render( <div> <WelcomDialog /> </div>, document.getElementById('root') );
特殊化
- コンポーネントを他のコンポーネントの特殊なものとして考えることがある、例えば、WelcomDialogはDialogの特殊な例として。
- Reactでは、コンポジションを使って実現する
- クラスを用いても同様に記述できる
function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title">{props.title}</h1> </FancyBorder> ); } function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); } function WelcomDialog() { return ( <Dialog title="Welcome" /> ); }
React Router
Redux
Tips
example.zip(729)
YAGI Hiroto (piroto@a-net.email.ne.jp)
twitter http://twitter.com/pppiroto
Copyright© 矢木 浩人 All Rights Reserved.