React ハンズオン

# 今日作成するアプリ
![app.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124660/53eb688c-bbbc-d027-8dba-12ebca76796a.gif)


# 1.Reactの簡単な説明
- データの変更を検知したら、関連する部分だけを効率的に更新、描画する
- 仮想 DOM(インメモリに保持されたUI表現)による高速な描画
- JSXを使う(JavaScriptのソースコードにHTML的なものを埋め込む)
- 単一のWebページでアプリケーション(Single Page Application)を作れる

## Reactの採用事例
主な採用事例

- Netflix
- Slack
- Uber
- Airbnb
- Paypal

## ES2015(ES6)について
- ECMASCriptの6th Editionのこと
- letとconstで変数を宣言できる
- アロー関数 : console.log(materials.map(material => material.length));
- Class構文
- extendsでクラスの継承
- 全てのブラウザで対応しているわけではないため、Babelというトランスパイラを利用する
- ReactはES6かES7で書く場合が多い


# 2.新しいシングルページアプリケーションを作成する

create-react-appという新規のReactプロジェクトを作るCLIツールを使います。
まずは、create-react-appをインストールします。
(Node >= 8.10 及び npm >= 5.6 の環境が必要です)

```
npm install -g create-react-app
```

todoプロジェクトを作成します。

```shell
npx create-react-app todo
```

# 3.ディレクトリ構成についての説明

```
.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html Reactアプリケーションがのるページ
├── src
│   ├── App.css App.jsで使用されるcss
│   ├── App.js index.jsから呼ばれるReactコンポーネント
│   ├── App.test.js
│   ├── index.css index.jsで使われるcss
│   ├── index.js Reactアプリケーションで最初に走るスクリプト(ルート DOM ノードにレンダリングする処理が書かれている)
│   └── logo.svg
└── yarn.lock
```

# 4.実行する

次のコマンドを実行します。

```
npm start
```

