「React」の版間の差分
ナビゲーションに移動
検索に移動
(ページの作成:「==React== [Node.js] {{amazon|4873117887}} ===スケルトン作成=== #[http://typea.info/blg/glob/2017/08/react-1.html React 開発の全体像を把握しつつ…」) |
|||
1行目: | 1行目: | ||
==React== | ==React== | ||
− | [Node.js] | + | [[Node.js]] |
{{amazon|4873117887}} | {{amazon|4873117887}} | ||
9行目: | 9行目: | ||
#[http://typea.info/blg/glob/2017/08/react-react-router-redux-saga.html React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する] | #[http://typea.info/blg/glob/2017/08/react-react-router-redux-saga.html React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する] | ||
#[http://typea.info/blg/glob/2017/08/react-react-router-redux-saga-ajax.html React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む] | #[http://typea.info/blg/glob/2017/08/react-react-router-redux-saga-ajax.html React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む] | ||
− | #[http://typea.info/blg/glob/2017/08/react-react-router-redux-saga-superagent-bootstrap.html React環境- | + | #[http://typea.info/blg/glob/2017/08/react-react-router-redux-saga-superagent-bootstrap.html React環境->React Router->Redux-Saga->SuperAgent に Bootstrapを適用する] |
==導入== | ==導入== | ||
18行目: | 18行目: | ||
*Reactはバックエンドロジックやデータベースを持たないが、使いたいものを使えばよい。 | *Reactはバックエンドロジックやデータベースを持たないが、使いたいものを使えばよい。 | ||
*Babelやwebpackのようなビルドツールも設定なしに利用できる。 | *Babelやwebpackのようなビルドツールも設定なしに利用できる。 | ||
− | PS C:\workspaces\vscode\reactlesson | + | PS C:\workspaces\vscode\reactlesson> create-react-app react-lesson |
Creating a new React app in C:\workspaces\vscode\reactlesson\react-lesson. | Creating a new React app in C:\workspaces\vscode\reactlesson\react-lesson. | ||
25行目: | 25行目: | ||
: | : | ||
*アプリケーションが作成されたら実行 | *アプリケーションが作成されたら実行 | ||
− | PS C:\workspaces\vscode\reactlesson | + | PS C:\workspaces\vscode\reactlesson> cd react-lesson |
− | PS C:\workspaces\vscode\reactlesson | + | PS C:\workspaces\vscode\reactlesson> npm start |
*実行された | *実行された | ||
[[File:1086_react01.jpg]] | [[File:1086_react01.jpg]] | ||
34行目: | 34行目: | ||
import './App.css'; | import './App.css'; | ||
− | const element = ( | + | const element = (<h1>Hello,world</h1>); |
class App extends Component { | class App extends Component { | ||
render() { | render() { | ||
50行目: | 50行目: | ||
==クイックスタート== | ==クイックスタート== | ||
===JSX=== | ===JSX=== | ||
− | const element = | + | const element = <h1>Hello,world!</h1>; |
*文字列でも、HTMLでもなく、JSX | *文字列でも、HTMLでもなく、JSX | ||
*JavaScriptの拡張文法 | *JavaScriptの拡張文法 | ||
57行目: | 57行目: | ||
===JSX表現=== | ===JSX表現=== | ||
*どのようなJavaScriptの表現も、中括弧で囲むことでJSXに埋め込むことができる | *どのようなJavaScriptの表現も、中括弧で囲むことでJSXに埋め込むことができる | ||
− | *const element = ( | + | *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) { | function formatName(user) { | ||
return user.firstName + ' ' + user.lastName; | return user.firstName + ' ' + user.lastName; | ||
} | } | ||
const user = {firstName:'Hiroto', lastName:'Yagi'}; | const user = {firstName:'Hiroto', lastName:'Yagi'}; | ||
− | const element = ( | + | const element = (<h1>Hello, {formatName(user)}!</h1>); |
ReactDOM.render( | ReactDOM.render( | ||
element, | element, | ||
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
− | + | </script> | |
− | + | </body> | |
− | + | </html> | |
*コンパイルされたJSXは通常のJavaScriptオブジェクト | *コンパイルされたJSXは通常のJavaScriptオブジェクト | ||
89行目: | 89行目: | ||
function greeting(user) { | function greeting(user) { | ||
var now = new Date(); | var now = new Date(); | ||
− | if (5 | + | if (5 <= now.getHours() && now.getHours() <= 12) { |
− | return | + | return <h1> Good morning {user.firstName + ' ' + user.lastName}.</h1>; |
} else { | } else { | ||
− | return | + | return <h1> Hello {user.firstName + ' ' + user.lastName}.</h1>; |
} | } | ||
} | } | ||
====属性に利用==== | ====属性に利用==== | ||
*中括弧を利用して、二重引用符なし(使用すると文字列リテラルとして扱われる)で直接利用できる。 | *中括弧を利用して、二重引用符なし(使用すると文字列リテラルとして扱われる)で直接利用できる。 | ||
− | + | <div id="root"></div> | |
− | + | <script type="text/babel"> | |
function greeting(user) { | function greeting(user) { | ||
var now = new Date(); | var now = new Date(); | ||
− | if (5 | + | if (5 <= now.getHours() && now.getHours() <= 12) { |
− | return | + | return <h1> Good morning <a href={user.webpageUrl}>{user.firstName + ' ' + user.lastName}</a>.</h1>; |
} else { | } else { | ||
− | return | + | return <h1> Hello <a href={user.webpageUrl}>{user.firstName + ' ' + user.lastName}</a>.</h1>; |
} | } | ||
} | } | ||
const user = {firstName:'Hiroto', lastName:'Yagi', webpageUrl:'http://typea.info'}; | const user = {firstName:'Hiroto', lastName:'Yagi', webpageUrl:'http://typea.info'}; | ||
− | const element = ( | + | const element = (<h1>{greeting(user)}</h1>); |
ReactDOM.render( | ReactDOM.render( | ||
element, | element, | ||
115行目: | 115行目: | ||
[[File:1089_react04.jpg]] | [[File:1089_react04.jpg]] | ||
====子要素==== | ====子要素==== | ||
− | *子要素がない場合、XML同様 / | + | *子要素がない場合、XML同様 /> で閉じる |
*子要素を含む | *子要素を含む | ||
*JSXはHTMLよりJavaScriptにより近い。ReactDOMはキャメルケースプロパティ(HTMLでは、class がclassName、HTMLではtabindexがtabIndexなど)を持つ | *JSXはHTMLよりJavaScriptにより近い。ReactDOMはキャメルケースプロパティ(HTMLでは、class がclassName、HTMLではtabindexがtabIndexなど)を持つ | ||
function greeting(user) { | function greeting(user) { | ||
− | return | + | return <div> |
− | + | <h1> Good morning {user.firstName + ' ' + user.lastName}.</h1> | |
− | + | <h2><a href={user.webpageUrl}>webpage</a></h2> | |
− | + | </div> | |
; | ; | ||
} | } | ||
137行目: | 137行目: | ||
*XSS攻撃の予防を助ける | *XSS攻撃の予防を助ける | ||
function greeting(user, title) { | function greeting(user, title) { | ||
− | return | + | return <div> |
− | + | <h1>{title}</h1> | |
− | + | <h2> Good morning {user.firstName + ' ' + user.lastName}.</h2> | |
− | + | <h2><a href={user.webpageUrl}>webpage</a></h2> | |
− | + | </div> | |
; | ; | ||
} | } | ||
− | const title = " | + | const title = "<input type='button'>"; |
const user = {firstName:'Hiroto', lastName:'Yagi', webpageUrl:'http://typea.info'}; | const user = {firstName:'Hiroto', lastName:'Yagi', webpageUrl:'http://typea.info'}; | ||
const element = (greeting(user,title)); | const element = (greeting(user,title)); | ||
155行目: | 155行目: | ||
*Babelは、React.createElement()を呼び出しコンパイルを行う | *Babelは、React.createElement()を呼び出しコンパイルを行う | ||
*以下の2つは同じ意味 | *以下の2つは同じ意味 | ||
− | const element1 = ( | + | const element1 = (<h1 className='greeting'>hello</h1>); |
const element2 = React.createElement('h1',{className:'greeting'},'hello'); | const element2 = React.createElement('h1',{className:'greeting'},'hello'); | ||
*React.createElement()は、バグを防ぐ手助けをするが、本質的には以下のようなオブジェクトを生成する | *React.createElement()は、バグを防ぐ手助けをするが、本質的には以下のようなオブジェクトを生成する | ||
171行目: | 171行目: | ||
*HTMLどこかに記述する、以下のDIV要素をroot DOM ノードと呼ぶ | *HTMLどこかに記述する、以下のDIV要素をroot DOM ノードと呼ぶ | ||
*React DOM が管理するすべてが、この要素の中にある | *React DOM が管理するすべてが、この要素の中にある | ||
− | + | <div id="root"></div> | |
====React要素の更新==== | ====React要素の更新==== | ||
*React要素は不変。一旦生成したら、子要素、属性などは変更できない。 | *React要素は不変。一旦生成したら、子要素、属性などは変更できない。 | ||
*UIを更新するには、新しい要素を作成し、ReactDOM.rendar()に引き渡す。 | *UIを更新するには、新しい要素を作成し、ReactDOM.rendar()に引き渡す。 | ||
function tick() { | function tick() { | ||
− | const element = ( | + | const element = (<div>{(new Date()).toLocaleTimeString()}</div>); |
ReactDOM.render( | ReactDOM.render( | ||
element, | element, | ||
184行目: | 184行目: | ||
setInterval(tick,1000); | setInterval(tick,1000); | ||
[[File:1092_react07.jpg]] | [[File:1092_react07.jpg]] | ||
− | + | <blockquote>通常のReact アプリケーションでは、ReactDOM.rendar()は 一度しか呼びださない。</blockquote> | |
====Reactは必要なもののみ更新する==== | ====Reactは必要なもののみ更新する==== | ||
*React DOMは、要素および子要素を前の状態と比較し、DOMの更新が必要な個所にのみ適用する。 | *React DOMは、要素および子要素を前の状態と比較し、DOMの更新が必要な個所にのみ適用する。 | ||
*ブラウザツールで、上記のソースコードを確認する | *ブラウザツールで、上記のソースコードを確認する | ||
[[File:1093_react08.jpg]] | [[File:1093_react08.jpg]] | ||
− | + | <blockquote>毎tick()の呼び出しで、すべてのUIツリーを生成するよう記述してるが、変更が発生したテキストノードのみReact DOMにより更新されている。</blockquote> | |
===コンポーネントと Props=== | ===コンポーネントと Props=== | ||
*コンポーネントはUIを独立し再利用可能な部分に分割する。 | *コンポーネントはUIを独立し再利用可能な部分に分割する。 | ||
199行目: | 199行目: | ||
*このようなコンポーネントを"functional"と呼ぶ。 | *このようなコンポーネントを"functional"と呼ぶ。 | ||
function Welcome(props) { | function Welcome(props) { | ||
− | return | + | return <h1>Hello,{props.name}</h1>; |
} | } | ||
*ES6のクラスをコンポーネントの定義として利用できる | *ES6のクラスをコンポーネントの定義として利用できる | ||
class Welcome extends React.Component { | class Welcome extends React.Component { | ||
render() { | render() { | ||
− | return | + | return <h1>Hello,{props.name}</h1>; |
} | } | ||
} | } | ||
211行目: | 211行目: | ||
*Elementは、ユーザー定義コンポーネントも表すことができる | *Elementは、ユーザー定義コンポーネントも表すことができる | ||
function Welcome(props) { | function Welcome(props) { | ||
− | return | + | return <h1>Hello,{props.name}</h1>; |
} | } | ||
− | const element = | + | const element = <Welcome name='Hiroto' /> |
ReactDOM.render( | ReactDOM.render( | ||
element, | element, | ||
220行目: | 220行目: | ||
*Reactがユーザー定義コンポーネントを表示するときに、JSXの属性からコンポーネントへ"props"としてオブジェクトが渡される。 | *Reactがユーザー定義コンポーネントを表示するときに、JSXの属性からコンポーネントへ"props"としてオブジェクトが渡される。 | ||
− | + | <blockquote>コンポーネント名はいつも大文字から始める。DOM タグは、<div /> だが、<Welcome /> はコンポーネントを表現する。Welcomeがスコープに存在すること。</blockquote> | |
====コンポーネントの構成==== | ====コンポーネントの構成==== | ||
*コンポーネントはその出力において、他のコンポーネントに影響を与えることができる | *コンポーネントはその出力において、他のコンポーネントに影響を与えることができる | ||
226行目: | 226行目: | ||
*Reactでこれらは、一般にコンポーネントで表現される | *Reactでこれらは、一般にコンポーネントで表現される | ||
function Welcome(props) { | function Welcome(props) { | ||
− | return | + | return <h1>Hello,{props.name}</h1>; |
} | } | ||
function App() { | function App() { | ||
− | return ( | + | return ( <div> |
− | + | <Welcome name="Yagi"/> | |
− | + | <Welcome name="Kaela"/> | |
− | + | <Welcome name="Hiroto"/> | |
− | + | </div> ); | |
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <App />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
*一般的に新しいReactアプリケーションは、一つのAppコンポーネントを最上位に持つ。既存のアプリケーションにReactを統合する場合は、小さなコンポーネント、例えばButtonなど、ボトムアップから開始し、徐々にView階層の最上位にいたる。 | *一般的に新しいReactアプリケーションは、一つのAppコンポーネントを最上位に持つ。既存のアプリケーションにReactを統合する場合は、小さなコンポーネント、例えばButtonなど、ボトムアップから開始し、徐々にView階層の最上位にいたる。 | ||
− | + | <blockquote>コンポーネントは、一つのroot要素を返さなければならない。上記で、Welcome要素を div に含めたのはこのため</blockquote> | |
====Propsは読み取り専用==== | ====Propsは読み取り専用==== | ||
**Reactはかなりフレキシブルだが、1つ厳格なルールがある。function だろうと class だろうとコンポーネントはpropsを編集できない。 | **Reactはかなりフレキシブルだが、1つ厳格なルールがある。function だろうと class だろうとコンポーネントはpropsを編集できない。 | ||
248行目: | 248行目: | ||
function Clock(props) { | function Clock(props) { | ||
return ( | return ( | ||
− | + | <div> | |
− | + | <h2>{props.date.toLocaleTimeString()}</h2> | |
− | + | </div> | |
); | ); | ||
} | } | ||
function tick() { | function tick() { | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <Clock date={new Date()} />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
273行目: | 273行目: | ||
render() { | render() { | ||
return ( | return ( | ||
− | + | <div> | |
− | + | <h2>{this.props.date.toLocaleTimeString()}</h2> | |
− | + | </div> | |
); | ); | ||
} | } | ||
283行目: | 283行目: | ||
#render()メソッドの中の、this.props.date を this.state.date に変更 | #render()メソッドの中の、this.props.date を this.state.date に変更 | ||
#クラスにコンストラクタを追加し、this.stateの初期状態を記述する | #クラスにコンストラクタを追加し、this.stateの初期状態を記述する | ||
− | # | + | #<Clock date={new Date()} /> から dateを削除 |
class Clock extends React.Component { | class Clock extends React.Component { | ||
constructor(props) { | constructor(props) { | ||
291行目: | 291行目: | ||
render() { | render() { | ||
return ( | return ( | ||
− | + | <div> | |
− | + | <h2>{this.state.date.toLocaleTimeString()}</h2> | |
− | + | </div> | |
); | ); | ||
} | } | ||
317行目: | 317行目: | ||
componentDidMount() { | componentDidMount() { | ||
this.timerID = setInterval( | this.timerID = setInterval( | ||
− | () = | + | () => this.tick(),1000 |
); | ); | ||
} | } | ||
330行目: | 330行目: | ||
render() { | render() { | ||
return ( | return ( | ||
− | + | <div> | |
− | + | <h2>{this.state.date.toLocaleTimeString()}</h2> | |
− | + | </div> | |
); | ); | ||
} | } | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <Clock />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
347行目: | 347行目: | ||
// 正しい | // 正しい | ||
this.setSatate({comment:'Hello'}); | this.setSatate({comment:'Hello'}); | ||
− | + | <blockquote>this.stateを割り当てることができるのは、コンストラクタのみ</blockquote> | |
=====stateは、非同期に更新される===== | =====stateは、非同期に更新される===== | ||
*Reactは、複数のsetState() をパフォーマンスのためにまとめて一度に処理する。 | *Reactは、複数のsetState() をパフォーマンスのためにまとめて一度に処理する。 | ||
359行目: | 359行目: | ||
*関数は、ひとつ前の状態(state)を最初の引数として、更新時のpropsを2つ目の引数として取る | *関数は、ひとつ前の状態(state)を最初の引数として、更新時のpropsを2つ目の引数として取る | ||
// 正しい | // 正しい | ||
− | this.setState((prevState, props) = | + | this.setState((prevState, props) => ({ |
counter: prevState.counter + prpos.increament | counter: prevState.counter + prpos.increament | ||
})); | })); | ||
379行目: | 379行目: | ||
*これらを別のsetState() で更新 | *これらを別のsetState() で更新 | ||
componentDidMount() { | componentDidMount() { | ||
− | fetchPosts().then(response = | + | fetchPosts().then(response => { this.setState({ posts: response.posts }); }); |
− | fetchComments().then(response = | + | fetchComments().then(response => { this.setState({ commentss: response.comments }); }); |
} | } | ||
*マージはシャローなので、setState({comment}) は、this.state.posts を損なわずに、this.state.comments を置き換える | *マージはシャローなので、setState({comment}) は、this.state.posts を損なわずに、this.state.comments を置き換える | ||
387行目: | 387行目: | ||
*状態はしばしばローカルから呼び出されるかカプセル化されていて、他からアクセスできない。 | *状態はしばしばローカルから呼び出されるかカプセル化されていて、他からアクセスできない。 | ||
*コンポーネントは、自身の状態をpropsを通して子コンポーネントに渡す | *コンポーネントは、自身の状態をpropsを通して子コンポーネントに渡す | ||
− | + | <h2>It is {this.state.date.toLocalTimeString()}.</h2> | |
*これは、ユーザー定義コンポーネントでも動作する | *これは、ユーザー定義コンポーネントでも動作する | ||
− | + | <FormattedDate date={this.state.date} /> | |
*FormattedDateコンポーネントは、propsにdateを受け取り、Clockの状態が何かは知らない。 | *FormattedDateコンポーネントは、propsにdateを受け取り、Clockの状態が何かは知らない。 | ||
function FormatterDate(props) { | function FormatterDate(props) { | ||
− | return | + | return <h2>It is {props.date.toLocaleTimeString()}.</h2>; |
} | } | ||
*これは一般的に、トップダウン、もしくは、ユニディレクショナル データフローという | *これは一般的に、トップダウン、もしくは、ユニディレクショナル データフローという | ||
403行目: | 403行目: | ||
=====HTML===== | =====HTML===== | ||
− | + | <button onclick="test()" /> | |
=====React===== | =====React===== | ||
− | + | <button onClick={test} /> | |
*もう一つの違いは、Reactでは、falseを返しデフォルトのふるまいを防止することができない。 | *もう一つの違いは、Reactでは、falseを返しデフォルトのふるまいを防止することができない。 | ||
411行目: | 411行目: | ||
=====HTMLでデフォルトの新しいページを開くリンクのふるまいを防止する===== | =====HTMLでデフォルトの新しいページを開くリンクのふるまいを防止する===== | ||
− | + | <a href="#" onclick="console.log('hoge'); return false" >Click</a> | |
=====React===== | =====React===== | ||
function ActionLink() { | function ActionLink() { | ||
419行目: | 419行目: | ||
} | } | ||
return ( | return ( | ||
− | + | <a href="#" onClick={handleClick} >Click</a> | |
) | ) | ||
} | } | ||
433行目: | 433行目: | ||
} | } | ||
handleClick() { | handleClick() { | ||
− | this.setState(prevStae = | + | this.setState(prevStae => ({ |
isToggleOn: !prevStae.isToggleOn | isToggleOn: !prevStae.isToggleOn | ||
})); | })); | ||
439行目: | 439行目: | ||
render() { | render() { | ||
return ( | return ( | ||
− | + | <button onClick={this.handleClick} > | |
{this.state.isToggleOn?"ON":"OFF"} | {this.state.isToggleOn?"ON":"OFF"} | ||
− | + | </button> | |
); | ); | ||
} | } | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <Toggle />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
460行目: | 460行目: | ||
*isLoggedIn により、異なるレンダリングがなされる。 | *isLoggedIn により、異なるレンダリングがなされる。 | ||
function UserGreeting(props){ | function UserGreeting(props){ | ||
− | return | + | return <h1>Welcome to back!</h1> |
} | } | ||
function GuestGreeting(props) { | function GuestGreeting(props) { | ||
− | return | + | return <h1>Please Sign up!</h1> |
} | } | ||
function Greeting(props) { | function Greeting(props) { | ||
const isLoggedIn = props.isLoggedIn; | const isLoggedIn = props.isLoggedIn; | ||
if (isLoggedIn) { | if (isLoggedIn) { | ||
− | return | + | return <UserGreeting /> |
} else { | } else { | ||
− | return | + | return <GuestGreeting /> |
} | } | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <Greeting isLoggedIn={true} />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
482行目: | 482行目: | ||
function LoginButton(props) { | function LoginButton(props) { | ||
return ( | return ( | ||
− | + | <button onClick={props.onClick} > | |
Login | Login | ||
− | + | </button> | |
); | ); | ||
} | } | ||
function LogoutButton(props) { | function LogoutButton(props) { | ||
return ( | return ( | ||
− | + | <button onClick={props.onClick} > | |
Logout | Logout | ||
− | + | </button> | |
); | ); | ||
} | } | ||
511行目: | 511行目: | ||
let button = null; | let button = null; | ||
if (isLoggedIn) { | if (isLoggedIn) { | ||
− | button = | + | button = <LogoutButton onClick={this.handleLogoutClick} /> |
} else { | } else { | ||
− | button = | + | button = <LoginButton onClick={this.handleLoginClick} /> |
} | } | ||
− | return ( | + | return (<div>{button}</div>); |
} | } | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <LoginControl />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
529行目: | 529行目: | ||
const unreadMessages = props.unreadMessages; | const unreadMessages = props.unreadMessages; | ||
return ( | return ( | ||
− | + | <div> | |
− | + | <h1>Hello!</h1> | |
− | {unreadMessages.length | + | {unreadMessages.length > 0 && |
− | + | <h2> | |
You have {unreadMessages.length} unread messages. | You have {unreadMessages.length} unread messages. | ||
− | + | </h2> | |
} | } | ||
− | + | </div> | |
); | ); | ||
} | } | ||
const messages = ['message1','message2']; | const messages = ['message1','message2']; | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <MailBox unreadMessages={messages} />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
547行目: | 547行目: | ||
*条件 ? true : false | *条件 ? true : false | ||
function UserGreeting(props){ | function UserGreeting(props){ | ||
− | return | + | return <h1>Welcome to back!</h1> |
} | } | ||
function GuestGreeting(props) { | function GuestGreeting(props) { | ||
− | return | + | return <h1>Please Sign up!</h1> |
} | } | ||
function Greeting(props) { | function Greeting(props) { | ||
const isLoggedIn = props.isLoggedIn; | const isLoggedIn = props.isLoggedIn; | ||
return ( | return ( | ||
− | + | <div> | |
{isLoggedIn ? ( | {isLoggedIn ? ( | ||
− | + | <UserGreeting /> | |
) : ( | ) : ( | ||
− | + | <GuestGreeting /> | |
) | ) | ||
} | } | ||
− | + | </div> | |
); | ); | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <Greeting isLoggedIn={false} />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
576行目: | 576行目: | ||
} | } | ||
return ( | return ( | ||
− | + | <div>Warning!</div> | |
); | ); | ||
} | } | ||
586行目: | 586行目: | ||
} | } | ||
handleToggleClick() { | handleToggleClick() { | ||
− | this.setState(prevState = | + | this.setState(prevState =>({showWarning : !prevState.showWarning})); |
} | } | ||
render() { | render() { | ||
return ( | return ( | ||
− | + | <div> | |
− | + | <WarningBanner warn={this.state.showWarning} /> | |
− | + | <button onClick={this.handleToggleClick}> | |
{this.state.showWarning ? 'Hide' : 'Show'} | {this.state.showWarning ? 'Hide' : 'Show'} | ||
− | + | </button> | |
− | + | </div> | |
); | ); | ||
} | } | ||
601行目: | 601行目: | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <Page />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
608行目: | 608行目: | ||
=====JavaScriptでリストの変換には、map()を利用する===== | =====JavaScriptでリストの変換には、map()を利用する===== | ||
const numbers = [1,2,3,4,5]; | const numbers = [1,2,3,4,5]; | ||
− | const doubled = numbers.map((number)= | + | const doubled = numbers.map((number)=> number * 2); |
console.log(doubled) | console.log(doubled) | ||
− | + | > [2, 4, 6, 8, 10] | |
*React では同様に配列をリストに変換する | *React では同様に配列をリストに変換する | ||
====複数のコンポーネントをレンダリングする==== | ====複数のコンポーネントをレンダリングする==== | ||
const fruits = ['apple','orange','grape','banana']; | const fruits = ['apple','orange','grape','banana']; | ||
− | const listItems = fruits.map((fruit) = | + | const listItems = fruits.map((fruit) => <li>{fruit}</li>); |
ReactDOM.render( | ReactDOM.render( | ||
− | + | <ol>{listItems}</ol>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
629行目: | 629行目: | ||
function FruitList(props) { | function FruitList(props) { | ||
const fruits = props.fruits; | const fruits = props.fruits; | ||
− | const listItems = fruits.map((fruit) = | + | const listItems = fruits.map((fruit) => <li key={fruit}>{fruit}</li>); |
return ( | return ( | ||
− | + | <ol>{listItems}</ol> | |
); | ); | ||
} | } | ||
const fruits = ['apple','orange','grape','banana']; | const fruits = ['apple','orange','grape','banana']; | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <FruitList fruits={fruits}/>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
643行目: | 643行目: | ||
*兄弟間で同じKeyを持つとエラーとなる | *兄弟間で同じKeyを持つとエラーとなる | ||
function ListItem(props) { | function ListItem(props) { | ||
− | // この | + | // この<li> にKeyを設定するのは、ここがルートになるため間違い |
− | return | + | return <li>{props.value}</li> |
} | } | ||
function FruitList(props) { | function FruitList(props) { | ||
const fruits = props.fruits; | const fruits = props.fruits; | ||
− | // | + | // <ListItem>の配列となるため、ここでKeyを指定するのが正しい |
− | const listItems = fruits.map((fruit) = | + | const listItems = fruits.map((fruit) => <ListItem key={fruit} value={fruit} />); |
return ( | return ( | ||
− | + | <ol>{listItems}</ol> | |
); | ); | ||
} | } | ||
const fruits = ['apple','orange','grape','banana']; | const fruits = ['apple','orange','grape','banana']; | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <FruitList fruits={fruits}/>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
662行目: | 662行目: | ||
*中括弧のインラインにmap()を置くことができる | *中括弧のインラインにmap()を置くことができる | ||
function ListItem(props) { | function ListItem(props) { | ||
− | return | + | return <li>{props.value}</li> |
} | } | ||
function FruitList(props) { | function FruitList(props) { | ||
const fruits = props.fruits; | const fruits = props.fruits; | ||
return ( | return ( | ||
− | + | <ol>{fruits.map((fruit) => <ListItem key={fruit} value={fruit} />)}</ol> | |
); | ); | ||
673行目: | 673行目: | ||
const fruits = ['apple','orange','grape','banana']; | const fruits = ['apple','orange','grape','banana']; | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <FruitList fruits={fruits}/>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
703行目: | 703行目: | ||
render() { | render() { | ||
return( | 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( | ReactDOM.render( | ||
− | + | <NameForm />, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
733行目: | 733行目: | ||
render(){ | render(){ | ||
return( | 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> | |
); | ); | ||
} | } | ||
757行目: | 757行目: | ||
render(){ | render(){ | ||
return ( | return ( | ||
− | + | <label>Textarea:<textarea value={this.state.value} onChange={this.handleChange} /></label> | |
); | ); | ||
} | } | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <div> | |
− | + | <SelectParts /> | |
− | + | <TextareaParts /> | |
− | + | </div>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
789行目: | 789行目: | ||
render(){ | render(){ | ||
return ( | 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( | ReactDOM.render( | ||
− | + | <div> | |
− | + | <Reservation /> | |
− | + | </div>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
812行目: | 812行目: | ||
=====水が沸騰しているかどうかの計算を行う。===== | =====水が沸騰しているかどうかの計算を行う。===== | ||
function BoilingVerdict(props) { | function BoilingVerdict(props) { | ||
− | if (props.celsius | + | if (props.celsius >= 100) { |
− | return | + | return <p>The water would boil.</p>; |
} | } | ||
− | return | + | return <p>The water would not boil.</p> |
} | } | ||
class Calculator extends React.Component { | class Calculator extends React.Component { | ||
829行目: | 829行目: | ||
const temprature = this.state.temprature; | const temprature = this.state.temprature; | ||
return ( | return ( | ||
− | + | <fieldset> | |
− | + | <legend>Enter temprature in Celsius:</legend> | |
− | + | <input value={temprature} onChange={this.handleChange} /> | |
− | + | <BoilingVerdict celsius={parseFloat(temprature)} /> | |
− | + | </fieldset> | |
); | ); | ||
} | } | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <div> | |
− | + | <Calculator /> | |
− | + | </div>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
855行目: | 855行目: | ||
}; | }; | ||
function BoilingVerdict(props) { | function BoilingVerdict(props) { | ||
− | if (props.celsius | + | if (props.celsius >= 100) { |
− | return | + | return <p>The water would boil.</p>; |
} | } | ||
− | return | + | return <p>The water would not boil.</p> |
} | } | ||
function toCelsius(fahrenheit) { | function toCelsius(fahrenheit) { | ||
890行目: | 890行目: | ||
const scale = this.props.scale; | const scale = this.props.scale; | ||
return ( | return ( | ||
− | + | <fieldset> | |
− | + | <legend>Enter temprature in {scaleName[scale]}:</legend> | |
− | + | <input value={temprature} onChange={this.handleChange} /> | |
− | + | <BoilingVerdict celsius={parseFloat(temprature)} /> | |
− | + | </fieldset> | |
); | ); | ||
} | } | ||
917行目: | 917行目: | ||
const fahrenheit = scale == 'c' ? tryConvert(temprature, toFahrenheit) : temprature; | const fahrenheit = scale == 'c' ? tryConvert(temprature, toFahrenheit) : temprature; | ||
return ( | 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( | ReactDOM.render( | ||
− | + | <div> | |
− | + | <Calculator /> | |
− | + | </div>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
938行目: | 938行目: | ||
*これは、特にサイドバーやダイアログなどボックスを表現するコンポーネント共通 | *これは、特にサイドバーやダイアログなどボックスを表現するコンポーネント共通 | ||
*このようなコンポーネントには特別な子供propを使用し直接その要素に出力する | *このようなコンポーネントには特別な子供propを使用し直接その要素に出力する | ||
− | * | + | *<FancyBorder>の内側に何があっても、JSXタグはFancyBorderコンポーネントをprops.children として経由する |
− | + | <style type="text/css"> | |
div.FancyBorder { | div.FancyBorder { | ||
border : 2px dashed; | border : 2px dashed; | ||
946行目: | 946行目: | ||
border-color: blue; | border-color: blue; | ||
} | } | ||
− | + | </style> | |
function FancyBorder(props) { | function FancyBorder(props) { | ||
return ( | return ( | ||
− | + | <div className={'FancyBorder FancyBorder-' + props.color}> | |
{props.children} | {props.children} | ||
− | + | </div> | |
); | ); | ||
} | } | ||
function WelcomDialog() { | function WelcomDialog() { | ||
return ( | return ( | ||
− | + | <FancyBorder color="blue"> | |
− | + | <h1 className="Dialog-title">Welcome</h1> | |
− | + | </FancyBorder> | |
); | ); | ||
} | } | ||
ReactDOM.render( | ReactDOM.render( | ||
− | + | <div> | |
− | + | <WelcomDialog /> | |
− | + | </div>, | |
document.getElementById('root') | document.getElementById('root') | ||
); | ); | ||
975行目: | 975行目: | ||
function Dialog(props) { | function Dialog(props) { | ||
return ( | return ( | ||
− | + | <FancyBorder color="blue"> | |
− | + | <h1 className="Dialog-title">{props.title}</h1> | |
− | + | </FancyBorder> | |
); | ); | ||
} | } | ||
function FancyBorder(props) { | function FancyBorder(props) { | ||
return ( | return ( | ||
− | + | <div className={'FancyBorder FancyBorder-' + props.color}> | |
{props.children} | {props.children} | ||
− | + | </div> | |
); | ); | ||
} | } | ||
function WelcomDialog() { | function WelcomDialog() { | ||
return ( | return ( | ||
− | + | <Dialog title="Welcome" /> | |
); | ); | ||
} | } |
2020年2月15日 (土) 08:05時点における版
目次
React
スケルトン作成
- 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
- 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') );
インジェクション攻撃の予防
- デフォルトでReact DOMは、JSXに埋め込まれた値をレンダリング前にエスケープする
- 明示的にアプリケーションに記述しなくてもインジェクションされないことを保証する
- XSS攻撃の予防を助ける
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'}};
- これらの要素を"React elements"と呼ぶ
- 画面に表示させたいものの記述と考えることができる
- Reactはこれらのオブジェクトを読み、DOMの構築と最新化に利用する
Elementsのレンダリング
- Elements はReact アプリケーションの最小のビルディングブロック
- 1つの要素は、画面上に表示するものを表現する
- ブラウザのDOM要素と異なり、React Elements はプレーンなオブジェクトで生成のコストは小さい
- React DOMは、DOMをReact Elementsに一致するように取り扱う
1つの要素をDOMの中にレンダリング
- HTMLどこかに記述する、以下のDIV要素をroot DOM ノードと呼ぶ
- React DOM が管理するすべてが、この要素の中にある
<div id="root"></div>
React要素の更新
- React要素は不変。一旦生成したら、子要素、属性などは変更できない。
- UIを更新するには、新しい要素を作成し、ReactDOM.rendar()に引き渡す。
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
- React Router
Redux
- Redux
Tips
ビルド
© 2006 矢木浩人