仕事では, 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に任せる
ビルドした後のjsを取り込んでいるだけです。この部分はもう基本的には変更しません。Redux Sample
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 件のコメント:
コメントを投稿