# React lecture ###### tags: `lecture` `react` Reactの超入門レクチャーになります。 公式のリファレンスを参考にしつつ、自己流に解説します。なので、知識周りの正当性は怪しいと思ってください。すみません...。 プラス、このレクチャーほど厳密にやらなくても雰囲気でなんとかなる部分もあります。この辺は慣れと勘です。ゆるして。 本レクチャーのコードは全て[こちらのリポジトリ](https://github.com/akubi0w1/react-lecture)で公開しています。 materブランチに、モックアップ、CSSファイル、`create-react-app`で初期化した状態のコードが入っています。 章ごとのコードは、各ブランチを参考にしてください。 事前知識として、HTML, CSS, JavaScriptの基本構文、ES6表記が理解できているとよいと思います。 3項演算子や一部、高階関数(map, filter)を使用しています。 [TOC] ## I. 知識整理 ### React is 何 ユーザインターフェース(UI)を構築するためのJavaScriptのライブラリです。 データの変更を検知して、関連するコンポーネント(UIの部品。フォームや表など)だけを更新し、描画してくれます。 宣言的なView(データの変更検知、再レンダリングをReactに任せる)を用いてアプリケーションを書きます。 single page application(SPA: 一ページのみで構築されるアプリケーション)の作成に強いですが、ルーティングも可能なので、SPAでなくても全然作れます。 また、ReactNativeを使うことで、Reactの知識を用いてモバイルアプリも作れます。もちろんクロスプラットフォームで、iOSでもandroidでも動きます。時代はweb技術ですね。 tips: 宣言的プログラミングとは? これして、あれして、ということを記述するプログラミングのやり方。 あってるかはわからないけど、ソートを例にすると。 Declarative: List.sort() Imperative: バブルソートを実装する 日本では、なんか知らんがVue.jsが人気です。みんなVue触ってて怖いです。 ドキュメントについて、私個人としては、[React公式](https://ja.reactjs.org/)が原点にして頂点だと思っているので、ぜひ参考にしてください。 日本語への対応部分が多いので安心ですね。 そんなReactを利用するために、パッケージ管理ツールの`npm`をインストールする必要があります。 `node.js`をインストールすると一緒に落ちてきてくれるので、`node.js`をインストールします。 ### node.js is 何 > Node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。 Node.jsでは、JavaScriptを使ってアプリケーションのサーバサイドを書くことができます。 `HTTPリクエストを受けてから、どんな処理をして、どんなレスポンスを返すか`の部分を書くことができるわけです。つまり、`ブラウザのフォームで送信ボタンを押した後の処理`を書くということです。 今回はNode.jsに関しては何も知らないでおっけーです。 ### npm is 何 `npm`は、`node package manager`の略で、`Node.js`のパッケージを管理するツールです。 必要なパッケージをインストールする際に、依存しているパッケージもまとめてインストールしてくれます。 パッケージの依存関係などは、`package.json`というファイルに自動で書き込まれていきます。 ## II. 環境構築 ### install node.js / npm `node.js`をインストールします。ついでに`npm`もインストールされます。 #### windows [Node.js公式](https://nodejs.org/ja/)からインストーラーをダウンロードしましょう。 ダウンロードが終わったら、インストーラーを開いて、セットアップを開始します。基本的に`Next`を選択し続けます。 `Tools for Native Modules`という設定のみ、チェックを外しても大丈夫です。 チェックしておくと、いくつかのモジュールをコンパイルするのに必要なツールを自動で落としてくれます。この辺、詳しくは謎です。 インストールが終わったら、コマンドプロンプトを開いて、インストールが成功しているか確認します。 ``` $ node --version v11.2.0 $ npm --version 6.4.1 ``` こんな感じになれば、おっけーです。 #### mac 私は`homebrew`が大好きです。 この記事がめっちゃおすすめなのでぜひ。 [MacにNode.jsをインストール](https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09) ~~書くのがだるいかったとかそんなんじゃないです。~~ ## 1. Create React App 今回はSPAのめっちゃシンプルなtodoアプリを作ります。 完成形は以下のような感じです。 ![appImage](https://i.imgur.com/DmYmKGl.png) ### 1.1 npxで雛形を作る `npx`を使ってReactアプリの雛形を作ります。 コマンドプロンプト or ターミナルで、適当なディレクトリに移動します。 今回のアプリケーション名は`simple-todo`にします。 ``` $ npx create-react-app simple-todo ``` tips: npxとは? `npm5.2`から利用できるパッケージランナーツールです。 [Introducing npx: an npm package runner](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) tips: npxを使わずにReact環境を作ると? [私の殴り書き](https://tmblr.co/Zyxq5VYSDVlRKi00)ですが、こんなことをやっているらしいです。めっちゃ大変です。[参考にしたページ](https://blog.usejournal.com/creating-a-react-app-from-scratch-f3c693b84658)が英語なのですが、勉強になるのでぜひ。 ### 1.2 雛形を動かしてみる `create-react-app`を利用して雛形を作ると、ある程度のコードとスクリプトを自動で用意してくれます。 Reactアプリを起動するには、`npm start`を利用します。 ``` // simple-todoディレクトリにて $ npm start ``` すると、`localhost:3000`にてアプリが起動します。 ![initApp](https://i.imgur.com/cUvxV88.png) scriptの詳細に関しては、自動生成される`README.md`を参照してください。 現時点でアプリがどのように動いているか軽く解説します。 `public/index.html`、`src/index.js`が起点になります。 `public/index.html`では、Reactアプリを動かす元となるHTMLが記述されています。 `src/index.js`にて、`public/index.html`内のセレクタ(`<div id="root"></div>`)を指定し、その箇所にレンダリング(描画)を行う処理が記述されています。 ```javascript= // src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render( <React.StrictMode> {/* Appコンポーネント(src/App.jsのApp)を描画 */} <App /> </React.StrictMode>, document.getElementById('root') ); serviceWorker.unregister(); ``` ## 2. コンポーネントの構造を考える [Reactの流儀](https://ja.reactjs.org/docs/thinking-in-react.html)を参考にしつつ、私見を交えてコンポーネントを考えます。 ### 2.1 コンポーネント is 何 コンポーネントとは、UIを構成する部品のことです。例えばフォーム、リストなど、突き詰めるとボタンなんかもコンポーネントと言えるかもしれません。 Reactには、関数コンポーネントとクラスコンポーネントが存在します。 関数コンポーネントはstateを持たず、クラスコンポーネントは持つことができるという違いがあります。 また、関数コンポーネントは戻り値としてDOM構造を返し、クラスコンポーネントは`render()`を持つ必要があります。 この辺は後々出てくるので、今は流して結構です。 Reactでは、コンポーネントを組み合わせることでページを作成します。 この辺は習うより慣れろです。 ### 2.2 デザインモックを作る Reactの流儀にも書いてありますが、コンポーネントの構造を考える上で、デザインモックがあるとめちゃめちゃ考えやすくなります。 デザインモックというのは、画面のサンプルみたいなもののことです。今回作るアプリのデザインモックはこちら。 ![designMock](https://i.imgur.com/DmYmKGl.png) ### 2.3 コンポーネントに分割する Reactの流儀より、1つのコンポーネントが1つのことだけをする(単一責任の原則)ように考えるとよいそうです。 扱うデータの構造がJSONだった場合、コンポーネントとJSONの構造がマッピングされるイメージになります。 個人的には、AtomicDesignでいう、分子レベルまで分割するようにしている。原子まで行くとちょっと細かくなりすぎる気がします。 AtomicDesignについては、「[Atomic Designを分かったつもりになる](https://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B/)」を参考にするとなんとなくわかるかもしれません。 以上のことを踏まえて、先ほどのデザインモックをコンポーネントに分割してみます。 ![componentsStructure](https://i.imgur.com/dR4wjnH.png) 各コンポーネントの役割は以下のようになっています。 - Header : ヘッダ部分。ナビゲーションがないから寂しい - Container : コンテンツを差し込む場所 - TodoForm : ユーザの入力を受け付ける - TodoList : todoリストを完了/未完了に分けて表示する - TodoItem : 一項目を表示 コンポーネントを階層構造にすると、以下のようになります。 - Header - Container - TodoForm - TodoList - TodoItem tips: リストのタイトル部分をコンポーネントにするか、しないかは好みの問題です。今回は分割することの恩恵があまりなさそうだったので分割していません。 コンポーネントの分割については、ベストプラクティスがわからないので、誰か教えてください(´・ω・`) ## 3. Staticなページを作る 静的なページを作成します。静的なページとは、動的なコンテンツや機能を何も持たないページのことです。 静的なページを作る上で、propsという概念が出てきます。 ### 3.1 props is 何 ここで、Reactの重要な概念であるprops(proparty)が出てきます。 propsとは、**親コンポーネントから渡される値**のことをさします。関数でいう引数とよく似ています。props自体はJavaScriptのオブジェクトです。 propsは**読み取り専用**です。どんな場合であれ、propsは決して変更してはいけません。 公式には以下のような文言で書いてあります。 > React は柔軟ですが、1 つだけ厳格なルールがあります: **全ての React コンポーネントは、自己の props に対して純関数のように振る舞わねばなりません。** 純粋関数は、入力値が同じなら、必ず同じ結果が得られる関数のことです。 ### 3.2 ファイルの作成 後々使うファイルも一緒に作ってしまいましょう。今回は1コンポーネントにつき1ファイル、みたいな雰囲気で作ります。 実際に作業するときは、必要になったらファイルを作る、という感じでいいと思います。 ディレクトリ構造が以下になるようにファイルを作成します。 ``` simple-todo/src/ ├── assets │ └── css │ └── App.css ├── components │ ├── Container.js │ ├── Header.js │ ├── TodoForm.js │ └── TodoList.js ├── App.js ├── ...省略 └── setupTests.js ``` ``` // simple-todoディレクトリで行います $ mkdir -p src/assets/css $ touch src/assets/css/App.css $ mkdir src/components $ touch src/components/Header.js $ touch src/components/Container.js $ touch src/components/TodoForm.js $ touch src/components/TodoList.js ``` `src/assets/css/App.css`の内容は[こちら](https://raw.githubusercontent.com/akubi0w1/react-lecture/master/src/assets/css/App.css)です。コピペしちゃってください。 `TodoItem.js`がないのは、コンポーネントの規模が小さいだろうなあーって思ったからです。 tips: ファイルの分割について 私見ですが、慣れてくると最初からコンポーネントを各ファイルに分割して書き始めます。 ただ、最初のうちはファイルを行ったり来たりすると何書いてるかわからなくなるので、一つのファイルで書いちゃうのが良いかもしれないです。 tips: 私がどうやってるかの話 デザインモックを1コンポーネントにコピペで突っ込みます。そのあと、各ファイルに各コンポーネントを切り出していく、という風にやってます。 ### 3.3 静的なページをゴリゴリ書く 今回は一旦、`src/components/Header.js`と`src/components/Container.js`にすべて突っ込みます。コンポーネントごとにファイルを散らすのは後にしましょう。 Reactでは、基本的にデータは下方向に伝わっていきます。つまり、親コンポーネントから子コンポーネントに伝わっていくということです。 親コンポーネントから子コンポーネントにデータを渡す際はpropsを使用します。 子から親へのデータフローは、*5. 逆方向データフローを追加する*で触れます。 ここでは、propsのみを利用して組み上げていきます。state(直後に出てきます)は一切使いません。 > state はユーザ操作や時間経過などで動的に変化するデータを扱うために確保されている機能です。 - Reactの流儀 そのため、全てのコンポーネントを関数コンポーネントで記述します。 おさらいですが、関数コンポーネントはstateを持ちません。stateを管理する場合、クラスコンポーネントを使う必要があります。 tips: 実は React Hooksを使うことで、関数内でstateを管理できるようになりました。この辺は気合があれば書きます。 #### 3.3.1 jsx Reactでは`JSX`という記法が使えます。 JavaScriptの中に、HTMLのようなものを埋め込んで記述することができます。 #### 3.3.2 コード ```javascript= // simple-todo/src/App.js import React from 'react'; import './assets/css/App.css' import Container from './components/Container'; import Header from './components/Header' const App = () => ( <> <Header /> <Container items={TODO_LIST}/> </> ); export default App; // sample data const TODO_LIST = [ { id: 1, todo: 'something', isDone: false, }, { id: 2, todo: 'something', isDone: false, }, { id: 3, todo: 'something', isDone: false, }, { id: 4, todo: 'something', isDone: true, }, { id: 5, todo: 'something', isDone: true, }, ] ``` ```javascript= // simple-todo/src/components/Header.js import React from 'react'; const Header = () => ( <div className="header"> <span>Simple TODO</span> </div> ); export default Header; ``` ```javascript= // simple-todo/src/components/Container.js import React from 'react'; const Container = (props) => { // props = src/App.jsのTODO_LIST const itemList = [ { title: "todo", items: props.items.filter(todo => !todo.isDone) }, { title: "done", items: props.items.filter(todo => todo.isDone) } ] return ( <div className="container-wrapper"> <div className="container"> <TodoForm /> { itemList.map(elem => ( <TodoList key={elem.title} title={elem.title} items={elem.items} /> )) } </div> </div> ) }; const TodoForm = () => ( <form className="form" method="POST"> <input type="text" name="todo" autoComplete="off" /> <button type="submit" className="btn btn-primary">ADD</button> </form> ); const TodoList = (props) => ( // props = { title, items } // items = [ { id, todo, isDone }, ... ] <div> <label className="table-title">{props.title}</label> <table className="todo-list"> <tbody> { props.items.map(item => ( <TodoItem key={item.id} item={item} /> )) } </tbody> </table> </div> ); const TodoItem = (props) => ( // props = { item } // item = { id, todo, isDone } <tr> <td>{props.item.todo}</td> { !props.item.isDone ? <td><button className="btn btn-primary">done</button></td> : <> <td><button className="btn btn-secondary">todo</button></td> <td><button className="btn btn-danger">delete</button></td> </> } </tr> ); export default Container; ``` ## 4. stateを考える 静的なページに動的な要素を加えていきます。 その上で、stateという概念が重要になります。 ### 4.1 state is 何 Reactの重要な概念の一つです。 stateとは、あるコンポーネントの状態を表す変数(private field)です。ユーザの操作や時間経過などによる動的な値の変化を扱います。 クラスコンポーネントはstateを持つことができます。 stateの注意点としては以下が挙げられます。 - stateを直接編集しない - 直で代入するのではなく、必ず`setState()`を使用する - stateの更新は非同期で行われる可能性がある - 更新後の値が、更新前のprops, stateに依存しているべきではない - stateの更新はマージで行われる - `{ todo: "", done: false }`というstateに`setState({todo: "something"})`とすると、結果は`{ todo: "something", done: false }`となる - stateの更新はイミュータブルに行うべき - 元stateに対し、破壊的な更新にならないようにする ### 4.2 stateになりうる値を検討する Reactの流儀を参考に行なっていきます。 まず、アプリで扱うデータを全て洗い出します。 今回は以下のデータになります。 - 元のtodoリスト - formに入力された文字列値 - todo/doneに状態分けされたtodoのリスト 次に、挙げたデータが**stateになりうるか**を検討します。 Reactの流儀より、以下三つの点で見ていきましょう。 1. 親から props を通じて与えられたデータでしょうか? もしそうなら、それは state ではありません 2. 時間経過で変化しないままでいるデータでしょうか? もしそうなら、それは state ではありません 3. コンポーネント内にある他の props や state を使って算出可能なデータでしょうか? もしそうなら、それは state ではありません 元todoリストは、親コンポーネントから与えられるデータなので、stateではありません。 次に、formの値ですが、これはstateになりそうです。 次に、todo/doneに状態分けされたtodoのリストですが、これは元todoリストから算出できるデータなので、stateではありません。 以上より、stateで扱う値は、 - formに入力された文字列値 のみになります。 今回はゼミのプロジェクトの性質上、元のtodoリストもstateで扱わせてください...。 なので、最終的にstateで扱うデータは、以下2つです。 - 元のtodoリスト - formに入力された文字列値 tips: 私見 アプリケーションにバックエンドがある場合、バックエンドサーバから取得するデータはstateで扱っています。 ### 4.3 stateをどのコンポーネントに持たせるか検討する こちらもReactの流儀に沿って進めていきます。 各state(formの値、元todoリスト)について、以下項目を見ながら決めていきます。 - その state を使って表示を行う、すべてのコンポーネントを確認する - 共通の親コンポーネントを見つける(その階層構造の中で、ある state を必要としているすべてのコンポーネントの上位にある単一のコンポーネントのことです) - 共通の親コンポーネントか、その階層構造でさらに上位の別のコンポーネントが state を持っているべきである - もし state を持つにふさわしいコンポーネントを見つけられなかった場合は、state を保持するためだけの新しいコンポーネントを作り、階層構造の中ですでに見つけておいた共通の親コンポーネントの上に配置する 階層構造のおさらいです。ついでに、描画に必要なデータも書いておきます。 - Header - Container - TodoForm (formの値) - TodoList (元todoリスト、todo/doneに別れているリスト) - TodoItem (todoリストの1つ) formの値、元todoリストは、それぞれの親コンポーネントとなる`Container`が持っていればよい、ということになりそうです。 tips: Redux stateの数が増えてくると、stateを管理するコンポーネントが散らばったり、管理するstateが多すぎてコンポーネントが重くなることがあります。 そうなった時に、stateを`store`を用いて一元管理することができます。 ### 4.4 stateを持たせる stateを持たせるために、`Container`コンポーネントをクラスコンポーネントに書き換えます。 この際、関数コンポーネントで`props`と指定していた部分が、`this.props`となる部分が多いので注意してください。 また、stateの管理ロジックもここで書きます。 ```javascript= // simple-todo/src/components/Container.js ...省略 // クラスコンポーネントに書き換え class Container extends React.Component { // props = src/App.jsのTODO_LIST constructor(props) { // コンポーネントがレンダリングされた時一回だけ呼ばれる super(props) this.state = { input: "", items: props.items, counter: props.items.length + 1, } } // クラスコンポーネントはrender()を持つ render() { const itemList = [ { title: "todo", items: this.state.items.filter(todo => !todo.isDone) }, { title: "done", items: this.state.items.filter(todo => todo.isDone) } ] return ( <div className="container-wrapper"> <div className="container"> {/* stateを子コンポーネントに渡します */} <TodoForm value={this.state.input}/> { itemList.map(elem => ( <TodoList key={elem.title} title={elem.title} items={elem.items} /> )) } </div> </div> ) } }; const TodoForm = (props) => ( <form className="form" method="POST"> {/* valueをContainerコンポーネントのstateで管理します */} <input type="text" name="todo" autoComplete="off" value={props.value} /> <button type="submit" className="btn btn-primary">ADD</button> </form> ); ...省略 ``` この時点で、inputフィールドに文字を入力しても、描画されなくなります。 なぜこんなことが起こるかというと、inputフィールドのvalue属性が、`Container`コンポーネントのstateで管理されているためです。 stateが更新されなければ、inputフィールドの値が描画されることはありません。 ## 5. 逆方向データフローを追加する ### 5.1 逆方向データフロー is 何 Reactは基本的に、親から子へデータを渡します。 しかし、子コンポーネントの変更を親コンポーネントに伝えなければならない場合がどうしたって訪れます。 このような、子から親へデータを伝えることが、逆方向データフローです。 今回の例では、例えば、`TodoForm`コンポーネントで起きるイベント(formの入力、submitボタンのクリックなど)が、formの値をstateとして管理している`Container`コンポーネントへ伝わる必要があります。 ### 5.2 逆方向データフローを実装する 以下を実装します。 - `TodoForm`のinputフィールドへの入力を検知して、`Container`の`state.input`を更新する - `TodoForm`のsubmitが押されたことを検知して、`Container`の`state.items`を更新する - `TodoList`の`done`ボタン/`todo`ボタンが押された時、`Container`の`state.items`を更新する - 特定の`todo`の`isDone`を切り替える - `TodoList`の`delete`ボタンが押された時、`Container`の`state.items`を更新する - 特定の`todo`を削除する イベントハンドラ(今回はイベントが起きた時の処理を書く関数)を定義する関係で、クラスコンポーネントに変更しているコンポーネントがあります。 この際、関数コンポーネントで`props`と指定していた部分が、`this.props`となる部分が多いので注意してください。 ```javascript= // simple-todo/src/components/Container.js import React from 'react'; class Container extends React.Component { // props = src/App.jsのTODO_LIST constructor(props) { super(props); this.state = { input: "", items: props.items, counter: props.items.length + 1, }; // ここでbindしてあげないと、handle内でthisが使えません this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.handleSwitch = this.handleSwitch.bind(this); this.handleDelete = this.handleDelete.bind(this); } // inputフィールドの値の変更をstateに反映する handleChange(value) { this.setState({ input: value }); } // submitされたらstate.itemsを更新する handleSubmit(value) { if (!value) { console.log("empty input"); return } const newItem = { id: this.state.counter, todo: value, isDone: false, } this.setState({ items: [ newItem, ...this.state.items ], counter: this.state.counter + 1, input: "", }) } // todo/doneの切り替え handleSwitch(todoId) { const newItems = this.state.items.map(item => item.id === todoId ? {...item, isDone: !item.isDone} : item); this.setState({ items: newItems }); } // 削除ハンドラ handleDelete(todoId) { const newItems = this.state.items.filter(item => item.id !== todoId) this.setState({ items: newItems }) } render() { const itemList = [ { title: "todo", items: this.state.items.filter(todo => !todo.isDone) }, { title: "done", items: this.state.items.filter(todo => todo.isDone) } ]; return ( <div className="container-wrapper"> <div className="container"> {/* 定義したハンドラを渡す */} <TodoForm value={this.state.input} handleChange={this.handleChange} handleSubmit={this.handleSubmit}/> {/* 定義したハンドラを渡す */} { itemList.map(elem => ( <TodoList key={elem.title} title={elem.title} items={elem.items} handleSwitch={this.handleSwitch} handleDelete={this.handleDelete}/> )) } </div> </div> ); } }; // handlerが必要なのでクラスにしなくちゃあかんわ class TodoForm extends React.Component { constructor(props) { super(props); // ここでbindしてあげないと、handle内でthisが使えません this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(e) { this.props.handleChange(e.target.value); } handleSubmit(e) { e.preventDefault() // POSTリクエストが飛んでいくのを阻止する this.props.handleSubmit(this.props.value) } render() { return ( // submitハンドラを指定 <form className="form" method="POST" onSubmit={this.handleSubmit}> {/* changeハンドラを指定 */} <input type="text" name="todo" autoComplete="off" value={this.props.value} onChange={this.handleChange} /> <button type="submit" className="btn btn-primary">ADD</button> </form> ); } } const TodoList = (props) => ( // props = { title, items } // items = [ { id, todo, isDone }, ... ] <div> <label className="table-title">{props.title}</label> <table className="todo-list"> <tbody> { // さらにハンドラを渡す props.items.map(item => ( <TodoItem key={item.id} item={item} handleSwitch={props.handleSwitch} handleDelete={props.handleDelete}/> )) } </tbody> </table> </div> ); // ハンドラ定義のためにclassにしなきゃあかんわ class TodoItem extends React.Component { constructor(props) { super(props) this.handleSwitch = this.handleSwitch.bind(this); this.handleDelete = this.handleDelete.bind(this); } handleSwitch(e) { this.props.handleSwitch(this.props.item.id); } handleDelete(e) { this.props.handleDelete(this.props.item.id); } render() { return ( <tr> <td>{this.props.item.todo}</td> { !this.props.item.isDone // 各ボタンにハンドラをしてする ? <td><button className="btn btn-primary" onClick={this.handleSwitch}>done</button></td> : <> <td><button className="btn btn-secondary" onClick={this.handleSwitch}>todo</button></td> <td><button className="btn btn-danger" onClick={this.handleDelete}>delete</button></td> </> } </tr> ); } } export default Container; ``` ## 6. コンポーネントを各ファイルに分割する ### 6.1 各ファイルに分割する ほぼコピペタイム。 今まで`src/components/Container.js`に全て記述していましたが、これを各ファイルに散らしていきます。 1ファイルのもつコードを減らすことで、全体の見通がつきやすくなりますし、変更・改修時にどこをいじればいいかすぐに見つけることができます。 tips: 私見。ディレクトリ構造の話 私の流行は[React Project Structure Best Practices for Scalable Application](https://dev.to/syakirurahman/react-project-structure-best-practices-for-scalable-application-18kk)です。 今回のレクチャーでは、ここまで細かく分割するメリットがなさすぎるので、ディレクトリ分割は行いません。規模の大きいアプリになった場合、参考にどうぞ。 ```javascript= // simple-todo/src/components/Containe.js import React from 'react'; // importを追加 import TodoForm from './TodoForm'; import TodoList from './TodoList'; class Container extends React.Component { // props = src/App.jsのTODO_LIST constructor(props) { ...省略 } ...省略 render() { ...省略 } }; export default Container; ``` ```javascript= // simple-todo/src/components/TodoForm.js import React from 'react'; // handlerが必要なのでクラスにしなくちゃあかんわ class TodoForm extends React.Component { constructor(props) { super(props); // ここでbindしてあげないと、handle内でthisが使えません this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(e) { this.props.handleChange(e.target.value); } handleSubmit(e) { e.preventDefault() // POSTリクエストが飛んでいくのを阻止する this.props.handleSubmit(this.props.value) } render() { return ( // submitハンドラを指定 <form className="form" method="POST" onSubmit={this.handleSubmit}> {/* changeハンドラを指定 */} <input type="text" name="todo" autoComplete="off" value={this.props.value} onChange={this.handleChange} /> <button type="submit" className="btn btn-primary">ADD</button> </form> ); } } export default TodoForm; ``` ```javascript= // simple-todo/src/components/TodoList.js import React from 'react'; const TodoList = (props) => ( // props = { title, items } // items = [ { id, todo, isDone }, ... ] <div> <label className="table-title">{props.title}</label> <table className="todo-list"> <tbody> { // さらにハンドラを渡す props.items.map(item => ( <TodoItem key={item.id} item={item} handleSwitch={props.handleSwitch} handleDelete={props.handleDelete} /> )) } </tbody> </table> </div> ); // ハンドラ定義のためにclassにしなきゃあかんわ class TodoItem extends React.Component { constructor(props) { super(props) this.handleSwitch = this.handleSwitch.bind(this); this.handleDelete = this.handleDelete.bind(this); } handleSwitch(e) { this.props.handleSwitch(this.props.item.id); } handleDelete(e) { this.props.handleDelete(this.props.item.id); } render() { return ( <tr> <td>{this.props.item.todo}</td> { !this.props.item.isDone // 各ボタンにハンドラをしてする ? <td><button className="btn btn-primary" onClick={this.handleSwitch}>done</button></td> : <> <td><button className="btn btn-secondary" onClick={this.handleSwitch}>todo</button></td> <td><button className="btn btn-danger" onClick={this.handleDelete}>delete</button></td> </> } </tr> ); } } export default TodoList; ``` ## 7. ライフサイクルについて考える 力尽きたので、詳しくは許してください。 [ライフサイクル図](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) ```javascript= // simple-todo/src/components/Container.js ... class Container extends React.Component { constructor(props) { ...省略 } // コンポーネントがマウントされた時に実行される componentDidMount() { this.setState({ items: this.props.items, counter: this.props.items.length + 1, }); } // コンポーネントがアンマウントされる時に実行される componentWillUnmount() { this.setState({ items: [], counter: 0, input: '', }); } ...省略 render() { ...省略 } }; ... ``` ### Reactのレンダリングの仕組み ## 8. React Hooks 力尽きたので(ry ## 参考 - [React公式](https://ja.reactjs.org/) - [Reactの流儀](https://ja.reactjs.org/docs/thinking-in-react.html) - [npm入門 - とほほのWWW入門](http://www.tohoho-web.com/ex/npm.html) - [Node.jsをインストールする - Qiita](https://qiita.com/sefoo0104/items/0653c935ea4a4db9dc2b) - [MacにNode.jsをインストール - Qiita](https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09) - [React Project Structure Best Practices for Scalable Application](https://dev.to/syakirurahman/react-project-structure-best-practices-for-scalable-application-18kk) - [ライフサイクル図](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) - [Atomic Designを分かったつもりになる](https://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B/)