React
ナビゲーションに移動
検索に移動
目次
React
スケルトン作成
- 開発の全体像を把握しつつ開発環境を整える
- React の単純なサンプルに React Router を適用する
- React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する
- React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む
- React環境->React Router->Redux-Saga->SuperAgent に Bootstrapを適用する
導入
Create React App
- Reactアプリ開発のためのコマンドラインツール。トランスパイラ、バンドラ、開発サーバーなどを含むツールチェーン
- Facebook本家が提供。他にもNext.js、Vite、Gatsby、Parcelなどのツールチェーンがある。
- より高度な環境としてNext.jsも存在
Next.js
- Next.js
- Reactは、あくまでUI部分のみ
- 本格的なアプリ開発には周辺領域を担うためのフレームワークが必要
- Reactベースのフレームワークとしてデファクトスタンダードと言える存在
- 主なライブラリ
- ファイルシステムベースの設定レスルーター
- サーバーコンポーネント
- データ取得用fetchメソッド
- リソース組み込みの自動最適化
- CSSフレームワーク、Tailwind CSSへの標準対応
Node
Node.js のインストール
npx
- ローカルにインストールされたツールを実行するためのパッケージランナー
Next.jsアプリの作成
TypeScriptの導入
ファイル拡張子
JSX
- https://3chome.net/javascript-typescript/#:~:text=.tsx%EF%BC%88TSX%E3%83%95%E3%82%A1
- TypeScriptの拡張子
- .ts : コードにJSX含まない
- .tsx : コードにJSX含む(含まない場合tsxとしてもよいが望ましくない)
Module
型アサーション
導入(旧)
- 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
- npx create-react-app my-app
- cd my-app
- npm start
- Reactはバックエンドロジックやデータベースを持たないが、使いたいものを使えばよい。
- Babelやwebpackのようなビルドツールも設定なしに利用できる。
- 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);
<blockquote>通常のReact アプリケーションでは、ReactDOM.rendar()は 一度しか呼びださない。</blockquote>
Reactは必要なもののみ更新する
- React DOMは、要素および子要素を前の状態と比較し、DOMの更新が必要な個所にのみ適用する。
- ブラウザツールで、上記のソースコードを確認する
<blockquote>毎tick()の呼び出しで、すべてのUIツリーを生成するよう記述してるが、変更が発生したテキストノードのみReact DOMにより更新されている。</blockquote>
コンポーネントと 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"としてオブジェクトが渡される。
<blockquote>コンポーネント名はいつも大文字から始める。DOM タグは、<div /> だが、<Welcome /> はコンポーネントを表現する。Welcomeがスコープに存在すること。</blockquote>
コンポーネントの構成
- コンポーネントはその出力において、他のコンポーネントに影響を与えることができる
- 同じコンポーネントをどんなレベルの詳細にも抽象的に利用できる、ボタン、フォーム、ダイアログ、スクリーン
- 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階層の最上位にいたる。
<blockquote>コンポーネントは、一つのroot要素を返さなければならない。上記で、Welcome要素を div に含めたのはこのため</blockquote>
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'});
<blockquote>this.stateを割り当てることができるのは、コンストラクタのみ</blockquote>
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ではなく、関数をイベントハンドラーとして渡す。
HTML
- <button onclick="test()" />
React
- <button onClick={test} />
- もう一つの違いは、Reactでは、falseを返しデフォルトのふるまいを防止することができない。
- 明示的に、preventDefault を呼び出す必要がある。
HTMLでデフォルトの新しいページを開くリンクのふるまいを防止する
- <a href="#" onclick="console.log('hoge'); return false" >Click</a>
React
- 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では、少し他のDOMとは動作が異なる。
- なぜなら Fromは通常内部状態をもつから。
コントロールされたコンポーネント
- 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
ビルド
© 2006 矢木浩人