#React、Reduxを使ったアプリを解説してみる
今回はアプリのコードを例にReactとReduxを解説していきます。
ReactとReduxの最初の壁として
・チュートリアル的教材は豊富だが、webpackなどのバージョンが違う場合エラーが出る
点があげられます。
これはフロントエンドの技術的進歩が爆速であることに起因しているのですが、ReactやReduxを扱う前に、その沼にはまってしまうのは少し悲しいですよね。。
なんで今回はcodesandboxという便利なサイトを使い環境構築をすっ飛ばして、私の書いたコードをサンプルに解説していこうと思います。
又、ぐぐって出てくるreduxのチュートリアルではディレクトリ構成の違いやES6をどこまで使うのかという点で、サイトによってばらつきがあるという点も覚えておいてください。僕はかなり混乱しました。
ReduxではAction、Reducer、Storeが重要な要素なのですが、Storeはややこしいためindex.tsは最初無視しましょう。
root.tsx、index.tsxも無視です。
このアプリでは
①actionsフォルダ配下のapp.jsでアクションを書き
②containersフォルダ配下App.jsでReactコードを書き、React・Reduxの結合もしています。
③reducersフォルダ配下のapp.jsとindex.jsでReducerを書いています。
Action、Reducer、Storeの役割については下記に書いています。
https://qiita.com/d_tech_log/items/ae91fc4d77adf25545ff
①と③ではRedux側、②ではReact側のコーディング(最後Reduxと合体)がなされていることを意識していてください。
##Reactのコード解説(containersフォルダ配下App.js)
②のReactのコードから解説していきます。
ReactとReduxを使ったアプリでは、ReactでView部分、Reduxで状態管理を行うことによって大規模開発をスムーズするという利点を得られます。
今回のサンプルコードでも、View側のボタン操作→Action→Store→Reducerの流れができているので、まずは最初のView部分を見ていきましょう。
import * as React from "react";
import { bindActionCreators } from "redux";
import { connect, Dispatch } from "react-redux";
import { Map } from "immutable";
import * as App_actions from "../actions/app";
export class App extends React.Component {
constructor(props) {
super(props);
// 名前入力欄の文字は、state.name で持っておくことにします
this.state = {
name: ""
};
}
// input が変更されると呼ばれるメソッド
// e.target.value には 変更後のテキストが入ります
on_change_name = e => {
this.setState({ name: e.target.value });
};
// ログインボタンをクリック(フォームがsubmit)したときに呼ばれるメソッド
on_submit = e => {
e.preventDefault();
const { app_actions } = this.props;
const { name } = this.state;
// 名前が入力されていなかったらアラートを出します
if (name.length === 0) {
alert("名前を入力してね!");
} else {
// 名前が入力されていたら action creator を呼んで、ユーザーが入力していた name を渡します。
app_actions.login(name);
}
};
// ログアウトボタンをクリックしたときに呼ばれるメソッド
on_click_logout = () => {
const { app_actions } = this.props;
app_actions.logout();
};
// DOMを作るメソッド
render() {
const { app } = this.props;
const { name } = this.state;
const login_user_name = app.get("login_user_name");
const is_login = login_user_name.length > 0;
return (
<>
<h1>React / React Redux Practive App!</h1>
<p>
<b>現在のログイン状態</b>:
{is_login
? `'${login_user_name}' でログインしています`
: "ログインしていません"}
</p>
<form onSubmit={this.on_submit}>
{is_login ? (
<>
<button onClick={this.on_click_logout}>ログアウト</button>
</>
) : (
<>
<input
type="text"
value={name}
onChange={this.on_change_name}
placeholder="お名前をどうぞ"
/>
<button type="submit">ログイン</button>
</>
)}
</form>
</>
);
}
}
// ここから下は、React と Redux の Store, ActionCreator を紐づける処理です
// おまじないだと思ってもらって、一旦よくわからなくてもOKです。
function mapStateToProps(state) {
return state;
}
function mapDispatchToProps(dispatch) {
return {
app_actions: bindActionCreators(App_actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
このコードを分解すると
import * as React from "react";
import { bindActionCreators } from "redux";
import { connect, Dispatch } from "react-redux";
import { Map } fromΩ"immutable";
import * as App_actions from "../actions/app";
reactからReactを呼び出し、reduxからbindActionCreatorsをimportして呼び出しています。
importがあるのですからexportもあります。
import * as React from "react";の* asは現在調べております。
ここでいうimmutableとはimmutable.jsのことでJavaScriptで不変なデータ構造を扱うためのものです。
immutable.jsを使うことによりコードの可読性が上がります。
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
ざっくり言うと輸出と輸入を繰り返してアプリを作り上げているイメージでしょうか。
下記ではAppクラスをコーディングしています。
がっつりReactですね。
constructor(props)でstateを作り、ES6記法でon_change_name()、on_submit()、on_click_logout()ファンクションを定義しています。
render()で仮想DOMを生成し、描画しています。h1タグやpタグが見えますね。
export class App extends React.Component {
constructor(props) {
//super()は必ず書きましょう!
super(props);
// 名前入力欄の文字は、state.name で持っておくことにします
//nameが空の状態で定義されました。
this.state = {
name: ""
};
}
// input が変更されると呼ばれるメソッド
// e.target.value には 変更後のテキストが入ります
// ES6で書かれています。これを書かないと画面上に入力した文字が表示されません。
//stateを変更する際はsetState()を使いましょう。
on_change_name = e => {
this.setState({ name: e.target.value });
};
// ログインボタンをクリック(フォームがsubmit)したときに呼ばれるメソッド
on_submit = e => {
//preventDefault()は、実行したイベントがキャンセル可能である場合、イベントをキャンセルするためのメソッドです。チェックボックスや入力ボックスを置く場合は書いてあげましょう。
e.preventDefault();
const { app_actions } = this.props;
const { name } = this.state;
// 名前が入力されていなかったらアラートを出します
//if文の書き方も色々あるので現場のコーディングルールに合わせましょう。
if (name.length === 0) {
alert("名前を入力してね!");
} else {
// 名前が入力されていたら action creator を呼んで、ユーザーが入力していた name を渡します。
//app_actions.login(name);がaction creatorのlogin(name)ファンクションを呼び出しています、actionを呼ぶ時はthis.propsなのですね。
app_actions.login(name);
}
};
// ログアウトボタンをクリックしたときに呼ばれるメソッド
on_click_logout = () => {
const { app_actions } = this.props;
app_actions.logout();
};
// DOMを作るメソッド
render() {
const { app } = this.props;
const { name } = this.state;
//actionのlogin_user_nameを呼んでます。
const login_user_name = app.get("login_user_name");
//login_user_nameの数が1以上ならログインしていますよってことです。
const is_login = login_user_name.length > 0;
return (
<>
<h1>React / React Redux Practive App!</h1>
<p>
<b>現在のログイン状態</b>:
{is_login
//?と:でログインした状態としてない状態をわけています。横並びではなく縦に書くとわかりやすいですね
? `'${login_user_name}' でログインしています`
: "ログインしていません"}
</p>
<form onSubmit={this.on_submit}>
{is_login ? (
<>
<button onClick={this.on_click_logout}>ログアウト</button>
</>
) : (
<>
<input
type="text"
value={name}
//onChange書かないと入力した文字が画面に表示されません。
onChange={this.on_change_name}
placeholder="お名前をどうぞ"
/>
<button type="submit">ログイン</button>
</>
)}
</form>
</>
);
}
}
そして最後にReactとReduxの結合部分です。
// ここから下は、React と Redux の Store, ActionCreator を紐づける処理です
// おまじないだと思ってもらって、一旦よくわからなくてもOKです。
function mapStateToProps(state) {
return state;
}
function mapDispatchToProps(dispatch) {
return {
app_actions: bindActionCreators(App_actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
僕も理解が薄いので、分かり次第更新していきます。
mapStateToPropsとかmapDispatchToPropsはどのサンプルコードにも出てくるので、ほんとおまじないのイメージです。
connent()でreduxとreactを繋げてるってイメージだけもって次行きましょう。
export defaultで輸出してます。
##Action解説(Redux部分、actionsフォルダ配下のapp.js)
次にActionの解説です。
export function login(name) {
return {
type: "APP_LOGIN",
login_user_name: name
};
}
export function logout() {
return {
type: "APP_LOGOUT"
};
}
またexportという文字列が見えます、ファンクションを輸出してますね。
actionでは必ずtypeを指定してあげる必要があります。typeはreducerで使われる識別番号みたいなもんです。
React部分で書いた下記を見ると、view側とactionが結びついていて、view操作→action→reducerの流れがわかるかと思います。
const { app_actions } = this.props;
app_actions.logout();
##Redux部分、Reducerの解説(reducersフォルダ配下のapp.jsとindex.js)
ここではapp.jsでアクションで決めたtypeごとに処理を分岐させています。
そした最後にindex.jsでcombineReducers()させてCheetahReducerとして輸出です。
なんでこういうことするかというと、アプリ規模が大きくなりactionが増えていく場合、reducerを一つのファイルに書くと視認性にかけますし、ばらばらで保持しても何か気持ち悪いですよね、だからcombineReducers()を使ってindex.jsで一元管理しているんです。
import { Map } from "immutable";
export default function(state, action) {
switch (action.type) {
case "APP_LOGIN":
return state.set("login_user_name", action.login_user_name);
case "APP_LOGOUT":
return state.set("login_user_name", "");
default:
}
return state || Map({ login_user_name: "" });
}
##まとめ
ReactとReduxを使ったアプリはaction、reducer、storeの部品、Reactのコードがあり混乱しますし、サンプルコードによってフォルダ構成が違うのでさらに沼にハマります(実体験)
最初学ぶ時は、まず理解すべきファイルとそうでないファイルの区別をつけ(何がわからない状態ではかなり難しいことです、、僕は幸せなことにそこを知ることができたので今これを書いてます)、大枠から理解して細かいところを詰めるやり方って物凄く効果的だなと感じました。
分かってない所、詰まっているところはReduxなのか、Reactなのか
理解できないのはjavascriptなのか、Reactなのか、ES6についてなのかを常に意識してこれからも頑張っていきましょう!!
ES6まとめ