31
21

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 5 years have passed since last update.

『Redux』を用いて状態管理をしてみよう!

Last updated at Posted at 2019-04-30

#概要
前回の記事ではReactのみを用いて状態遷移を行いましたが、今回はReactJSが扱うUIのstate(状態)を管理をするためのフレームワークである__『Redux』__を用いて、前回と同様の機能を実装したいと思います。

前回の記事
『state』を用いて、入力された文字数をカウントしてみよう!

#パッケージのインストール
まずは、必要なパッケージのインストールを行いましょう!下記コマンドでreduxreact-reduxをインストールできます。

$ yarn add redux react-redux

#なぜReduxが必要なのか
早速コードを書いていきたいのですが、まずはなぜ__『Redux』__というものが開発されたのかを調べたいと思います。公式のページをGoogle翻訳で訳して簡単にまとめたものを以下に載せます。

JavaScriptで開発されるシングルページアプリケーションが複雑になるにつれて、管理しなければいけない状態(state)の数が増大していき、新機能開発やバグ修正などがとても大変になります。その複雑さは『変化(mutation)』と『非同期性(asynchronicity)』という2つの概念を混在させていることにより起こるものです。Reactのようなライブラリは、非同期と直接DOM操作の両方を削除することによって、ビューレイヤでこの問題を解決しようとします。しかし、状態(state)の管理はユーザーに任せられたままでした。__『Redux』__はその状態(state)管理の複雑さを解消するために開発されました。

公式ページより

#Action
では実際にコードを見ながら理解を進めていきましょう!
まずは__『Action』__からです!

『Action』:今から『stateをこのように変化させます』といった情報を持っているオブジェクトのことです。

そのオブジェクトの中で、『type』というKeyとそのKeyに対応する値を持つのが『Action』の特徴です。その『type』の値はユニークなものでないといけません。『type』以外は自由に定義して大丈夫です。

{
    type: TEXTCHANGE,
    textValue: textValue,
    textLength: textLength,
}

では続いて、作成したActionを返す関数__『Action Creator』__を書いていきましょう!

src/actions/index.js
export const TEXTCHANGE = 'TEXTCHANGE';

export const textChange = (textValue, textLength) => {
  return {
    type: TEXTCHANGE,
    textValue: textValue,
    textLength: textLength,
  };
};

TEXTCHANGEはこの後のReducerでも使うので、複数箇所で活用されるデータについては1箇所で定義してそれを再利用しましょう!

#Reducer
『Reducer』:Actionが発生した時に、そのActionに組み込まれているtypeに応じて、状態をどう変化させるのかというのを定義したものです。

実際にコードを見てみましょう!

src/reducers/index.js
import {combineReducers} from 'redux';
import text from './text';

export default combineReducers({text});

//複数のreducerを持った場合
//combineReducers({text, foo, baz});

src/reducers/index.jsではアプリケーション内で実装される__『Reducer』を1つにまとめる役割があります。今回は実装する『Reducer』が1つしかないのでcombineReducers({text})となっておりますが、まとめる『Reducer』__が複数個ある場合はcombineReducers({text, foo, baz});のようにcombineReducersにまとめれば大丈夫です!

src/reducers/text.js
import {TEXTCHANGE} from '../actions';

const initialState = {textLength: 0, textValue: 'initial value'};

export default (state = initialState, action) => {
  switch (action.type) {
    case TEXTCHANGE:
      return {textValue: action.textValue, textLength: action.textLength};
    default:
      return state;
  }
};

src/reducers/text.jsではstateとactionから新しいstateを作成してreturnで返します。ここではstateを更新しているのではなく、新しいstateを作成しているというところがポイントです!

#Store
『Store』:アプリケーション内のstate(状態)を保存しているところです。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {Provider} from 'react-redux';

import './index.css';
import reducer from './reducers';
import App from './components/App';
import * as serviceWorker from './serviceWorker';

const store = createStore(reducer);

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

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

ここでは、Reducerをもとに『store』を作成します。まずは『store』を作成するための関数であるcreateStoreをreduxパッケージからimportしましょう。そしてimportしたreducerをcreateStoreの引数に使うことで、『store』を定義します。

次に、作成した『store』を、全Componentに渡すための機能を持つProviderという特殊なComponentをreact-reduxからimportしましょう!そして各Componentを<Provide store={store}></Provider>でラップすることで『store』のデータを渡すことができるようになります。

#connect
connect関数を用いて、今まで作成してきたstateやActionとComponentとの関連付けを行なって、viewのイベントで状態を遷移させて遷移後の状態を画面に再描画しましょう。

src/components/App.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {textChange} from '../actions';

class App extends Component {
  render() {
    const props = this.props;
    console.log(props);

    return (
      <React.Fragment>
        <div>文字数: {props.textLength}</div>
        <textarea
          type="text"
          onChange={e => props.textChange(e.target.value, e.target.value.length)}
        />
      </React.Fragment>
    );
  }
}

const mapStateToProps = state => {
  return {
    textValue: state.text.textValue,
    textLength: state.text.textLength,
  };
};

const mapDispatchToProps = dispatch => {
  return {
    textChange: (textValue, textLength) =>
      dispatch(textChange(textValue, textLength)),
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

stateとactionをComponentに結びつけるためにconnect関数をimportし、Componentの名前であるAppを引数にとり、mapStateToPropsmapDispatchToPropsに結びつけます。

mapStateToPropsはstateからComponentに必要な情報を抜き出して、propsとしてマッピングする機能を持つ関数です。引数にはstateを取って、どういうオブジェクトをpropsとして対応させるのかといったことを返します。

あるactionが発生した時にreducerにtypeに応じた状態遷移を実行させるための関数がdispatchになります。mapDispatchToPropsでは、このdispatch関数を引数においてこのComponentにおいて必要なdispatch関数を宣言します。今回はtextが入力された際に、入力された文字と文字数を表示したいので、textChangeをkeyにtextChange関数を引数に持つdispatch関数を値に定義した、mapDispatchToPropsを作成します。

#最後に
以上で実装は終わりです。前回の記事と同様のものがブラウザにレンダリングされていると思います。

ezgif.com-optimize.gif

次回は今まで学習してきた、ReactとReduxを使ってTodoアプリケーションを作成する方法をまとめたいと思います!

#リファレンス

31
21
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
31
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?