LoginSignup
1
2

More than 1 year has passed since last update.

今から学ぶReduxの基礎(実践編)

Posted at

Reduxを使ってみる

Reduxを使って以下のようなカウントアップする簡単なアプリケーションを作成していきます。
カウントを行うコンポーネントが2つ存在し、propsで受け渡すことなく状態を共有しています。
redux-sample.gif

事前準備

サンプルアプリの作成とReduxのインストールから行います。

// アプリケーション作成
npx create-react-app redux-practice

// Reduxインストール
yarn add redux react-redux

Redux Hooks

まずはHooksを使用した書き方で作成します。
アプリケーションの名前は「redux-practice」とします。
以下のようなファイル構成でファイルを作成します。

redux-practice
├── public
└── src
    ├── actions
    │   └── index.js
    ├── components
    │   ├── Another.js
    │   └── App.js
    └── reducers
    │   └── index.js
    └── index.js

次に以下のようにactions/index.jsを編集していきます。
今回はカウンターアプリなので「increment」と「decrement」というアクションを作成しています。

actions/index.js
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

export const increment = () => {
  return {type: INCREMENT}
}

export const decrement = () => {
  return {type: DECREMENT}
}

続いて、reducers/index.jsを以下のように編集します。
こちらは、先ほど定義したActionと最新のstateを自動的に受け取って新しいstateを返しています。

reducers/index.js
import { INCREMENT, DECREMENT } from '../actions'
const initialState = { value: 0 }

const count = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return { value: state.value + 1 }
    case DECREMENT:
      return { value: state.value - 1 }
    default:
      return state
  }
}

export default

次にcomponents/App.jsで表示部分を以下のように作成していきます。
以下では「+」ボタンが押された時に「increment」、「-」ボタンが押された時に「decrement」というイベントを発行するようにしています。

components/App.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { increment, decrement } from '../actions'

const App = () => {
  const value = useSelector((state) => state.value)
  const dispatch = useDispatch();
  return (
    <>
      <div>count:{value}</div>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </>
  );
}

export default App

最後に他のコンポーネントからstateを参照してみましょう。
components/Another.jsでuseSelectorを使うことで、stateの値を参照することができます。

components/Another.js
import React from 'react'
import { useSelector } from 'react-redux'

const Another = () => {
  const value = useSelector((state) => state.value)

  return (
      <div>AnotherComponentCount:{value}</div>
  );
}

export default Another

ReduxがHooksに対応する前の書き方

先ほどはHooksを使用した書き方を紹介しましたが、
古いコードではHooksを使用しない書き方を使っていることもあるので、
先ほどのコードを古いコードで書き換えてみます。

components/App.js
import React from 'react'
import { connect } from 'react-redux'
import { increment, decrement } from '../actions'

const App = (props) => {
  return (
    <>
      <div>count:{props.value}</div>
      <button onClick={props.increment}>+</button>
      <button onClick={props.decrement}>-</button>
    </>
  );
}

// stateから必要な情報をコンポーネントにマッピングする関数
const mapStateToProps = state => ({ value: state.count.value })

// dispatch関数をコンポーネントにマッピングする関数
const mapDispatchToProps = dispatch => ({
  increment: () => dispatch(increment()),
  decrement: () => dispatch(decrement())
})

// 指定したコンポーネントのpropsにstateを混ぜ込む
export default connect(mapStateToProps, mapDispatchToProps)(App)
components/Another.js
import React from 'react'
import { connect } from 'react-redux'

const Another = (props) => {
  return (
      <div>AnotherComponentCount:{props.value}</div>
  );
}

// stateから必要な情報をコンポーネントにマッピングする関数
const mapStateToProps = state => ({ value: state.count.value })

// 指定したコンポーネントのpropsにstateを混ぜ込む
export default connect(mapStateToProps)(Another)

connectという関数を使う必要があるため、少し冗長になってしまいます。。。

Redux Toolkitを使う

