仕事では, Angular 1 がメインでしたそろそろReact + webpackでいこうと模索しています。
React をしばらく触って, それからReact + Redux の学習を初めています。
React は本読んだりしてなんとかしましたが, redux にはかなり苦労しています。
参考にしたもの
一番上の解説はわかりやすかったです。今回のサンプルもここで取り上げていたサンプルをベースに作っています。 下2つはReduxの公式のサンプルです。フォルダの構成とか結構参考になると思います。最終的なゴール
- (今回)webpackでビルドする
- (今回)webpack-dev-server で動作確認
- (今回)reduxの導入
- (今回)es6でかけるようにする
- (今回)action, container, component, reducerの構成を作る
- react-routerを使う
- ajaxでデータを取ってくる
バージョンなど
作成日 : 2017/05/28 webpack : 2.5.1プロジェクトの作成, react, webapack, redux の導入
mkdir first cd first npm init -y npm install --save webpack webpack-dev-server npm install --save-dev babel babel-core babel-loader babel-preset-es2015 babel-preset-react npm install --save react react-dom react-redux redux npm install --save prop-typesfirstはプロジェクト名です。なんでも良いです。 今回は, webpack, webpack-dev-server, react, react-redux, reactのプロパティを使うとWarningが出るので prop-typesを使います。
フォルダ構成
first |- dist |- src | |- actions | |- components | |- containers | |- reducers | |- index.jsx |- .babelrc |- index.html |- package.json |- wbpack.config.js
.babelrc
ここで, es6を使うための設定をします。
{
"presets": [
"es2015", "react"
]
}
webpack.config.js
webpack の設定です。今回は jsx を動作させるための設定だけを記載しました。
const path = require('path');
module.exports = {
entry: path.join(__dirname, 'src/index.jsx'),
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
devServer: {
contentBase: '.',
port: 8080,
inline: true,
historyApiFallback: true
},
module: {
loaders: [
{
test: /\.jsx$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
};
webpackでビルドした結果を distフォルダへ, babel-loaderを使って, jsxを処理します。
webpack-dev-server はポート8080で動作させます。
Redux
Redux に関しては他のブログなどのエントリーを参考にしてさらっとまとめます。 Redux : Redux 細かい話は公式などを確認した方が良さそう。 状態管理をアプリ全体で行う, 状態管理するStoreはアプリ全体で1つ。 ユーザのActionがコールされると, Recuderを使って, Storeの状態を更新する。 ここで登場人物が3つ出てきました。(Action, Reducer, Store) Reduxではデータフローが重要で, データフローのルールに従います。 以下各要素の説明とプログラム上での登場人物の整理です。Action
typeをキーとしてStateを更新するための定義を書く。 状態更新はdispatch関数を実行して行う (ajaxの通信はここで行う) ActionはAction名(Reducerで処理を判別するため)と状態の値を持った単なるオブジェクト Storeのdispatch() メソッドの引数にAction Creatorを渡すことでActionがReducerに送られるActionCreator
最終的には, Actionと同じファイルに書いている functionで Action = object を返す Actionで書いていない更新したい状態の定義を書く。 Stateの更新自体は, Reducerがするので更新したい部分を引数などから生成するReducer
Reducerは、現在の状態(state)と受け取ったActionを引数に取り、新しい状態を返す関数 typeをキーにして処理を分ける, Switch 文中に Action で定義した State の type で分岐しロジックを書く stateの更新には, ES2015 Object.assign()を使おう!! Object.assign() はmergeオブジェクトのマージを行います。 ※Reducerは複数作ることができます。(ページごとに作るイメージかなぁ) (storeは一つしか引数に取れないので combineReducersで combine)Store
状態管理のオブジェクト 初期データと, ReducerからcreateStoreメソッドで作成します。これをProvider渡します。 Reduxのmiddlewareを渡す場合はそれも引数に加えます。Container Components
親コンポーネント。ここでReactとReduxのつなぎ込みを行います。 Container ComponentsはPresentational Componentを持ちます。Presentational Component
子コンポーネント。最小単位のコンポーネント 親からプロパティを受け取り描画します。イベントなども親から受け取ります。サンプル
今回のサンプルは参考の一番上のエントリーを参考にreact + reduxをwebpackで動作させる, フォルダ構成をReactの公式サンプルを 元に作り変えたものです。 index.jsx エントリー部分。
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { reducers } from './reducers/index.jsx';
import AppContainer from './containers/AppContainer.jsx';
const initialState = {
formReducer: {
value : null
}
};
const store = createStore(reducers,
initialState); // reducer, initial state
// Rendering
ReactDOM.render(
,
document.querySelector('.content')
);
とりあえず必要なComponentなどを全部importしています。 ここに直接書くのは, 初期状態の設定と(この部分もファイル分離できる) store の作成と, レンダリングの部分ですね。 Reactに連携させるReduxのStoreを渡しています。Providerという部分でReactとReduxのStoreの連携をしていますね。
index.html 元になるView,ただし動作関係は全部上のindex.jsxに任せる
Redux Sample
ビルドした後のjsを取り込んでいるだけです。この部分はもう基本的には変更しません。
actions/index.jsx アクションの実装です。Actionと,ActionCreatorの実装をしています。
const SEND = 'SEND';
/* Action Creator */
// Return Object
export function send(value) {
// Action
return {
type: SEND,
value,
};
}
Actionは, objectを返します。
ここでは, Actionタイプ(アクションの名前みたいなもの)とアクションを発行した部分から
valueという値を受け取ってそれを返しています。
recucers/formreducer.jsx Reducerの実装です。
export function formReducer(state = {}, action) {
switch(action.type) {
case 'SEND':
return Object.assign({}, state, {
value: action.value,
});
default:
return state;
}
}
containers/FormApp.jsx
import React from 'react';
import PropTypes from 'prop-types';
import FormInput from '../components/FormInput.jsx';
import FormDisplay from '../components/FormDisplay.jsx';
class FormApp extends React.Component {
render() {
return (
);
}
}
FormApp.propTypes = {
onClick: PropTypes.func.isRequired,
value: PropTypes.string
};
export { FormApp as default };
containers/AppContainer.jsx
import { connect } from 'react-redux';
import { send } from '../actions/index.jsx';
import FormApp from './FormApp.jsx';
// Connect to Redux
function mapStateToProps(state) {
return {
//value: state.value
value: state.formReducer.value //http://qiita.com/usagi-f/items/ae568fb64c2eac882d05
}
}
function mapDispatchToProps(dispatch) {
return {
onClick(value) {
dispatch(send(value));
}
};
}
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(FormApp)
export { AppContainer as default };
ここで Redux のプロパティとComponentを繋ぐ作業をしています。
storeからデータをひっぱてくるところ, メソッドを渡すとこですね。
components/FormDisplay.jsx
class FormDisplay extends React.Component {
render() {
return (
{this.props.data}
);
}
};
FormDisplay.propTypes = {
data: PropTypes.string
};
export { FormDisplay as default };
components/FormInput.jsx
class FormInput extends React.Component {
send(e) {
e.preventDefault();
this.props.handleClick(this.myInput.value.trim());
this.myInput.value = '';
return;
}
render() {
return(
);
}
};
FormInput.propTypes = {
handleClick: PropTypes.func.isRequired
};
export { FormInput as default };
動作確認
webpackでビルド, webpack-dev-serverで簡易サーバーを動かします。 ビルドwebpackサーバーを動かす
webpack-dev-serverlocalhost:8080/index.html へアクセス
0 件のコメント:
コメントを投稿