LoginSignup
0
0

More than 1 year has passed since last update.

今更 React Redux に入門

Last updated at Posted at 2021-08-03

前々からReact Reduxを勉強していて、いまいちピンと来ていなかったのですが、ある記事を見てピンと来たので、入門者ながらまとめておきます。

お世話になったサイト
https://reffect.co.jp/react/react-redux-for-beginner#Redux-2

この記事の対象者

・JSXやReactとそのprops,stateくらいなら分かる
・関数コンポーネントで学んでいる
・いろんな記事でReduxみたけどよくわからない

React Reduxとは?

Reactの状態管理ライブラリです。
Reactで状態を管理するときはuseStateを使いますが、useStateで管理できるのはそのコンポーネント内のstateだけです。つまり、他のコンポーネントからアクセスしたり書き換えたりというのはできません。
そこで様々なコンポーネントでstateを共有できるようにしたのがReduxです。

Reduxの予備知識

様々なコンポーネントでstateを共有するために、ReduxではStoreと呼ばれる倉庫を用意します。
各コンポーネントは倉庫の中のstateを読んだり、倉庫の中のstateを書き換えたりします。
つまり、stateを共有するためにStoreをあらかじめ作っておく必要があります
なので入門者はまず次の内容を学ぶ必要があります。

  • 倉庫(Store)の作り方
  • 倉庫内のstateのアクセスの仕方
  • 倉庫内のstateの書き換え方

この記事では以上3点について解説します。

準備

まずはReact/Reduxをインストールする必要があるので、それぞれインストールしましょう。

(前提としてNodeをインストールする必要があります)

Reactのインストール

npx create-react-app Prac-Redux

Reduxのインストール

npm install redux react-redux

これで準備OKです。開発用ローカルサーバを起動するために、npm startしましょう。

cd Prac-Redux
npm start

ここまで来たらファイル構成が次の通りになっているはずです。

Prac-Redux
 -node_modules
 -public
  -index.htmlなどいろいろ
 -src
  -index.jsなどいろいろ
 package.jsonなどいろいろ

またこの講座で書くコードはすべてsrcフォルダ内のファイルだけですのでそれ以外はいじらないようにしてください。

最後に、お使いのテキストエディタを開いておいてください。

今回サンプルとして作るもの

簡単なカウンターを作ります。+ボタンを押すとカウントが
1ふえて、-ボタンを押すとカウントが1へります。このカウンターのカウント数をReduxを使って管理します。

qiita-redux-intro.PNG

Storeの作り方

倉庫を作るにはcreateStore関数を利用します。

srcフォルダ内にstoreフォルダを作り、

Prac-Redux
 -src
  -store

その中にindex.jsを作成します。

Prac-Redux
 -src
  -store
   -index.js

作成したら、次のコードを書いてください。

src/store/index.js
//注意 まだ実行できません
import { createStore } from "redux";

const store = createStore(/*Reducerを指定*/);

export default store;

createStore関数を使って倉庫を作成しました。
ですが、createStore関数にはReducerを渡さないといけないというルールがあります。Reducerとは簡単に言えば倉庫内のStoreを作る関数です(がとりあえずは分からなくてもOKです)。また、storeはあとでいろいろなコンポーネントからアクセスしたいので他ファイルからアクセスできるように export default しておきましょう。

Reducerを指定しましょう。

src/store/index.js
//注意 まだ実行できません
import { createStore } from "redux";

const initialState = {
    count:
};
const reducer = (state=initialState, action)=>{
    const newState = {
        ...state,
    } ;
    return newState ;
};

const store = createStore(reducer);

export default store;

先ほどReducerは倉庫内のStoreを作る関数といいましたが、正確には 古いstateをもとに新しいstate(newState)を作る関数です。
上の例では引数にstate(古いstate)とaction(後述)を受け取り(state=initialStateとなっているため、もしstateがundefindなら(==一番最初なら)上で定義したinitialStateを設定して受け取ります)、
新しいstate(newState)を作ってそれをreturnしています。
またスプレット構文を使ってnewStateを作っています。

スプレット構文ってなに?

例えば次のようにオブジェクトを定義します。

スプレット構文の例
const oldObj = {
    name:"TBSten",
    old:100
} ;
const newObj = {
    ...oldObj,
    friends:"none..."
}

こうすると定義したオブジェクトの中身はそれぞれ

oldObj = {
    name:"TBSten",
    old:100
}
newObj = {
    name:"TBSten", //oldObjから引き継がれる
    old:100,       //oldObjから引き継がれる
    friends:"none..."
}

となります。
スプレット構文(上の例だと...oldObj)を使うと、
新たなオブジェクトを定義する際にあるオブジェクト(oldObj)からプロパティを引き継いで新しいオブジェクトを作ることが出来ます。
また、次のように、古いオブジェクトにも新しいオブジェクトにも同じプロパティがある場合は、新しいオブジェクトで値が更新されます。