ここまでReduxのコードを見てきて感じたかもしれませんが、Reduxを使うとコード量が増えます。
その課題を解決するのがRedux Toolkitです。
実際にRedux Toolkitでコードを書いていきましょう。

ファイル構成は以下

redux-practice
├── public
└── src
    ├── components
    │   ├── Another.js
    │   └── App.js
    └── counter
    │   └── counter.js
    └── index.js

まず、counter/counter.jsを以下のように編集します。

counter/counter.js
import { createSlice } from '@reduxjs/toolkit'

const initialState = { value: 0 }

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => ({...state, value: state.value + 1}),
    decrement: (state) => ({...state, value: state.value - 1})
  }
})

Redux Toolkitで提供されているcreateSlice関数を使用すると、ActionとReducerをまとめて記述することができます。

components/App.jsとcomponents/Another.jsは以下のように編集します。
(ここら辺は以前とほぼ同じです)

components/App.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { counterSlice } from '../counter/counter';

const App = () => {
  const value = useSelector((state) => state.value)
  const dispatch = useDispatch();
  const { increment, decrement } = counterSlice.actions;
  return (
    <>
      <div>count:{value}</div>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </>
  );
}

export default App
components/Another.js
import React from 'react'
import { useSelector } from 'react-redux'

const Another = () => {
  const value = useSelector((state) => state.value)

  return (
      <div>AnotherComponentCount:{value}</div>
  );
}

export default Another

最後にindex.jsを編集して終わりです。

index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { configureStore } from '@reduxjs/toolkit'
import App from './components/App'
import Another from './components/Another'
import {counterSlice} from './counter/counter'

const store = configureStore({reducer: counterSlice.reducer})

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

useReducerとuseContextを使って、Reduxのようなグローバルステートを実現する

useReducerとuseContext

これまでReduxを使用してグローバルステートを実現していましたが、
HooksのuseReducerとuseContextを使ってReduxのようにグローバルステートを実現することができます。

ではuseReducerとuseContextでコードを書いていきましょう。

ファイル構成は以下

redux-practice
├── public
└── src
    ├── actions
    │   ├── index.js
    ├── components
    │   ├── Another.js
    │   └── App.js
    └── counter
    │   └── counter.js
    └── index.js

まず、actions/index.jsは以下のように編集します。
(最初のコードと同じです)

actions/index.js
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

export const increment = () => {
  return {type: INCREMENT}
}

export const decrement = () => {
  return {type: DECREMENT}
}

続いて、counter/counter.jsを以下のように編集します。
(こちらも最初のコードとほぼ同じです)

counter/counter.js
export const counterReducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {value: state.value + 1}
    case "DECREMENT":
      return {value: state.value - 1}
    default:
      return state
  }
}

次にcomponents/App.jsを以下のように編集します。

components/App.js
import React, { createContext, useReducer } from "react";
import { counterReducer } from '../counter/counter'
import Another from './Another'
import { increment, decrement } from "../actions";

export const CounterContext = createContext();

const App = () => {
  const initialState = { value: 0 }
  const [state, dispatch] = useReducer(counterReducer, initialState);
  return (
    <>
      <CounterContext.Provider value={{state, dispatch}}>
        <div>count:{state.value}</div>
        <button onClick={() => dispatch(increment())}>+</button>
        <button onClick={() => dispatch(decrement())}>-</button>
        <Another/>
      </CounterContext.Provider>
    </>
  );
}

export default App

ここでのポイントは以下の3つです。

  • useReducerの引数にReducerとstateの初期値を渡し、その返り値として最新のstateとdispatchを受け取っている

  • CounterContextという定数を定義し、CounterContext.Providerのvalue属性にグローバルで扱いたいstateを設定

  • CounterContext.Providerコンポーネントに囲まれているコンポーネントは、設定したstateを使用できる

上記のようにReact Hooksを使えばReduxのように状態管理を行えるようになります。

1
2
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
2