ブラウザに次のように表示されたら成功です。(http://localhost:3000のURLでブラウザが起動します)

<img width="581" alt="chapter4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124660/698c3f02-d9e7-2edb-b320-17cebba6e50f.png">


# 5.ソースを読んでみる

src/index.jsを開きます

以下の処理はpublic/index.htmlの`<div id="root"></div>`にAppコンポーネントをレンダリングしています。

```javascript
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
```

src/App.jsを開きます

AppクラスがReact.Componentを継承しReact コンポーネントになっています。React.Component サブクラスで必ず定義しなければならない唯一のメソッドは render() です。render() メソッドは変更が起こるたびに呼び出されます。

returnで返しているのはJSXです。classはclassNameと書かないといけないことに注意してください。

`export default App`でこのファイルのデフォルトとしてAppをexportしています。

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

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

```


# 6.コンポーネントを作る
目的：コンポーネントの作り方と使い方

作成したプロジェクトの中に移動してください。

```shell
cd todo
```

## 1) コンポーネントを入れるためのディレクトリを作ります。
```shell
mkdir -p src/components
```

## 2) componentsディレクトリにList.jsを作ります。

List.js

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

class List extends Component {
  render() {
    return (
      <table>
        <tbody>
          <tr><td style={{border: 1, borderColor: 'gray', borderStyle: 'solid', cursor: 'pointer'}}>掃除する</td></tr>
          <tr><td style={{border: 1, borderColor: 'gray', borderStyle: 'solid', cursor: 'pointer'}}>買い物</td></tr>
          <tr><td style={{border: 1, borderColor: 'gray', borderStyle: 'solid', cursor: 'pointer'}}>洗濯する</td></tr>
        </tbody>
      </table>
    );
  }
}

export default List;

```

## 3) App.jsに組み込み

```javascript
import React, { Component } from 'react';
import List from './components/List';

class App extends Component {
  render() {
    return (
      <List />
    );
  }
}

export default App;

```

次のように表示されます。
<img width="173" alt="chapter6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124660/ae2169a9-123b-6695-06c3-a7b9a8a43302.png">



# 7.stateを使う
目的：stateの使い方

stateはコンポーネントの内部で制御されるオブジェクトです。更新はsetStateにより非同期に更新されます。stateが更新されると再描画されます。

List.jsを次のように書き換えます。

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

class List extends Component {

  constructor(props) {
      super(props);
      this.state = {
        todos: [
          '掃除する',
          '買い物',
          '洗濯する'
        ]
      };
  }

  onClickAdd() {
    const newTodo = window.prompt("やることを入力してください", "");
    const todos = this.state.todos;
    todos.push(newTodo);
    this.setState({todos});
  }

  render() {
    return (
      <div>
        <div><button onClick={() => this.onClickAdd()}>追加</button></div>
        <table>
          <tbody>
          {this.state.todos.map((todo) => (
            <tr key={todo}><td style={{border: 1, borderColor: 'gray', borderStyle: 'solid', cursor: 'pointer'}}>{todo}</td></tr>
          ))}
          </tbody>
        </table>
      </div>

    );
  }
}

export default List;
```

追加でリストが増えると成功です。

# 8.propsを使う
目的：propsの使い方

propsはコンポーネントに属性として設定し値を渡すことができます。値だけでなく関数も渡すことができます。

## 1) componentsディレクトリにItem.jsを作ります。

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

class Item extends Component {

  render() {
    const {todo, onClickItem} = this.props;
    return (
      <tr onClick={() => onClickItem(todo)}><td style={{border: 1, borderColor: 'gray', borderStyle: 'solid', cursor: 'pointer'}}>{todo}</td></tr>
    );
  }
}

export default Item;

```

## 2) List.jsのrenderを次のように書き換えます。

Itemをimportしてください。

```javascript
import Item from './Item';
```

```javascript
  render() {
    return (
      <div>
        <div><button onClick={() => this.onClickAdd()}>追加</button></div>
        <table>
          <tbody>
          {this.state.todos.map((todo) => (
            <Item key={todo} todo={todo} onClickItem={(todo) => alert(todo)} />
          ))}
          </tbody>
        </table>
      </div>
    );
  }
```


# 9.詳細内容表示用のコンポーネントの追加

## 1) src/componentsディレクトリにContent.jsを追加します。

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

class Content extends Component {

  render() {
    return (
      <div style={{marginLeft: 50}}>
        <span>掃除をする</span>
      </div>
    );
  }

}

export default Content;

```

## 2) App.jsを次のように書き換えます。

```javascript
import React, { Component } from 'react';
import List from './components/List';
import Content from './components/Content';

class App extends Component {
  render() {
    return (
      <div style={{display:'flex'}}>
        <List />
        <Content />
      </div>
    );
  }
}

export default App;

```

Listコンポーネントで選択した内容をContentコンポーネントに表示させたいですが、このままではうまく行きません。

# 10.reduxとredux-sagaの導入
目的：redux・redux-sagaの導入方法と使い方

Reduxは、Reactのstate(状態)を管理をするためのフレームワークです。
redux-sagaとは

> redux-saga は、アプリケーションの副作用（つまり、データフェッチのような非同期のものや、ブラウザキャッシュへのアクセスのような不純なも> > の）を管理しやすく、実行効率が高く、テストが簡単で、障害処理を改善することを目的としたライブラリです。

redux-sagaの構成
<img width="391" alt="chapter10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124660/00494814-773b-0e85-d1f4-96ef5b0fb0e8.png">


## 1) ライブラリをインストールします
```
npm install redux react-redux redux-saga
npm install
```

## 2) actionの作成

```shell
mkdir -p src/actions
```

src/actions/index.js

```javascript
/**
 * Redux Actions
 */
export * from './TodoAppActions';

```

src/actions/TodoAppActions.js

```javascript
/**
 * Todo App Actions
 */
import {
    GET_TODOS,
    GET_TODOS_SUCCESS,
    GET_TODOS_FAILURE,
    ADD_TODO,
    ADD_TODO_SUCCESS,
    SELECT_TODO,
    SELECT_TODO_SUCCESS,
} from './types';

export const getTodos = () => ({
    type: GET_TODOS
});

export const getTodosSuccess = (response) => ({
    type: GET_TODOS_SUCCESS,
    payload: response
});

export const getTodosFailure = (error) => ({
    type: GET_TODOS_FAILURE,
    payload: error
});

export const addTodo = (todo) => ({
    type: ADD_TODO,
    payload: todo
});

export const addTodoSuccess = (todo) => ({
    type: ADD_TODO_SUCCESS,
    payload: todo
});

export const selectTodo = (todo) => ({
    type: SELECT_TODO,
    payload: todo,
});

export const selectTodoSuccess = (todo) => ({
    type: SELECT_TODO_SUCCESS,
    payload: todo,
});

```

src/actions/types.js

```javascript
export const GET_TODOS = 'GET_TODOS';
export const GET_TODOS_SUCCESS = 'GET_TODOS_SUCCESS';
export const GET_TODOS_FAILURE = 'GET_TODOS_FAILURE';
export const ADD_TODO = 'ADD_TODO';
export const ADD_TODO_SUCCESS = 'ADD_TODO_SUCCESS';
export const SELECT_TODO = 'SELECT_TODO';
export const SELECT_TODO_SUCCESS = 'SELECT_TODO_SUCCESS';

```

## 3) sagasの作成

```shell
mkdir -p src/sagas
```

src/sagas/index.js

```javascript
/**
 * Root Sagas
 */
import { all } from 'redux-saga/effects';

// sagas
import todoSagas from './Todo';

export default function* rootSaga(getState) {
    yield all([
        todoSagas(),
    ]);
}

```

src/sagas/Todo.js

```javascript

import { all, call, fork, put, takeEvery } from 'redux-saga/effects';

import {
    GET_TODOS,
    SELECT_TODO,
    ADD_TODO,
} from '../actions/types';

import {
    getTodosSuccess,
    getTodosFailure,
    selectTodoSuccess,
    addTodoSuccess,
} from '../actions';

const getTodosRequest = () => new Promise((resolve, reject) => {
  const todos = ['部屋の掃除', '買い物', '洗濯'];
  resolve(todos);
});

function* getTodosFromServer() {
    try {
        const response = yield call(getTodosRequest);
        yield put(getTodosSuccess(response));
    } catch (error) {
        yield put(getTodosFailure(error));
    }
}

function* addTodoToServer(action) {
  yield put(addTodoSuccess(action.payload));
}

function* selectTodoFromServer(action) {
  yield put(selectTodoSuccess(action.payload));
}

export function* getTodos() {
    yield takeEvery(GET_TODOS, getTodosFromServer);
}

export function* selectTodo() {
    yield takeEvery(SELECT_TODO, selectTodoFromServer);
}

export function* addTodo() {
    yield takeEvery(ADD_TODO, addTodoToServer);
}

export default function* rootSaga() {
    yield all([
        fork(getTodos),
        fork(selectTodo),
        fork(addTodo),
    ]);
}

```

[function*についてはこちら](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/function*)
[yieldについてはこちら](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/yield)

takeEvery: Actionがdispatchされるたびに起動させたいタスクを指定します
put: Actionをdispatchします


## 4) reducersの作成

```shell
mkdir -p src/reducers
```

src/reducers/index.js

```javascript

/**
 * App Reducers
 */
import { combineReducers } from 'redux';
import todoAppReducer from './TodoAppReducer';

const reducers = combineReducers({
   todoApp: todoAppReducer,
});

export default reducers;

```

src/reducers/TodoAppReducer.js

```javascript
/**
 * Todo App Reducer
 */

// action types
import {
    GET_TODOS,
    GET_TODOS_SUCCESS,
    GET_TODOS_FAILURE,
    ADD_TODO_SUCCESS,
    SELECT_TODO_SUCCESS,
} from '../actions/types';

// initial state
const INIT_STATE = {
    todos: [],
    selectedTodo: '',
};

export default (state = INIT_STATE, action) => {
    switch (action.type) {

        case GET_TODOS:
            return { ...state, todos: [] };

        case GET_TODOS_SUCCESS:
            return { ...state, todos: action.payload };

        case GET_TODOS_FAILURE:
            return {}

        case ADD_TODO_SUCCESS:
            const newTodos = [];
            state.todos.forEach((todo) => newTodos.push(todo));
            newTodos.push(action.payload);
            return { ...state, todos: newTodos };

        case SELECT_TODO_SUCCESS:
            return { ...state, selectedTodo: action.payload };

        default: return { ...state };

    }
}

```

## 5) storeの追加

src/index.js

```javascript

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

import { Provider } from "react-redux";
import createSagaMiddleware from 'redux-saga';
import { createStore, applyMiddleware } from "redux";

import reducers from './reducers';
import RootSaga from "./sagas";
const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  reducers,
  applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(RootSaga);

ReactDOM.render(
  <Provider store={store}>
     <App />
  </Provider>
,
  document.getElementById('root')
);

```

## 6) コンポーネントの修正
src/components/Content.js

```javascript
import React, { Component } from 'react';
import { connect } from 'react-redux';

class Content extends Component {

  render() {
    const {selectedTodo} = this.props;
    return (
      <div style={{marginLeft: 50}}>
        <span>{selectedTodo}</span>
      </div>
    );
  }

}

const mapStateToProps = ({ todoApp }) => {
	const { selectedTodo } = todoApp;
	return { selectedTodo };
}

export default connect(mapStateToProps, null)(Content);

```

src/components/Item.js

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

import { connect } from 'react-redux';
import { selectTodo } from '../actions';

class Item extends Component {

  render() {
    const {todo, selectTodo} = this.props;
    return (
      <tr onClick={() => selectTodo(todo)}><td style={{border: 1, borderColor: 'gray', borderStyle: 'solid', cursor: 'pointer'}}>{todo}</td></tr>
    );
  }
}

export default connect(null, { selectTodo })(Item);

```

src/components/List.js

```javascript
import React, { Component } from 'react';
import Item from './Item';

import { connect } from 'react-redux';
import { getTodos, addTodo } from '../actions';

class List extends Component {

  componentDidMount() {
    const { getTodos } = this.props;
    getTodos();
  }

  onClickAdd() {
    const { addTodo } = this.props;
    const newTodo = window.prompt("やることを入力してください", "");
    addTodo(newTodo);
  }

  render() {
    const {todos} = this.props;
    return (
      <div>
        <div><button onClick={() => this.onClickAdd()}>追加</button></div>
        <table>
          <tbody>
          {todos.map((todo) => (
            <Item key={todo} todo={todo} />
          ))}
          </tbody>
        </table>
      </div>

    );
  }
}

const mapStateToProps = ({ todoApp }) => {
	const { todos } = todoApp;
	return { todos };
}

export default connect(mapStateToProps, { getTodos, addTodo })(List);

```

# 11.フォームを利用する

## 1) src/componentsディレクトリにAddItem.jsを追加します。

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

import { connect } from 'react-redux';
import { addTodo } from '../actions';

class AddItem extends Component {

  constructor(props) {
    super(props);
    this.state = { todo: '' };
  }

  handleChange(event) {
    this.setState({ todo: event.target.value });
  }

  onClickAdd() {
    const { addTodo } = this.props;
    addTodo(this.state.todo);
  }

  render() {
    return (
      <div style={{display: 'flex'}}>
        <div><input type="text" value={this.state.todo} onChange={(event) => this.handleChange(event)} /></div>
        <div><button onClick={() => this.onClickAdd()}>追加</button></div>
      </div>
    );
  }
  
}

export default connect(null, { addTodo })(AddItem);
```

## 2) List.jsでAddItemコンポーネントを使用するように修正する

```javascript
import React, { Component } from 'react';
import Item from './Item';
import AddItem from './AddItem';

import { connect } from 'react-redux';
import { getTodos } from '../actions';

class List extends Component {

  componentDidMount() {
    const { getTodos } = this.props;
    getTodos();
  }

  render() {
    const {todos} = this.props;
    return (
      <div>
        <div><AddItem /></div>
        <table>
          <tbody>
          {todos.map((todo) => (
            <Item key={todo} todo={todo} />
          ))}
          </tbody>
        </table>
      </div>
    );
  }
}

const mapStateToProps = ({ todoApp }) => {
    const { todos } = todoApp;
    return { todos };
}

export default connect(mapStateToProps, { getTodos })(List);
```

# 12.関数コンポーネントを使用する

AddItemコンポーネントを次のように修正します。

```javascript
import React, { useState } from 'react';

import { connect } from 'react-redux';
import { addTodo } from '../actions';

function AddItem(props) {

  const [todo, setTodo] = useState('');

  const onClickAdd = () => {
    const { addTodo } = props;
    addTodo(todo);
  }
  return (
    <div style={{display: 'flex'}}>
      <div><input type="text" value={todo} onChange={(event) => setTodo(event.target.value)} /></div>
      <div><button onClick={() => onClickAdd()}>追加</button></div>
    </div>
  );
}

export default connect(null, { addTodo })(AddItem);
```

これまでのソースコードはGitHubに上げています。
https://github.com/yuichi0301/todo