const oldObj = {
    name:"TBSten",
    old:100
} ;
const newObj = {
    ...oldObj,
    old:30
}
/*↓↓↓↓↓↓↓↓↓
newObj = {
    name:"TBSten",
    old:30         //新しいオブジェクトのプロパティが優先
}
*/

いまのままではstateとnewStateは同じプロパティを持ちますが、あとで...state,の後にいろいろ追加してnewStateを作ります。

Store内のstateのアクセスの仕方

Store内の値にアクセスするには、ProvideruseSelectorを使います。
Storeで定義したstateを共有する範囲(どのコンポーネントに共有するか)を決めるために、共有したいコンポーネントをProviderコンポーネントで囲います。今回はすべてのコンポーネント内で共有したいので、src/index.js内でAppコンポーネントを囲います。

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

import App from './App';
import { Provider } from "react-redux"
import store from "./store/index";

ReactDOM.render(
  <Provider store={store}>    //AppコンポーネントをProviderで囲う
    <App />
  </Provider>,
  document.getElementById('root')
);

またProviderには先ほど作ったstoreをstoreプロップスとして渡しておきましょう。

次にApp.jsの中を整えます。デフォルトでいろいろ書いてありますが、全部削除して以下のコードを書いてください。

src/App.js
import React from "react" ;
import { useSelector } from "react-redux";


const App = ()=>{
  const count = useSelector((state)=>state.count) ;

  return (
    <div>
      <h1>Reduxテスト</h1>
      NOW:{count}
      //あとでここにボタン設置
    </div>
  );
};

export default App ;

Storeのstateを取得するには、 useSelector を使います。
useSelectorには引数に Storeのstateから取得したいデータを取得する 関数を渡します。
上の例では(state)=>state.countのstateがStoreのstate,state.countが取得したいデータです。

このように、

  • Providerで囲んで
  • useSelector関数で取得

することでStoreの値を取得することが出来ます。

倉庫内のstateの書き換え方

今度はStoreのstateを書き換えてみましょう。
実はStoreの書き換えは少し厄介で、 dispatchaction という用語が出てきます。

  1. 各コンポーネントが値を書き換えたいとき(ボタンをクリックした時等)にdispatch(action)を呼ぶ。
  2. Reducerはdispatch(action)が呼ばれると、actionの情報をもとにstoreのstateの内容を書き換える。

という順番で行われます。1. は書き換えたいコンポーネント内で、2. はsrc/index.jsのreducer内で記述します。

これをマクドナルドに例えるならば、

  1. 表のスタッフ(各コンポーネント)が注文が入った時に、「チーズバーガーを1つ」という注文の情報(action)を、注文を裏方(Reducer)に伝える(dispatch)
  2. 裏方(Reducer)は注文(action)をもとにチーズバーガーをつくって在庫からチーズ(Storeのstate)を減らす。

といったところでしょうか。

それぞれ見ていきましょう。

1. コンポーネントでdispatch(action)する

今回はボタンを押すと、Storeのstate(今回はcount)が1上がるようにしたいので、まずボタンをAppコンポーネントに追加します。

src/App.js
import React from "react" ;
import { useSelector } from "react-redux";


const App = ()=>{
  const count = useSelector((state)=>state.count) ;

 return (
    <div>
      <h1>Reduxテスト</h1>
      NOW:{count}
            <div>
                <button> 1 ふやす </button>  //ここを追加

            </div>
    </div>
  );
};

export default App ;

ボタンを押したときに何かしたいのでbuttonタグにonClickを追加してhandleClickを登録します。

src/App.js
import React from "react" ;
import { useSelector } from "react-redux";


const App = ()=>{
  const count = useSelector((state)=>state.count) ;

  const handleClick = ()=>{
    //ここでStoreのstateを1ふやす
  };
  return (
    <div>
      <h1>Reduxテスト</h1>
      NOW:{count}
            <div>
                <button onClick={handleClick}> 1 ふやす </button>

            </div>
    </div>
  );
};

export default App ;

あとはhandleClick内でStoreの状態を書き換えれればよいです。
そのためにまず、useDispatch関数を使って(インポートを忘れずに...)

src/App.js
import React from "react" ;
import { useSelector,useDispatch } from "react-redux";


const App = ()=>{
  const count = useSelector((state)=>state.count) ;
  const dispatch = useDispatch();

  const handleClick = ()=>{
    //ここでStoreのstateを1ふやす
  };
  return (
    <div>
      <h1>Reduxテスト</h1>
      NOW:{count}
            <div>
                <button onClick={handleClick}> 1 ふやす </button>

            </div>
    </div>
  );
};

export default App ;

useDispatch関数の戻り値(dispatch)は関数です。
この関数にactionを渡すことで、storeに「state書き換えんといかんぞー詳しくはactionを見てくれ」と教えることが出来ます。

actionは次のような構造になっているただのオブジェクトです。

