1
1

More than 3 years have passed since last update.

Reduxについて調べて、実際に触ってみた

Last updated at Posted at 2020-04-16

はじめに

以下の2つの記事をめっちゃ参考にしました。

何ができる?

Reactのstateを一元管理することができる

登場人物

  • ActionCreator
    • Actionを作成するメソッド
  • Action
    • 実行する処理
  • Store
    • State置き場
  • State
    • データ
  • Reducer
    • stateを更新するメソッド

処理の流れ

  • ユーザーが送信ボタン等をクリックする
  • ActionCreatorがActionを作成する
  • ActionをStoreに送る(dispatchする)
  • 受け取ったActionと現在のStateをReducerに渡す
  • 新しいStateを作成する
    • 受け取ったActionによってStoreの扱い方を判断する
    • 元のStateを改変するのではなく、新しいStateを作る
  • 古いStateを破棄して、新たに作成されたStateを保存する

スクリーンショット 2015-11-30 1.13.59.png

以下の記事から拝借しました。
Redux入門【ダイジェスト版】10分で理解するReduxの基礎

3原則

  • Single source of truth
    • アプリケーション内でStoreは1つだけ。Stateは単独のオブジェクトとしてStoreに保持される。
  • State is read-only
    • Stateを直接変更することはできず、actionをStoreへdispatchすることでしかStateは変更できない。
  • Mutations are written as pure functions
    • Stateを変更する関数(Reducer)はpureな関数にする。

Reduxを使ったTODOアプリを作ってみる

セットアップ

$ create-react-app react-redux-todo
$ cd react-redux-todo
$ npm i redux react-redux redux-logger
$ rm -rf src
$ mkdir src
$ cd src
$ mkdir actions components containers reducers
$ touch actions/Todo.js components/Todo.js containers/Todo.js reducers/Todo.js createStore.js App.js index.js
$ cd ..

Storeを作成

src/createStore.js
import { createStore as reduxCreateStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import { todoReducer } from "./reducers/Todo";

export default function createStore() {
  const store = reduxCreateStore(
    //複数のReducerを扱うために、combineReducerを利用
    combineReducers({
      todo: todoReducer,
    }),
    applyMiddleware(
      logger,
    )
  );

  return store;
}

Reducer(仮)を作成

src/reducers/Todo.js
export const todoReducer = (state = {}) => state;

ReactにReduxを組み込む

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

import { Provider } from 'react-redux';
import createStore from './createStore';

const store = createStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>, 
  document.getElementById('root')
);
  • createStorereact-reduxをimport
  • createStoreでstoreを生成
  • storeをProviderに渡し、Appコンポーネントをラップ

次にApp.jsのひな形を作成。

src/App.js
import React from 'react';

function App() {
  return (
    <div>
      Hello World      
    </div>
  );
}

export default App;
$ npm start

とりあえずここまででエラーが発生していないことを確認

TODOコンポーネントを作成

ひとまず入力エリア、ボタン、リストを作っておきます。

src/components/Todo.js
import React from 'react';

export default class Todo extends React.Component {
  render() {
    return (
      <div>
        <input type="text" />
        <button>追加</button>
        <ul>
          <li>TODO1</li>
          <li>TODO2</li>
        </ul>
      </div>
    );
  }
}

そしてApp.jsで読み込みます。

src/App.js
import React from 'react';
import Todo from './components/Todo';

function App() {
  return (
    <div>
      <Todo />   
    </div>
  );
}

export default App;

Actionを定義

src/actions/Todo.js
export const addTodo = (todo) => {
  return { 
    type: 'ADD_TODO',
    payload: { todo: todo }
  };
}
  • typeが処理のキーで、全てのactionに必ず1つ設定されています
    • どのreducerを使うかの判断材料になります
  • payloadは処理に使うパラメーターです

Reducerを修正

src/reducers/Todo.js
const initialState = {
  todoList: []
}

export const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      const todo = action.payload.todo;
      const newState = Object.assign({}, state);
      newState.todoList.push(todo);
      return newState;
    default:
      return state;
  }
};
  • 現在のstateを定義
  • 引数に現在のstateと、受け取ったactionをとったメソッドを作成
  • action.typeに応じて処理を切り分ける
  • Object.assignメソッドで現在のstateを複製
  • 複製したstateに値を追加して返す
    • 元のstateの値を直接編集することはできない点に注意

Containerを定義

コンテナを定義して、reduxとコンポーネントを結びつけます

src/containers/Todo.js
import { connect } from 'react-redux';
import * as actions from '../actions/Todo';
import Todo from '../components/Todo';

const mapStateToProps = state => {
  return {
    todo: state.todo,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addTodo: (todo) => dispatch(actions.addTodo(todo)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Todo)
  • mapStateToPropsメソッドで、必要なStateをコンポーネントのpropsから参照できるようにします
  • mapDispatchToPropsメソッドで、actions/Todo.jsで定義したアクションをコンポーネントのpropsから参照できるようにします
  • connect関数でTodoコンポーネントとの紐付けを行います

最後に、App.jsでコンポーネントをimportしていた部分を、コンテナをimportするように書き換えます。

src/App.js
- import Todo from './components/Todo';
+ import Todo from './containers/Todo';

コンポーネントからStateを参照

コンテナからマッピングされて使えるようになったstatedispatch用のpropsを使うように修正します。

src/components/Todo.js
import React from 'react';

export default class Todo extends React.Component {
  state = {
    todo: ''
  }

  render() {
    //console.log(this.props);
    const list = this.props.todo.todoList.map((todo, index) => <li key={index}>{todo}</li>)

    return (
      <div>
        <input type="text" onChange={elm => this.setState({ todo: elm.target.value })} />
        <button onClick={() => this.props.addTodo(this.state.todo)}>追加</button>
        <ul>
          {list}
        </ul>
      </div>
    );
  }
}
  • storetodoListから現在のリストの内容を受け取ってリストを作成→表示
  • onChange={elm => this.setState({ todo: elm.target.value })}でテキスト変更時に入力内容をローカルのstateに反映
  • 「追加」ボタンをクリックした時に、actionsで定義したaddTodoメソッドをdispatch
    • 引数にローカルのstateを渡す
  • createStore.jsredux-loggerというミドルウェアをインポートしているので、storestateが変更されるたびコンソールにログが出力される

思ったことなど

  • ActionCreatorはこの仕組みでいうどこの部分?
    • src/actions/Todo.js内で定義している関数をActionCreatorと呼ぶらしい
  • reducer中、const initialState = {todoList: []}でからの配列を定義しているけれど、本来はここにDBから取ってきた値をセットするはず
  • 同じくreducer内、actionを受け取った後に発火する関数の中で、サーバーにデータを送信してDBの更新を試みるはず
  • あくまで他のコンポーネントと値をやりとりする時にstoreを経由しないといけないって話なので、自分のコンポーネント内でstateを変更することはできる
1
1
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
1
1