React × Reduxの連載記事を投稿していこうと思います。
第一回目はサラのMacに環境構築を行っていきます。
webpack4.42を使用してreact-reduxのsampleが動くところまでやってみたいと思います。
とりあえずプラグインやライブラリ等は追々検討していきますので、目指すは最小構成のreact redux環境となります。
React16.8からはフック(hook)が使えるようになったので、基本classではなくフックを利用して作成していきます。
基本的に独学なので、よくわかってないで書いてる部分もすごいあります。
間違いとかあったらコメントとかで教えてくれると嬉しいです。
これからReactを勉強したいなーって思ってる人の少しでも参考になれば嬉しいです。
- React×Reduxを使用した環境構築
- Reactでhookを率先して使って構築
- webpack-dev-serverで実行
- 環境ターゲット(2020-03-06現在最新)
- React16.13
- webpack4.42
この記事のターゲットとなる環境
現在の環境状況や前提条件を書いておきます。
- Mac OS Catalina ver 10.15.3
- Homebrewがインストール済み
- Node.jsがインストール済み
Homebrewのインストール
Homebrewのインストールについては過去に書いたこちらの記事を参考にしてください。
http://hirooooo-lab.com/development/homebrew/Node.jsのインストール
Node.jsのインストールについてはこちらを参考にしてください。
http://hirooooo-lab.com/development/nstall-node/これ以外のReact × Reduxの連載記事はこちらからどうぞ
http://hirooooo-lab.com/react-redux/React環境構築
開発ディレクトリを作成して初期化する
適当な場所にアプリのソースディレクトリを作成して移動します。
$ mkdir uriel
$ cd uriel
※私の場合はuriel(ウリエル)www
続いてパッケージの初期化を行います。
$ npm init
いろいろ聞かれるのでよしなに答えておけばOKです。
基本Enter連発で最後の質問をyesでも問題ないです。
$ npm init [~/dev/labo/uriel][master]
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (uriel)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/hiro/dev/labo/uriel/package.json:
{
"name": "uriel",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
}
Is this OK? (yes) yes
これで配下にpackage.jsonができていればOKです。
必要なNodeModuleを取得
とりあえず今回使うModuleは以下となります。
- dependencies
- react
- react-dom
- react-redux
- redux
- prop-types
- devDependencies
- @babel/core
- @babel/preset-env
- @babel/preset-react
- babel-eslint
- babel-loader
- eslint
- eslint-loader
- eslint-plugin-react
- file-loader
- html-webpack-plugin
- webpack
- webpack-cli
- webpack-dev-server
この辺りは今後ちゃんと検討していくと思うので、とりあえずは下記コマンドで取り込んじゃいましょう。
$ npm install --save react react-dom react-redux redux
$ npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-eslint babel-loader eslint eslint-loader eslint-plugin-react file-loader html-webpack-plugin webpack webpack-cli webpack-dev-server
package.jsonに以下のdependenciesが追加されたと思います。
"dependencies": {
"prop-types": "^15.7.2",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-redux": "^7.2.0",
"redux": "^4.0.5"
},
"devDependencies": {
"@babel/core": "^7.8.7",
"@babel/preset-env": "^7.8.7",
"@babel/preset-react": "^7.8.3",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.0.6",
"eslint": "^6.8.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-react": "^7.18.3",
"file-loader": "^5.1.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
余談ですが、dependenciesにはアプリケーション実行時に使用するmoduleを、devDependenciesでは開発中だけ使用するmoduleを追加するのが良いと思います。
とりあえずこれで必要な基盤準備は整いました。
続いて設定ファイルを追加していきます。
設定ファイルの作成
babelの設定ファイル、eslantの設定ファイル、webpackの設定ファイルを作成していきます。
これらについては細かく書くと大変なことになってしまうので、とりあえず動くところまで持っていくために、今回は下記のファイルをそのまま置いておけばOKです。
.babelrcの追加
BABELの設定ファイルを追加します。
記載内容については今回は触れません。
BABELって何?って人のためにちょっとだけ
BABELとは簡単にいうとJavascriptのコードを新しい書き方から古い書き方へトランスパイルを行ってくれるヤツです。
今回はES6の記法で書いていきますが、ブラウザで使用するためにはES5の記法まで変換する必要があります。
そのときにいろいろやってくれるのがBABELです。
もっと詳しく知りたい人はちゃんと調べてみることをオススメします!
.babelrcファイルを作成し、以下のコード追加してください。
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
.eslintrcの追加
ESLintの設定ファイルを追加します。
例によって記載内容については今回は触れません。
ESLintって何?って人のためにちょっとだけ
ESLintとは簡単にいうとJavascriptの静的検証を行ってくれるツールです。
コードを実行する前にバグを見つけてくれたり、コーディングルールなどを設定することでスタイルを統一したりできます。
こちらも詳しく知りたい人はちゃんと調べてみることをオススメします!
.eslintrcファイルを作成し、以下のコードを追加してください。
{
"env": {
"es6": true,
"browser": true,
"node": true,
"jquery": true
},
"rules": {},
"parser": "babel-eslint",
"plugins": [
"react"
],
"ecmaFeatures": {
"arrowFunctions": true,
"jsx": true
}
}
webpack.config.jsの作成
webpackの設定ファイルを作成します。
こちらも記載内容は省きますが、Webpackについてはちゃんと理解をしておきたい所です。
webpackってなんぞやという人のためにちょっとだけ
webpackとはJSファイルをまとめる高機能なモジュールバンドラーです。
これは詳しく説明すると100億年かかってしまうので、すごくわかりやすくまとめられているサイトを紹介させていただきます。
[blogcard url=”https://ics.media/entry/12140/”]
webpack.config.jsファイルを作成して、以下のコードを追加してください。
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
context: __dirname,
entry: [
'./src/index.jsx',
],
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: 'bundle.js',
},
module: {
rules: [
{ test: /\.jsx$/, enforce: 'pre', exclude: /node_modules/, loader: 'eslint-loader' },
{ test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel-loader'] },
],
},
resolve: {
extensions: ['.js', '.jsx'],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
inject: 'body',
}),
new webpack.LoaderOptionsPlugin({
options: {
eslint: {
configFile: './.eslintrc',
},
},
}),
],
};
package.jsonのscriptを書き換える
起動するためのnpm scriptを追加します。
npmによって作成されたpackage.jsonのscript要素を以下のように変更します。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack-dev-server --mode development --hot --inline --progress --colors --history-api-fallback",
"build": "webpack --mode production --progress --colors"
},
webpack-dev-serverを使って起動するスクリプトがstartに設定されているのですが、オプションがいろいろあるので使っているやつだけ軽く説明しておきます。
- mode
developmentとするとソースマップが有効になります
productionではコードが圧縮されます - hot
HMR(Hot Module Replacement)が有効になります
※これについては別記事で詳しくやりたいと思います - inline
ブラウザのオートリフレッシュが有効になります - progress
コンパイル時にプログレスバーが表示されます - colors
コンパイル時に色付け表示れます - history-api-fallback
history APIのフォールバックのサポートを有効にします。
※これをつけないと直パスの時などで404になってしまいます。
これで起動までの設定は完了です。
次はいよいよソースを追加していきます。
Reactプロジェクト構成
今回は簡単なカウンターアプリを作ってみます。
reactのhooksの登場により、containerいらないんじゃないかとかいろいろ衝撃がいろいろありました。
componentをreduxに依存させてしまうと、テストがやりづらくなるとかいろいろデメリットはありますので、今回はできるだけcontainer層とcomponent層をちゃんと分けて、componentではreduxに依存しない構成で進めたいと思います。
構成完成図はこんな感じを目指します。
カウンターアプリを作ってみる
Actionの作成
Actionのタイプを定義したActionTypes.jsxとactionのfunctionを定義したcounter.jsxを作成します。
constants/ActionTypes.jsx
constants/ActionTypes.jsxを作成し、インクリメントとデクリメントのアクションタイプを定義します。
// カウンター増減
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
actions/counter.jsx
actions/counter.jsxを作成し、incrementアクションとdecrementアクションを作成します。
ActionTypesをimportしてtype定義を返しているだけです。
import * as types from '../constants/ActionTypes';
const increment = () => {
return { type: types.INCREMENT };
};
const decrement = () => {
return { type: types.DECREMENT };
};
const CounterActions = {
increment,
decrement,
};
export default CounterActions;
Reducerの作成
続いてReducerの作成です。
Reducerは現在のstateとAction から、新しいstateを生成するfunctionです。
react-reduxの思想では現在のstateを更新するのはNGで、新しいstateを返さないとダメです。
reducers/counter.jsx
reducers/counter.jsxを作成し、actionTypeにそった処理を実装します。
今回はstateにvalueという数値を持っているだけなので、incrementで+1をdecrementで-1をstateのvalueに行い、その結果を新しいstateとしてreturnしてます。
すべてのactionはreducerに渡されてしまうので、未定義のactionが来た場合はstateをそのまま返すようにします。
initialStateはstateの初期値を設定してます。
import * as types from '../constants/ActionTypes';
const initialState = {
value: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
case types.INCREMENT:
return { value: state.value + 1 };
case types.DECREMENT:
return { value: state.value - 1 };
default:
return state;
}
};
export default counter;
reducers/index.jsx
今はまだcounter.jsxだけですが、今後複数のreducerができた場合のために、combineReducersを使ってrootReducerにまとめておきます。
reducers/index.jsxを作成して、counterをrootReducerにまとめておきましょう。
import { combineReducers } from 'redux';
import counter from './counter';
const rootReducer = () => combineReducers({
counter,
});
export default rootReducer;
これでreducerの準備は完了です。
containers・componentsの作成
ではreactのコンポーネントを作成します。
冒頭でも書きましたが、React16.8以降のhooksの登場により、どのcomponentからもuseSelectorでreduxのstoreにアクセスできるようになりました。
それにより、containerの役割がなくてもreact-reduxの構成を簡単・簡潔に書くことができるようなりました。
ただ、テストが手間になったり、redux以外のデータストアに変更するときにガッツリ依存したcomponentだと大変というデメリットが存在します。
なので、今回は今までのclassパターンのようにreduxのデータを流し込む役割をcontainerだけで行い、component階層ではreduxには一切関与しないデザインパターンで進めていきます。
詳しくはこの辺りの記事を参考に一読していただけるとわかると思います。
[blogcard url=”https://qiita.com/kitagawamac/items/d769c85f446994349b52″]
[blogcard url=”https://qiita.com/seya/items/700184c0d4a52bc0d32b”]
[blogcard url=”https://qiita.com/niyou0ct/items/51719296642deae89c86″]
container/AppContainer.jsx
メインコンテナとなるcontainer/AppContainer.jsxを作成します。
まずはreturnを作成し後述するHeaderコンポーネントとCounterコンポーネントを表示します。
import React from 'react'
import Header from './header/HeaderComponent';
import Counter from './counter/CounterComponent';
const AppContainer = () => {
return (
<div>
<Header />
<Counter />
</div>
);
};
この後カウンターコンポーネントの中に増加と減少を行うボタンと、現在のstoreの値を表示させたいと思います。
カウンターコンポーネントにはstateのcounterとcounterActionsをpropsで流し込みたいので、useSelectorとuseDispatchからpropsに引き渡したいと思います。
actionCreatorはuseMemoを使ってメモ化をしてpropsに引き渡します。
useActionsというメソッドを作って、actionCreatorをメモ化しましょう。
詳しくはこちらをみてみてください。
[blogcard url=”https://react-redux.js.org/api/hooks#recipe-useactions”]
import React, { useMemo } from 'react'
import { bindActionCreators } from 'redux'
import { useDispatch, useSelector } from 'react-redux';
import Header from '../components/header/HeaderComponent';
import Counter from '../components/counter/CounterComponent';
import CounterActions from '../actions/counter';
const useActions = (actions, deps) => {
const dispatch = useDispatch();
return useMemo(
() => {
if (Array.isArray(actions)) {
return actions.map(a => bindActionCreators(a, dispatch))
}
return bindActionCreators(actions, dispatch)
},
deps ? [dispatch, ...deps] : [dispatch]
)
};
const AppContainer = (props) => {
const counterActions = useActions(CounterActions);
const counter = useSelector(state => state.counter);
const _counterProps = { counter, counterActions, ...props };
return (
<div>
<Header />
<Counter {..._counterProps} />
</div>
);
};
export default AppContainer;
※ハイライトしているところを追加
components/counter/CounterComponent.jsx
今回メインコンポーネントとなるcomponents/counter/CounterComponent.jsxを作成します。
propsから引き継いだstateのcounterからvalueを表示します。
counterActionのincrementとdecrementを2つのボタンのonClickに設定してdispatchさせます。
この辺は今までの書き方と全く同じでいけますね!
import React from 'react'
import PropTypes from 'prop-types';
const CounterComponent = (props) => {
const { counter, counterActions } = props;
return (
<div>
<h2>count={counter.value}</h2>
<button onClick={() => counterActions.increment()}>増加</button>
<button onClick={() => counterActions.decrement()}>減少</button>
</div>
);
};
CounterComponent.propTypes = {
counter: PropTypes.object.isRequired,
counterActions: PropTypes.object.isRequired,
};
export default CounterComponent;
components/header/HeaderComponent.jsx
CounterComponentだけじゃ味気ないのでcomponents/header/HederComponent.jsxを作成してヘッダー部分のコンポーネントを作成しておきます。
ただし、特に何もないコンポーネントを分割する例えです。
import React from 'react'
const HeaderComponent = () => {
return (
<div>
<h1>ヘッダだYO</h1>
</div>
);
};
export default HeaderComponent;
storeの作成
Storeはアプリケーションの状態(state)を一つだけ保持しています。
storeを作成する際には、reducerを渡して登録します。
dispatchすることによって、reducerに現在のstateとactionを渡して、結果のstoreを作成します。
stores/configureStore.jsx
stores/configureStore.jsxを作成し、createStoreで作成したstoreを返します。
import { createStore } from 'redux';
import rootReducer from '../reducers';
const configureStore = () => {
const store = createStore(
rootReducer(),
);
return store;
};
export default configureStore;
エントリポイントのindexの作成
最後にエントリポイントとなるindex.jsxとindex.htmlを作成します。
src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from '../stores/configureStore';
import App from '../container/AppContainer';
const stores = configureStore();
const render = () => {
ReactDOM.render(
<Provider store={stores}>
<div>
<App />
</div>
</Provider>,
document.getElementById('root'),
);
};
render();
Provider store={stores}>によって、配下でstoreを使用できるようにしています。
今回のソースはprovider内にAppのコンテナ含めて、それをhtmlのroot属性のところに注入しています。
src/index.html
注入されるrootのidのdivタグと、生成されたbundle.jsを読み込んでいるだけです。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>React-Redux Boilerplate</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
これで一通りの作成が終わりました。
実行してみる
ターミナルから以下のnpm startを実行してみてください。
$ npm start
正常にビルドが通ったらhttp://localhost:8080/にアクセスるとこんなページが表示されると思います。
増加ボタンと減少ボタンでaction が呼ばれ、stateのvalueが変更され、画面のcountの値が変わると思います。
React × Redux環境構築編まとめ
とりあえずざっとreact – reduxを使ったすご~く簡単な画面が動くところまでやってみました。
今回のパターンが正解というわけでもなく、ducksパターンやRe-ducksパターンなど、アプリ構成のベストプラクティスは人様々だと思います。
reactのhook登場により、かなり簡単に書けるようになりましたが、reactの基本やReduxの基本思想はしっかり理解してこれから頑張って習得して行きたいと思います。
何かありましたらコメントお願いします。
これからReact Reduxをやってみようと思ってる人や、勉強中の人に、1つのパターンとして参考にしていただければ嬉しいです。
- Webpack、Babel、Lint、についてはちゃんと理解することをオススメ
- ReactのHooks登場でどのコンポーネントからもsotreにアクセスできるけど、デメリットもあるからcontainerでreactとredexを接続して、componentにはpropsで流し込むのが良いとされている
- Reduxに依存してもいいなら直接componentでuseSelectorやuseDispatchをやってもいいと思う(圧倒的にコード量はへる)
- デザインパターンは人それぞれなのでこれが正解とかいう話ではない
次回予告
次回予告というかやろうと思ってること
- Redux Toolkitから作成するDucksパターンで実装する
- ESLintでAirbnbのスタイルガイドを実装する
- MaterialデザインとCSSを実装する
- ページルーティングを実装する
- 開発環境の効率化をするためにHotReloadとソースマップを実装する
- REST通信を実装する
- ログイン制御のフローを実装する
- デプロイ方法を検討、実装する
次の記事はこちら
http://hirooooo-lab.com/development/react-redux-ducks-pattern/勉強するならプログラミングスクールがベスト
今からプログラミングを勉強してプロのエンジニアを目指すなら、プログラミングスクールで勉強するのがベストです!
最短4週間で未経験からプロを育てるオンラインスクールなので、自宅からプログラミングやアプリ開発を学ぶことができます。
[jin_icon_book size=”18px” color=”#2c05f0″]オススメ本
コメント
コメント一覧 (2件)
こんにちは。入門記事、ありがとうございます。
写経していて気がついたのですが、container/AppContainer.jsx のソースの `../components/header/HeaderComponent` `../components/counter/CounterComponent` は、正しくは `../components/HeaderComponent` `../components/CounterComponent` かと思いました。
Aさん!ご指摘ありがとうございます!
おっしゃる通りで、Components配下にDirを切っていたのにHeaderComponent.jsxとCounterComponent.jsxがdirなしになっちゃってましたね・・・。
いちおうこの後の記事とかでもdirありきで進んでますので、HeaderComponentとCounterComponentのところを修正しておきました!!
本当にこういうご指摘はありがたいです!!
引き続きどうぞよろしくですー