7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React ハンズオン

Last updated at Posted at 2020-07-10

今日作成するアプリ

app.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プロジェクトを作成します。

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でブラウザが起動します)

chapter4.png

5.ソースを読んでみる

src/index.jsを開きます

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

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しています。

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.コンポーネントを作る

目的:コンポーネントの作り方と使い方

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

cd todo

1) コンポーネントを入れるためのディレクトリを作ります。

mkdir -p src/components

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

List.js

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に組み込み

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

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

export default App;

次のように表示されます。
chapter6.png

7.stateを使う

目的:stateの使い方

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

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

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を作ります。

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してください。

import Item from './Item';
  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を追加します。

import React, { Component } from 'react';

class Content extends Component {

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

}

export default Content;

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

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の構成
chapter10.png

1) ライブラリをインストールします

npm install redux react-redux redux-saga
npm install

2) actionの作成

mkdir -p src/actions

src/actions/index.js

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

src/actions/TodoAppActions.js

/**
 * 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

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の作成

mkdir -p src/sagas

src/sagas/index.js

/**
 * 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


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*についてはこちら
yieldについてはこちら

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

4) reducersの作成

mkdir -p src/reducers

src/reducers/index.js


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

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

export default reducers;

src/reducers/TodoAppReducer.js

/**
 * 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


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

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

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

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を追加します。

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コンポーネントを使用するように修正する

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コンポーネントを次のように修正します。

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

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?