An open API service indexing awesome lists of open source software.

https://github.com/kazuma1989/react-training-180421


https://github.com/kazuma1989/react-training-180421

Last synced: 5 days ago
JSON representation

Awesome Lists containing this project

README

        

# React トレーニング

[Cloud9](https://aws.amazon.com/jp/cloud9/) 上で [Create React App](https://github.com/facebook/create-react-app) を使ってみます。

## プロジェクトセットアップ

### AWS アカウント作成, Cloud9 インスタンス起動

割愛

### Node.js を最新化

Node.js のバージョンマネージャー `nvm` で LTS 版をインストールし、デフォルトをその LTS 版に変更します:

```bash
nvm install --lts
nvm alias default stable
```

### Create React App でプロジェクトを初期化

npm スクリプトを即時実行できる `npx` コマンドを使い、`create-react-app` を実行します:

```bash
npx create-react-app myapp
cd myapp
npm start
```

`npm start` で開発サーバーが起動するので、Cloud9 のメニューから表示します:

Cloud9 menu > Preview > Preview Running Application

## TODO アプリ作成

### モック作成

まずは、動きや変数のない見た目だけのモックを作成します。
CSS は、簡単のため削除してしまいます。

```diff
src/
-├── App.css
├── App.js
├── App.test.js
-├── index.css
├── index.js
-├── logo.svg
└── registerServiceWorker.js
```

App.js:

```diff
import React, { Component } from 'react';
-import logo from './logo.svg';
-import './App.css';

class App extends Component {
render() {
return (
-


-
- logo
-

Welcome to React


-
-


- To get started, edit src/App.js and save to reload.
-


-

+
+
+

todos


+
+
+
+

    +

  • +
    todo 1

    +

  • +

  • +
    todo 2

    +

  • +

  • +
    todo 3

    +

  • +

+
+
);
}
}
```

index.js:

```diff
import React from 'react';
import ReactDOM from 'react-dom';
-import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(, document.getElementById('root'));
registerServiceWorker();
```

### 変数を表示する

スクリプト内の値を HTML に反映させてみます:

App.js:

```diff
class App extends Component {
render() {
+ const todoList = [
+ 'House keeping',
+ 'Answer the survey',
+ 'Water the plants'
+ ];
+
return (


```

```diff



  • -
    todo 1

    +
    {todoList[0]}



  • -
    todo 2

    +
    {todoList[1]}



  • -
    todo 3

    +
    {todoList[2]}




```

### イベントを受け取る

クリックイベントを受け取り、アラートを表示してみます:

App.js:

```diff
import React, { Component } from 'react';

class App extends Component {
+
+ handleClick() {
+ alert('clicked!');
+ }
+
render() {
const todoList = [
'House keeping',
```

```diff



  • -
    {todoList[0]}

    +
    {todoList[0]}



  • {todoList[1]}

    ```

    ### コンポーネントの状態を変化させる

    クリックしたアイテムを「完了」状態にしてみます。

    まずは、コンポーネントが状態を持てるようにします:

    App.js:

    ```diff
    class App extends Component {

    + constructor(props) {
    + super(props);
    +
    + this.state = {
    + todoList: [
    + 'House keeping',
    + 'Answer the survey',
    + 'Water the plants'
    + ]
    + };
    + }
    +
    handleClick() {
    alert('clicked!');
    }

    render() {
    - const todoList = [
    - 'House keeping',
    - 'Answer the survey',
    - 'Water the plants'
    - ];
    -
    return (


    ```

    ```diff



    • -
      {todoList[0]}

      +
      {this.state.todoList[0]}



    • -
      {todoList[1]}

      +
      {this.state.todoList[1]}



    • -
      {todoList[2]}

      +
      {this.state.todoList[2]}




    ```

    次に、クリックによってその状態を変化させてみます:

    App.js:

    ```diff
    'Water the plants'
    ]
    };
    +
    + this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
    - alert('clicked!');
    + const { todoList } = this.state;
    +
    + todoList[0] += ' (DONE)';
    +
    + this.setState({
    + todoList
    + });
    }

    render() {
    ```

    ここでの React 的に重要なポイントは、`setState()` メソッドを使うところです。
    コンポーネントの状態は、`setState()` メソッドによってのみ変化します。

    JavaScript 的につまずきやすいポイントは、`bind()` メソッドによる `this` の束縛です。

    ### テキストボックスへの入力を受け取る

    テキストボックスへの入力を状態として持ってみます:

    App.js:

    ```diff
    'House keeping',
    'Answer the survey',
    'Water the plants'
    - ]
    + ],
    + newTodo: ''
    };

    + this.handleChange = this.handleChange.bind(this);
    this.handleClick = this.handleClick.bind(this);
    }

    + handleChange(event) {
    + this.setState({
    + newTodo: event.target.value
    + });
    + }
    +
    handleClick() {
    const { todoList } = this.state;
    ```

    ```diff


    todos


    -
    +



      ```

      クリックイベントを受け取ったときと同様、`setState()` メソッドを使います。
      「コンポーネントの状態は、`setState()` メソッドによって **のみ** 変化します」と述べましたが、テキストボックスに関しては、そのことが如実に現れます。
      `setState()` メソッドの呼び出しをコメントアウトすると実感できます。

      ### アイテムを追加する

      テキストボックスの内容を、新たな TODO として追加できるようにします。

      まず、アイテムが増えたときにリストが増えるよう、リストを可変にします:

      App.js:

      ```diff




      -

    • -
      {this.state.todoList[0]}

      -

    • -

    • -
      {this.state.todoList[1]}

      -

    • -

    • -
      {this.state.todoList[2]}

      -

    • + {this.state.todoList.map((item, index) => (
      +

    • +
      {item}

      +

    • + ))}



    ```

    次に、エンターキーによってテキストボックスの内容がリストに追加されるようにします:

    App.js:

    ```diff
    newTodo: ''
    };

    + this.handleSubmit = this.handleSubmit.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleClick = this.handleClick.bind(this);
    }

    + handleSubmit(event) {
    + event.preventDefault();
    +
    + const { todoList, newTodo } = this.state;
    +
    + this.setState({
    + todoList: todoList.concat([newTodo])
    + });
    + }
    +
    handleChange(event) {
    this.setState({
    newTodo: event.target.value
    ```

    ```diff


    todos


    -
    +
    +
    +



      ```

      ### Option: DONE が適切な位置に追加されるよう修正

      割愛

      ### Option: リストをストレージに保存する

      割愛

      ## TODO アプリリファクタリング

      ### TodoList コンポーネントの作成

      ここまでは `App` コンポーネントに全ての処理を書いていました。
      このアプリの規模ではそれで十分です。
      しかし、より高度なアプリを(メンテナンス可能なように)作るには、部品化が欠かせません。

      TodoList コンポーネントを導入し、部品化を体験してみます。

      まず、リスト部分をそのまま外部ファイルに移します。
      この段階ではまだ、`Uncaught TypeError: Cannot read property 'todoList' of null` というランタイムエラーが出ます:

      ```diff
      src/
      ├── App.js
      ├── App.test.js
      ├── index.js
      ├── registerServiceWorker.js
      +└── TodoList.js
      ```

      App.js:

      ```diff
      import React, { Component } from 'react';
      +import TodoList from './TodoList';

      class App extends Component {
      ```

      ```diff





-

    - {this.state.todoList.map((item, index) => (
    -

  • -
    {item}

    -

  • - ))}
    -

+


);
```

TodoList.js:

```diff
+import React from 'react';
+
+class TodoList extends React.Component {
+
+ render() {
+ return (
+


    + {this.state.todoList.map((item, index) => (
    +

  • +
    {item}

    +

  • + ))}
    +

+ );
+ }
+}
+
+export default TodoList;
```

次に、エラーを解消します:

TodoList.js:

```diff
render() {
return (


    - {this.state.todoList.map((item, index) => (
    + {this.props.list.map((item, index) => (

  • -
    {item}

    +
    {item}


  • ))}

```

ここでの React 的に重要なポイントは、`props` プロパティーと、イベントハンドラーの受け渡しです。
`props.list` のようにデータを渡すことができ、`props.onClick` のようにイベント発生時の振る舞い自体も渡すことができます。

### TodoInput コンポーネントの作成

同様に、入力コンポーネントも外部に切り出します:

```diff
src/
├── App.js
├── App.test.js
├── index.js
├── registerServiceWorker.js
+├── TodoInput.js
└── TodoList.js
```

App.js:

```diff
import React, { Component } from 'react';
import TodoList from './TodoList';
+import TodoInput from './TodoInput';

class App extends Component {
```

```diff
'Answer the survey',
'Water the plants'
],
- newTodo: ''
};

this.handleSubmit = this.handleSubmit.bind(this);
- this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
```

```diff
});
}

- handleChange(event) {
- this.setState({
- newTodo: event.target.value
- });
- }
-
handleClick() {
const { todoList } = this.state;
```

```diff


todos


-
-
-
+



```

TodoInput.js:

```diff
+import React from 'react';
+
+class TodoInput extends React.Component {
+
+ handleChange(event) {
+ this.setState({
+ newTodo: event.target.value
+ });
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+export default TodoInput;
```

この時点ではまだエラーが残ります。
`Uncaught TypeError: Cannot read property 'newTodo' of null` というランタイムエラーです。

次に、エラーを解消します:

TodoList.js:

```diff
class TodoInput extends React.Component {

+ constructor(props) {
+ super(props);
+
+ this.state = {
+ newTodo: ''
+ };
+
+ this.handleChange = this.handleChange.bind(this);
+ }
+
handleChange(event) {
this.setState({
newTodo: event.target.value
```

```diff
render() {
return (
-
+


);
```

エラーは解消されますが、リストへ空のアイテムしか追加できなくなっています。

最後に、テキストボックス内の内容がリストに反映されるよう修正します:

App.js:

```diff
this.handleClick = this.handleClick.bind(this);
}

- handleSubmit(event) {
- event.preventDefault();
-
- const { todoList, newTodo } = this.state;
+ handleSubmit(newTodo) {
+ const { todoList } = this.state;

this.setState({
todoList: todoList.concat([newTodo])
```

TodoInput.js:

```diff
};

this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
```

```diff
});
}

+ handleSubmit(event) {
+ event.preventDefault();
+
+ const { newTodo } = this.state;
+ this.props.onSubmit(newTodo);
+ }
+
render() {
return (
-
+


);
```

## Question: DONE が適切な位置に追加されるよう修正

解答

App.js:

```diff
});
}

- handleClick() {
+ handleClick(index) {
const { todoList } = this.state;

- todoList[0] += ' (DONE)';
+ todoList[index] += ' (DONE)';

this.setState({
todoList
```

TodoList.js:

```diff


    {this.props.list.map((item, index) => (

  • -
    {item}

    +
    this.props.onClick(index)}>{item}


  • ))}

```

## Option: 完了状態をスタイルで表現する

クリックしたときにスタイルが変わるようにします。
インラインスタイルでもよいですが、CSS ファイルを作って実現してみます:

```diff
src/
├── App.js
├── App.test.js
├── index.js
├── registerServiceWorker.js
├── TodoInput.js
+├── TodoList.css
└── TodoList.js
```

TodoList.css:

```diff
+.completed {
+ color: #d9d9d9;
+ text-decoration: line-through;
+}
```

TodoList.js:

```diff
import React from 'react';
+import './TodoList.css';

class TodoList extends React.Component {
```

```diff
return (


    {this.props.list.map((item, index) => (
    -

  • -
    this.props.onClick(index)}>{item}

    +


  • +
    this.props.onClick(index)}>{item.title}


  • ))}

```

App.js:

```diff
this.state = {
todoList: [
- 'House keeping',
- 'Answer the survey',
- 'Water the plants'
+ { title: 'House keeping', completed: false },
+ { title: 'Answer the survey', completed: false },
+ { title: 'Water the plants', completed: false },
],
};
```

```diff
const { todoList } = this.state;

this.setState({
- todoList: todoList.concat([newTodo])
+ todoList: todoList.concat([{
+ title: newTodo,
+ completed: false,
+ }])
});
}

handleClick(index) {
const { todoList } = this.state;

- todoList[index] += ' (DONE)';
+ todoList[index].completed = !todoList[index].completed;

this.setState({
todoList
```