目次
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(853)

YAGI Hiroto (piroto@a-net.email.ne.jp)
twitter http://twitter.com/pppiroto
Copyright© 矢木 浩人 All Rights Reserved.