前々から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を使って管理します。
Storeの作り方
倉庫を作るにはcreateStore
関数を利用します。
src
フォルダ内にstore
フォルダを作り、
Prac-Redux
-src
-store
その中にindex.js
を作成します。
Prac-Redux
-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を指定しましょう。
//注意 まだ実行できません
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内の値にアクセスするには、Provider
とuseSelector
を使います。
Storeで定義したstateを共有する範囲(どのコンポーネントに共有するか)を決めるために、共有したいコンポーネントをProvider
コンポーネントで囲います。今回はすべてのコンポーネント内で共有したいので、src/index.js内でAppコンポーネントを囲います。
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の中を整えます。デフォルトでいろいろ書いてありますが、全部削除して以下のコードを書いてください。
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の書き換えは少し厄介で、 dispatch と action という用語が出てきます。
- 各コンポーネントが値を書き換えたいとき(ボタンをクリックした時等)に
dispatch(action)
を呼ぶ。 - Reducerはdispatch(action)が呼ばれると、actionの情報をもとにstoreのstateの内容を書き換える。
という順番で行われます。1. は書き換えたいコンポーネント内で、2. はsrc/index.jsのreducer内で記述します。
これをマクドナルドに例えるならば、
- 表のスタッフ(各コンポーネント)が注文が入った時に、「チーズバーガーを1つ」という注文の情報(action)を、注文を裏方(Reducer)に伝える(dispatch)
- 裏方(Reducer)は注文(action)をもとにチーズバーガーをつくって在庫からチーズ(Storeのstate)を減らす。
といったところでしょうか。
それぞれ見ていきましょう。
1. コンポーネントでdispatch(action)する
今回はボタンを押すと、Storeのstate(今回はcount)が1上がるようにしたいので、まずボタンをAppコンポーネントに追加します。
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を登録します。
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関数を使って(インポートを忘れずに...)
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は次のような構造になっているただのオブジェクトです。
{
type:/*state変更のタイプを指定する文字列*/,
payload:/*その詳細情報*/
}
先ほどのマクドナルドの例でいえば、
{
type:"注文",
payload:{
"商品名":"チーズバーガー",
"数量":1
}
}
といった感じになります。
今回はStoreに「カウントを増やしてくれー増やす数は1だぞー」と教えたいので、
{
type:"count/add",
payload:1
}
というactionを指定するといいでしょう。
なお、typeは必ず指定する必要があるみたいです。(なんでかは知らん)
なのでhandleClickの中は次のようになります。
const action = {
type:"count/add",
payload:1
}
dispatch(action);
ここまでで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のことです。
............
const reducer = (state=initialState, action)=>{
const newState = {
...state,
} ;
return newState ;
}
............
実はreducerのactionとは 先ほど1でtypeやpayloadというプロパティを入れたactionオブジェクトの事です。
なので、actionの情報に基づいて、newStateを変更していけばいいんです。早い話が次のようになります。
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
に作ることにします。
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
に記述することにします。
export function useCount(){
const count = useSelector((state)=>state.count) ;
function up(){
dispatch(countup(1));
}
return [
count,
up,
] ;
}