action
{
  type:/*state変更のタイプを指定する文字列*/,
  payload:/*その詳細情報*/
}

先ほどのマクドナルドの例でいえば、

{
  type:"注文",
  payload:{
    "商品名":"チーズバーガー",
    "数量":1
  }
}

といった感じになります。

今回はStoreに「カウントを増やしてくれー増やす数は1だぞー」と教えたいので、

action
{
  type:"count/add",
  payload:1
}

というactionを指定するといいでしょう。
なお、typeは必ず指定する必要があるみたいです。(なんでかは知らん)

なのでhandleClickの中は次のようになります。

handleClickの中身
const action = {
  type:"count/add",
  payload:1
} 
dispatch(action);

ここまででApp.jsの中身は次のようになっているはずです。

src/App.js
import React from "react" ;
import { useSelector,useDispatch } from "react-redux";


const App = ()=>{
  const count = useSelector((state)=>state.count) ;
  const dispatch = useDispatch() ;

  const handleClick = ()=>{
    const action = {
      type:"count/add",
      payload:1
    } ;
    dispatch(action);
  }
  return (
    <div>
      <h1>Reduxテスト</h1>
      NOW:{count}
            <div>
                <button onClick={handleClick}> 1 ふやす </button>
            </div>
    </div>
  );
};

export default App ;

2. ReducerでStoreのstateを更新する

先ほどで「表のスタッフが裏方に注文が入ったことをつたえる」ことはできました。
次は
裏方のスタッフがチーズバーガーを作って在庫数を減らす
ことをしてみましょう。
これをReduxの言葉で置き換えると
ReducerがStoreのstateを変更する
となります。

Reducerはこの記事の最初の方でsrc/store/index.jsに作ったreducerのことです。

src/store/index.js
............
const reducer = (state=initialState, action)=>{
    const newState = {
        ...state,
    } ;
    return newState ;
}
............

実はreducerのactionとは 先ほど1でtypeやpayloadというプロパティを入れたactionオブジェクトの事です。
なので、actionの情報に基づいて、newStateを変更していけばいいんです。早い話が次のようになります。

src/store/index.js
import { createStore } from "redux";


const initialState = {
    count : 0,
} ;
const reducer = (state=initialState, action)=>{
    const newState = {
        ...state,
    } ;
    switch(action.type){
      case "count/add":         //もしaction.typeが"count/add"なら
        newState.count += 1 ;   //newStateのカウントを1ふやす
            break;
    }
    return newState ;
}


const store = createStore(reducer);

export default store;

これで裏方の動きも実装することが出来ました。

ブラウザで確認してみてください。ボタンをクリックするとカウントが増えているはずです。

actionはaction creatorでつくろう

stateの変更は

const action = {
  type:"count/add",
  payload:1
} ;
dispatch(action);

で行うことが出来ますが、countを1増やしたいだけなのに5行も使っていたらコードとしてわかりにくくなってしまいます。なのでactionを作成する関数を用意しておきます。この関数を action creator といいます。

はじめに飢えのコードをaction creatorを使って書き換えた結果をお見せします。

dispatch(countup(1));

さきほど5行も必要だったコードがcountup関数(action creator)を使うことでたったの1行になりました。これなら毎回何回もactionを作って dispatch して...と長いコードを書かなくて済みますね。ではcountup関数を作ってみましょう。 action creator は src/actionCreator.js に作ることにします。

src/actionCreator.js
export function countup(upper){
  return {
    type:"count/add",
    payload:upper,
  } ;
}

countup関数はupper(countを増やす分)をもとにカウントを増やすためのactionを返す関数です。
つまりcountup関数を呼び出す1行を書くだけでactionを作成することが出来ます。
後はこれをdispatchするだけで済むというわけです。

カスタムフックにまとめよう

以上の通り、

stateの読み取りは

const count = useSelector((state)=>state.count) ;

stateの変更は

dispatch(countup(1));

で行うことが出来ます。

ここで考えてほしいのは count を読み取るときは大体 count を変更する処理も使う ことです。今はstateがcountの1つだけなのであまり困りませんが、あとでstateを追加していったり、stateがオブジェクトになったりすると、下のようにuseSelectorの中に何を指定すればいいのかわからなくなったりどのaction creatorを使えばいいかわからなくなったりします。

const count = useSelector( (state)=>/* state.count だっけ? state.top.countだっけ?*/ ) ;

dispatch( /*action creatorなんだっけ?*/ ) ;

これを次のようにuseStateみたく書けたらいいと思いませんか?

const [count, countup] = useCount();
// ↓↓↓
//アクセスしたいときは count
//1増やしたいときは countup(1)

ということでuseCount関数(カスタムフック)を作ってみましょう。hooks.jsに記述することにします。

src/hooks.js
export function useCount(){
  const count = useSelector((state)=>state.count) ;
  function up(){
    dispatch(countup(1));
  }
  return [
    count,
    up,
  ] ;
}
0
0
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
0
0