React Redux Tutorial for Beginners: The Definitive Guide (2018)
【所要時間】
5時間21分(2018年9月7,8,10日)
【概要】
React/Reduxのチュートリアル
【要約・学んだこと】
React Redux tutorial: who this guide is for
React Redux tutorial: what you will learn
- Reduxとは何か
- ReduxをReactで使う方法
を学ぶ。
React Redux tutorial: a minimal React development environment
ここからwebpack をcloneする。
git clone https://github.com/valentinogagliardi/webpack-4-quickstart.git
./src/App.js を削除する。
React Redux tutorial: what is the state?
import React, { Component } from "react";
class ExampleComponent extends Component {
constructor() {
super();
this.state = {
articles: [
{ title: "React Redux Tutorial for Beginners", id: 1 },
{ title: "Redux e React: cos'è Redux e come usarlo con React", id: 2 }
]
};
}
render() {
const { articles } = this.state;
return <ul>{articles.map(el => <li key={el.id}>{el.title}</li>)}</ul>;
}
}
すべてのstateful React componentは自身のstateを持つ。
React component ではstateがデータを保持し、componentはそのデータをユーザーに表示することがある。stateはまた動作やイベントに応じて変化する。Reactではlocal componentのstateをsetStateで変更できる。
そもそもstateとは何か。簡単なJavaScriptアプリですらstateを持つ。
例えばユーザーがボタンをクリックすると、モーダルが現れる。
JSオブジェクトとして初期stateを記述すると
var state = {
buttonClicked: 'no',
modalOpen: 'no'
}
そしてユーザーがクリックすると
var state = {
buttonClicked: 'yes',
modalOpen: 'yes'
}
ではこの時JS内でどうやってこれらのことを追跡しているのか。stateの追跡を助けるライブラリがJSにあるのか?
React Redux tutorial: what problem does Redux solve?
典型的なJSアプリには下記のようなたくさんのstateで溢れている。
- ユーザーが見たこと(data)
- dataが取得していること
- URLがユーザーに見せていること
などだ。
Reactアプリの場合は、親component内にstateを保持することができるが、stateを送ったり受け取ったりしているうちに、管理が難しくなる。
そこでReduxをReactのstateの管理に使用する。
Reduxは一箇所にstateを保管する。また、stateを取得して管理するためのロジックがReact外の場所にもある。
React Redux tutorial: should I learn Redux?
React Redux tutorial: should I use Redux?
ReduxもしくはFlux, Mobxのどれをstate管理に使うかは人それぞれ。
Reduxを使うのは
- 複数のReact componentが同じstateにアクセスする必要があるが、親子関係を持たない時
- 複数のcomponents間でpropsを使いstateの受け渡しをするのに苦労している時
だ。しかし、Reactは小規模アプリには有効ではない。大きいアプリで役立つ。
React Redux tutorial: getting to know the Redux store
Reduxのstoreは人間の脳に似ている。アプリケーション全体のstateが倉庫の中にある。
そのため、Reactを使うためにstateをラップするためのstoreを作成する必要がある。
npm i redux --save-dev
storeのためのディレクトリを作る。
mkdir -p src/js/store
src/js/storeの中にindex.jsを作り、下記のコードを記入し、storeを初期化する。
import { createStore } from "redux";
import rootReducer from "../reducers/index";
const store = createStore(rootReducer);
export default store;
createStoreがRedux storeを作るfunctionである。ここでは最初のargumentのrootReducerとして、reducerを使う。
また、initial stateをcreateStoreに渡すこともできる。しかし、基本的にその必要はない。initial stateを渡すことはサーバーサイドrenderingに役立つ。stateはreducerから来る。
ではreducerとは何をするのか。Reduxではreducerがstateを作る。stateは手入力で作るようなものではない。
React Redux tutorial: getting to know Redux reducers
初期stateはSSRに役立つが、Reduxではstateが全てreducersからreturnされなければいけない。
reducerとはただのJS functionだ。reducerは2つのparameterを取る。current stateとactionだ。
stateは不変でその場で変更ができないため、pureでなければいけない。pure functionは与えられたinputに対して、完全に同じoutputをreturnする。
ReactではsetStateでlocal stateを置き換えるが、Reduxではそれができない。
reducerを作るのは難しくない。2つのparameterを持つJS functionだ。
下記に、最初のparameterとしてinitial stateを取るreducerを作る。2つ目のparameterはactionを与える。これはreducerがinitial stateをreturnするだけだ。
src/js/reducers/index.js を作り、下記のコードを入力。
const initialState = {
articles: []
};
const rootReducer = (state = initialState, action) => state;
export default rootReducer;
React Redux tutorial: getting to know Redux actions
Reducerがアプリのstateを作るというのは最も重要なReduxのコンセプトだ。では、reducerはどうやって次のstateを作るタイミングを知るのだろうか。
stateを変更する唯一の方法は、storeにシグナルが送られることだ。このsignalはactionのことだ。”Dispatching an action”はsignalを送るプロセスだ。
不変のstateをどのように変えるのか。それはcurrent stateに新しいdataをプラスすることだ。
ReduxのactionはJS objectに過ぎない。
{
type: 'ADD_ARTICLE',
payload: { name: 'React Redux Tutorial', id: 1 }
}
全てのactionはstateをどうやって変更するべきかを記述するために、type propertyを必要とする。
上記の例ではpayloadは新しいarticleだ。reducerは後ほどarticleをcurrent statusに追加する。
全てのactionを1つのfunction内に全てwrapすることがベストプラクティスである。このようなfunctionをaction creatorと呼ぶ。
ではRedux actionを作ることで全て一緒に置いてみよう。
src/js/actions/index.js を作り、下記のコードを記入する。
export const addArticle = article => ({ type: "ADD_ARTICLE", payload: article });
type propertyはstringに過ぎない。reducerはこのstringをどうやって次の計算をするのかを決定するのに使用する。
stringがタイプミスと重複する傾向にあるので、constantとしてaction typeを宣言した方がよい。これはdebugが難しくなることによるエラーを避ける。
src/js/constants/action-types.jsを新たに作成し、下記のコードを記入する。
export const ADD_ARTICLE = "ADD_ARTICLE";
src/js/actions/index.js のactionをaction typeを使うように更新する。
import { ADD_ARTICLE } from "../constants/action-types";
export const addArticle = article => ({ type: ADD_ARTICLE, payload: article });
React Redux tutorial: refactoring the reducer
ここでもう一度Reduxの概念を確かめる。
- Redux storeは脳のようなもので、Reduxの動的部分の編成を担当する。
- アプリのstateはstore内で単一、不変 objectとして存在する。
- storeがactionを受け取ると、すぐにreducerのトリガーとなる。
- reducerは次のstateをreturnする。
Reducerはstateとactionを取るJS functionだ。reducer functionはswitch statementを持つ。(if/elseも使える)
reducerはaction typeによって、次のstateを計算する。さらに、action typeがマッチしないときは少なくとも初期stateをreturnする。
action typeがcase clauseとマッチすると、reducerは次のstateを計算し、新しいobjectをreturnする。
// ...
switch (action.type) {
case ADD_ARTICLE:
return { ...state, articles: [...state.articles, action.payload] };
default:
return state;
}
// ...
先ほどのセクションで作成したreducerはinitial stateをreturnするだけなので、src/js/reducers/index.js を修正しよう。
import { ADD_ARTICLE } from "../constants/action-types";
const initialState = {
articles: []
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_ARTICLE:
state.articles.push(action.payload);
return state;
default:
return state;
}
};
export default rootReducer;
上記のコードは有効だが、reducerはReduxの原則である不変を破壊している。
Array.prototype.pushは不純な関数で、元の配列を変更する。Array.prototype.concat(2つのarrayを結合する)をArray.prototype.pushの代わりに使うだけで、初期の配列を不変に保つことができる。
import { ADD_ARTICLE } from "../constants/action-types";
const initialState = {
articles: []
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_ARTICLE:
return { ...state, articles: state.articles.concat(action.payload) };
default:
return state;
}
};
export default rootReducer;
さらに spread operator を使い、reducerをさらによくする。
import { ADD_ARTICLE } from "../constants/action-types";const initialState = {
articles: []
};const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_ARTICLE:
return { ...state, articles: [...state.articles, action.payload] };
default:
return state;
}
};export default rootReducer;
initial stateはそのままにした。initial articles arrayはここでは変更していない。initial state objectも変更していない。結果はinitial stateのコピーだ。
ここでReduxの突然変異を避ける2つのポイントがある。
- array にはconcat(), slice(), and …spread を使う。
- objectには Using Object.assign() and …spread を使う。
object spread operatorはまだステージ3だ。Babelでobject spread operatorを使うときに、をSyntaxError Unexpected tokenを避けるためにObject rest spread transformを導入する。
npm i --save-dev babel-plugin-transform-object-rest-spread
.babelrcを開き、configurationを更新する。
{
"presets": ["env", "react"],
"plugins": ["transform-object-rest-spread"]
}
Redux protip: reducerはアプリが大きくなるにつれて大きくなる。大きいreducerを複数のfunctionにわけ、combineReducers で結合する。
React Redux tutorial: Redux store methods
Redux自体は小さなライブラリ(2kb)だ。 Redux store a simple API はstateを管理する。最も重要なメソッドは
これらをブラウザのコンソールで試す。そのために先に作ったstoreとactionのglobal variablesとして出力する。
src/js/index.jsを作り、下記のコードに更新する。
import store from "../js/store/index";
import { addArticle } from "../js/actions/index";
window.store = store;
window.addArticle = addArticle;
src/index.jsを開き、下記のコードに更新する。
import index from "./js/index"
npm startを実行し、コンソールを開く。storeをglobal variableとしてエクスポートしたので、メソッドにアクセスできる。
現在のstateにアクセスする。
store.getState()
initial stateをアップデートしていないので、articlesは0だ。
subscribe を使ってstateのアップデートをlistenする。subscribeメソッドはactionがdispatchedされるたびにcallbackを放つ。つまりstateを変えたいということをstoreに伝える。
callbackを登録する。
store.subscribe(() => console.log('Look ma, Redux!!'))
Reduxのstateを変えるために、actionをdispatchする。そのために dispatch method をコールする。disposalに1つのactionを持っている。stateに新しい項目を追加するaddArticleだ。
actionをdispatchする。
store.dispatch( addArticle({ name: 'React Redux Tutorial for Beginners', id: 1 }) )
このようにLook ma, Redux!!と表示される。
stateが変わったのを検証するためにもう一度実行する。
{articles: Array(1)} となった。
これがReduxの最もシンプルなフォームだ。
React Redux tutorial: connecting React with Redux
次にReactとReduxの組み合わせを学ぶ。
Reduxは自身のframe work agnosticを持つ。vanilla Javascript、もしくはAngular、Reactを使うことで、それが使用できる。
Reactにはreact-redux があるので、インストールする。
npm i react-redux --save-dev
- an App component
- a List component for displaying articles
- a Form component for adding new articles
上記3つのシンプルなcomponentsを持つアプリを作る。
React Redux tutorial: react-redux
react-reduxはreduxとreactを結ぶものだ。最も重要なメソッドはconnectだ。
connectはReact componentとReduxストアを繋ぐ。
connectを使い、2,3のargumentを繋ぐ。知るべき基本的なことは下記2点だ。
- the mapStateToProps function
- the mapDispatchToProps function
mapStateToPropsはその名の通り、Redux stateの一部をprops of a React component と繋ぐ。これにより、繋がれたReact componentは必要なstoreのパートに正確にアクセスする。
mapDispatchToPropsはactionのためにあり、Redux actionとReactのpropsを繋ぐ。これによりactionがdispatchできるようになる。
React Redux tutorial: App component and Redux store
ReduxとReactを繋ぐには、Provider を使う。これはreact-recuxからの上位componentだ。ProviderがReact appを包み、Reduxのstore全体を認識させる。なぜなら、storeはReduxの全てを管理するからで、stateにアクセスしたり、actionをdispatchするためにstoreに伝える必要があるからだ。
src/js/index.js を開き、下記のコードに書き換える。
import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import store from "./store/index";
import App from "./components/App";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("app")
);
providerがReact app全体を包んでいるのがわかる。さらに、storeをpropとして取得している。
次にApp componentを作る。 AppはList componentとrender自身をimportする。
// src/js/components/App.js
import React from "react";
import List from "./List";const App = () => (
<div className="row mt-5">
<div className="col-md-4 offset-md-1">
<h2>Articles</h2>
<List />
</div>
</div>
);export default App;
markupなしだとこうなる。
import React from "react";
import List from "./List";
const App = () => (
<List />
);
export default App;
React Redux tutorial: List component and Redux state
List componentはRedux storeと相互に働く。connectは少なくとも1つのargumentを取る。
articleのlistを取得するためにListが欲しいので、state.articlesをcomponentとmapStateToPropsを使ってconnectする。
新たにsrc/js/components/List.jsを作り、下記のコードを記載する。
import React from "react";
import { connect } from "react-redux";
const mapStateToProps = state => {
return { articles: state.articles };
};
const ConnectedList = ({ articles }) => (
<ul className="list-group list-group-flush">
{articles.map(el => (
<li className="list-group-item" key={el.id}>
{el.title}
</li>
))}
</ul>
);
const List = connect(mapStateToProps)(ConnectedList);
export default List;
List componentはarticles arrayのコピーであるarticlesのpropを受け取る。先ほど作ったRedux stateの内部にこれらのarrayは存在する。それらがreducerからやってくる。
const initialState = {
articles: []
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_ARTICLE:
return { ...state, articles: [...state.articles, action.payload] };
default:
return state;
}
};
そしてJSX内のpropを使用し、articlesのlistを作る。
{articles.map(el => (
<li className="list-group-item" key={el.id}>
{el.title}
</li>
))}
React protip: PropTypesでpropを検証する習慣を身につける。
これでcomponentがListのexportを取得した。Listはstateless componentとRedux storeとConnectedListのconnectingの結果だ。
stateless componentは自身のlocal stateを持たない。Dataはpropsとして渡される。
React Redux tutorial: Form component and Redux actions
Form componentはListよりやや複雑である。これは新しい項目をappに加えるstateful componentだ。
Reactのstateful componentは自身のローカルstateを運ぶcomponentだ。
Reduxを使っている時も、stateful componentsを持つことはできるが、アプリのstateの全てをRedux内部に入れるべきではない。
この例では、他のcomponentがFormのローカルstateを認識しないようにする。
componentはform提出時にローカルstateを更新するロジックが含まれている。さらに、Redux actionをpropとして受け取る。この方法でaddArticle actionをdispatchすることで、global stateを更新することができる。
src/js/components/Form.jsを新たに作り、下記のコードを書く。
import React, { Component } from "react";
import { connect } from "react-redux";
import uuidv1 from "uuid";
import { addArticle } from "../actions/index";
const mapDispatchToProps = dispatch => {
return {
addArticle: article => dispatch(addArticle(article))
};
};
class ConnectedForm extends Component {
constructor() {
super();
this.state = {
title: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.id]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
const { title } = this.state;
const id = uuidv1();
this.props.addArticle({ title, id });
this.setState({ title: "" });
}
render() {
const { title } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
value={title}
onChange={this.handleChange}
/>
</div>
<button type="submit" className="btn btn-success btn-lg">
SAVE
</button>
</form>
);
}
}
const Form = connect(null, mapDispatchToProps)(ConnectedForm);
export default Form;
mapDispatchToPropsとconnectが標準的なReactのstuffだ。これはRedux actionsをReact propsとconnectする。この方法でcomponentはactionをdispatchできる。
handleSubmitメソッド内のactionがどうやってdispatchを取得するかが下記の部分からわかる。
// ...
handleSubmit(event) {
event.preventDefault();
const { title } = this.state;
const id = uuidv1();
this.props.addArticle({ title, id }); // Relevant Redux part!!
// ...
}
// ...
最後に、componentがFormとしての出力を取得する。FormはConnectedFormとRedux storeの接続の結果だ。
Sidenote: Form exampleのように、mapStateToPropsが存在しない場合、connectの第一argumentはnullでなければならない。そうしなければTypeError: dispatch is not a function. のエラーとなる。
App.jsにForm componentを加える。
import React from "react";
import List from "./List";
import Form from "./Form";
const App = () => (
<div className="row mt-5">
<div className="col-md-4 offset-md-1">
<h2>Articles</h2>
<List />
</div>
<div className="col-md-4 offset-md-1">
<h2>Add a new article</h2>
<Form />
</div>
</div>
);
export default App;
uuidをインストール
npm i uuid — save-dev
これでnpm startを実行すると
動いた。List componentはRedux storeと繋がっている。新しい項目を追加するたびに、re-renderする。
React Redux tutorial: wrapping up
React Redux tutorial: courses and books for learning Redux
近頃は誰でもtutorialを作れるので、JS courseのなどを見つける際は、専門家のものにするのがよい。例えばPractical Redux by Mark Erikson.
MarkがReduxのmantainerであり、おそらくReduxを学ぶ上で最適だ。
Practical Reduxを学ぶには
- UI の挙動をReduxで制御する
- Redux storeの関連データの管理にRedux-ORMを使う
- master:detailなviewの表示を構築し、dataを編集する
- カスタムアドバンストなRedux reducer logicをかく
また、Human Redux by Henrik Joreteg という本もある。
他にも下記の方法で学べる
The official documentation: Redux Docs.
Resources for learning Redux by Mark Erikson.
Dan Abramov has a nice write-up that will help you understand if your application needs Redux
There’s also an interesting thread on dev.to with Dan explaining when is it time to stop managing state at the component level and switching to Redux
React Redux tutorial: resources for learning functional programming
Reduxを使うにはfunctional programmingとpure functionsの知識が必要。
Professor Frisby’s Mostly Adequate Guide to Functional Programming by Brian Lonsdorf
Functional-Light Javascript by Kyle Simpson
React Redux tutorial: async actions in Redux
Redux のコアコンセプトに自信を持てるようになったら、A Dummy’s Guide to Redux and Thunk in React by Matt Stow をチェックしてみると良い。Reduxにredux-thunkというAPI callsを導入するのに役立つ。
【わからなかったこと】
【感想】
Reduxでstateの管理をしやすくする。途中少しわかりにくくなった部分はReduxではなくReactやJSに関する部分だったので、複雑なことをやる前に理解を深めていきたい。