本項目開發基於 React
+ Redux
+ React-Route
框架,利用 webpack
進行模塊化構建,前端編寫語言是 JavaScript ES6,利用 babel
進行轉換。javascript
|--- project |--- build // 項目打包編譯目錄 |--- src // 項目開發的源代碼 |--- actions // redux的動做 |--- components // redux的組件 |--- containers // redux的容器 |--- images // 靜態圖片 |--- mixins // 通用的函數庫 |--- reducers // redux的store操做 |--- configureStore.js // redux的store映射 |--- index.js // 頁面入口 |--- routes.js // 路由配置 |--- index.html // 入口文件 |--- .babelrc // babel配置 |--- main.js // webkit打包的殼子 |--- package.json // 包信息 |--- webpack.config.js // webpack配置文件 |--- readme.md
"dependencies": { "babel-polyfill": "^6.7.4", "base-64": "^0.1.0", "immutable": "^3.7.6", "isomorphic-fetch": "^2.2.1", "moment": "^2.13.0", "normalizr": "^2.0.1", "react": "^0.14.8", "react-datetimepicker": "^2.0.0", "react-dom": "^0.14.8", "react-redux": "^4.4.1", "react-redux-spinner": "^0.4.0", "react-router": "^2.0.1", "react-router-redux": "^4.0.1", "redux": "^3.3.1", "redux-immutablejs": "0.0.8", "redux-logger": "^2.6.1", "redux-thunk": "^2.0.1" }, "devDependencies": { "babel-core": "^6.7.5", "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-1": "^6.5.0", "css-loader": "^0.23.1", "file-loader": "^0.8.5", "img-loader": "^1.2.2", "less": "^2.6.1", "less-loader": "^2.2.3", "mocha": "^2.4.5", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "webpack": "^1.12.14" }
也算是實際體驗了一把webpack,不得不說,論React
最佳搭檔,非此貨莫屬!真的很強大,很好用。css
var webpack = require('webpack'); // 引入webpack模塊 var path = require('path'); // 引入node的path模塊 var nodeModulesPath = path.join(__dirname, '/node_modules'); // 設置node_modules目錄 module.exports = { // 配置入口(此處定義了雙入口) entry: { bundle: './src/index', vendor: ['react', 'react-dom', 'redux'] }, // 配置輸出目錄 output: { path: path.join(__dirname, '/build'), publicPath: "/assets/", filename: 'bundle.js' }, module: { noParse: [ path.join(nodeModulesPath, '/react/dist/react.min'), path.join(nodeModulesPath, '/react-dom/dist/react-dom.min'), path.join(nodeModulesPath, '/redux/dist/redux.min'), ], // 加載器 loaders: [ // less加載器 { test: /\.less$/, loader: 'style!css!less' }, // babel加載器 { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, // 圖片加載器(圖片超過8k會自動轉base64格式) { test: /\.(gif|jpg|png)$/, loader: "url?limit=8192&name=images/[name].[hash].[ext]"}, // 加載icon字體文件 { test: /\.(woff|svg|eot|ttf)$/, loader: 'url?limit=50000&name=fonts/[name].[hash].[ext]'} ] }, // 外部依賴(不會打包到bundle.js裏) externals: { 'citys': 'Citys' }, // 插件 plugins: [ //new webpack.HotModuleReplacementPlugin(), // 版本上線時開啓 new webpack.DefinePlugin({ // 定義生產環境 "process.env": { NODE_ENV: JSON.stringify("production") } }), //new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), // 版本上線時開啓 // 公共部分會被抽離到vendor.js裏 new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), // 比對id的使用頻率和分佈來得出最短的id分配給使用頻率高的模塊 new webpack.optimize.OccurenceOrderPlugin(), // 容許錯誤不打斷程序 new webpack.NoErrorsPlugin() ], };
爲了瘦身你的js(還有你的css,若是你用到css-loader的話)webpack支持一個簡單的配置項:html
new webpack.optimize.UglifyJsPlugin()
這是一種簡單而有效的方法來優化你的webapp。而webpack還提供了modules 和 chunks ids 來區分他們倆。利用下面的配置項,webpack就可以比對id的使用頻率和分佈來得出最短的id分配給使用頻率高的模塊。前端
new webpack.optimize.OccurenceOrderPlugin()
入口文件對於文件大小有較高的優先級(入口文件壓縮優化率儘可能的好)java
若是你使用了一些有着很酷的依賴樹的庫,那麼它可能存在一些文件是重複的。webpack能夠找到這些文件並去重。這保證了重複的代碼不被大包到bundle文件裏面去,取而代之的是運行時請求一個封裝的函數。不會影響語義node
new webpack.optimize.DedupePlugin()
這個功能可能會增長入口模塊的一些花銷react
當coding的時候,你可能已經添加了許多分割點來按需加載。但編譯完了以後你發現有太多細小的模塊形成了很大的HTTP損耗。幸運的是Webpack能夠處理這個問題,你能夠作下面兩件事情來合併一些請求:webpack
Limit the maximum chunk count withgit
new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15})
Limit the minimum chunk size withes6
new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000})
Webpack經過合併來管理這些異步加載的模塊(合併更多的時候發生在當前這個chunk有複用的地方)。文件只要在入口頁面加載的時候沒有被引入,那麼就不會被合併到chunk裏面去。
Webpack 是爲單頁應用量身定作的 你能夠把app拆成不少chunk,這些chunk由路由來加載。入口模塊僅僅包含路由和一些庫,沒有別的內容。這麼作在用戶經過導航瀏覽表現很好,可是初始化頁面加載的時候你須要2個網絡請求:一個是請求路由,一個是加載當前內容。
若是你利用HTML5的HistoryAPI 來讓URL影響當前內容頁的話。你的服務器能夠知道那個內容頁面將被客戶端請求。爲了節約請求數,服務端能夠把要請求的內容模塊放到響應頭裏面:以script標籤的形式來添加,瀏覽器將並行的加載這倆請求。
<script src="entry-chunk.js" type="text/javascript" charset="utf-8"></script> <script src="3.chunk.js" type="text/javascript" charset="utf-8"></script>
你能夠從build stas裏面提取出chunk的filename (stats-webpack-plugin )
當編譯一個多頁面的app時,你想要在頁面之間共享一些代碼。這在webpack看來很簡單的:只須要和多個入口文件一塊兒編譯就好
webpack p1=./page1 p2=./page2 p3=./page3 [name].entry-chunk.js
module.exports = { entry: { p1: "./page1", p2: "./page2", p3: "./page3" }, output: { filename: "[name].entry.chunk.js" } }
由上面能夠產出多個入口文件
p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js
可是能夠增長一個chunk來共享她們中的一些代碼。 若是你的chunks有一些公用的modules,那我推薦一個很酷的插件CommonsChunkPlugin,它能辨別共用模塊並把他們放倒一個文件裏面去。你須要在你的頁面裏添加兩個script標籤來分別引入入口文件和共用模塊文件。
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1: "./page1", p2: "./page2", p3: "./page3" }, output: { filename: "[name].entry.chunk.js" }, plugins: [ new CommonsChunkPlugin("commons.chunk.js") ] }
由上面能夠產出入口文件
p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js
和共用文件
commons.chunk.js
在頁面中要首先加載 commons.chunk.js 在加載xx.entry.chunk.js 你能夠出實話不少個commons chunks ,經過選擇不一樣的入口文件。而且你能夠堆疊使用這些commons chunks。
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1: "./page1", p2: "./page2", p3: "./page3", ap1: "./admin/page1", ap2: "./admin/page2" }, output: { filename: "[name].js" }, plugins: [ new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]), new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"]) ] };
輸出結果:
page1.html: commons.js, p1.js page2.html: commons.js, p2.js page3.html: p3.js admin-page1.html: commons.js, admin-commons.js, ap1.js admin-page2.html: commons.js, admin-commons.js, ap2.js
另外你能夠將多個共用文件打包到一個共用文件中。
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1: "./page1", p2: "./page2", commons: "./entry-for-the-commons-chunk" }, plugins: [ new CommonsChunkPlugin("commons", "commons.js") ] };
做爲一個後端出身的前端工程師,寫簡單的css實在沒有那種代碼可配置和結構化的快感。因此引入less是個不錯的選擇,不管是針對代碼後期的管理,仍是提升代碼的複用能力。
global.less
這個是全局均可以調用的方法庫,我習慣把 項目的配色、各類字號、用於引入混出的方法等寫在這裏,其餘container
頁面經過@import
方式引入它,就可使用裏面的東西。不過定義它時要注意如下兩點:
第一,這個less裏只能存放變量和方法,less編譯時會忽略它們,只在調用它們的地方纔編譯成css。因此爲了防止代碼重複,請不要在這裏直接定義樣式,而是用一個方法把它們包起來,表示一個用途。
第二,這個less裏的方法若是是針對某些具體標籤訂義樣式的,只能初始化一次,建議在單頁的入口container
裏作,這樣好維護。好比reset()
(頁面標籤樣式初始化),這個方法放在入口container
的 login.less
裏調用且全局只調用一次。
下面是個人global.less
經常使用的一些模塊
/** * @desc 一些全局的less * @createDate 2016-05-16 * @author Jafeney <692270687@qq.com> **/ // 全局配色 @g-color-active: #ff634d; //活躍狀態的背景色(橘紅色) @g-color-info: #53b2ea; //通常用途的背景色(淺藍色) @g-color-primary: #459df5; //主要用途的背景色 (深藍色) @g-color-warning: #f7cec8; //用於提示的背景色 (橘紅色較淺) @g-color-success: #98cf07; //成功狀態的背景色 (綠色) @g-color-fail: #c21f16; //失敗狀態的背景色 (紅色) @g-color-danger: #ff634d; //用於警示的背景色 (橘紅色) @g-color-light: #fde2e1; //高飽合度淡色的背景色(橘紅) // 全局尺寸 @g-text-default: 14px; @g-text-sm: 12px; @g-text-lg: 18px; // 全局使用的自定義icon(這樣寫的好處是webpack打包時自動轉base64) @g-icon-logo: url("../images/logo.png"); @g-icon-logoBlack: url("../images/logoBlack.png"); @g-icon-phone: url("../images/phone.png"); @g-icon-message: url("../images/message.png"); @g-icon-help: url("../images/help.png"); @g-icon-down: url("../images/down.png"); @g-icon-top: url("../images/top.png"); @g-icon-home: url("../images/home.png"); @g-icon-order: url("../images/order.png"); @g-icon-cart: url("../images/cart.png"); @g-icon-source: url("../images/source.png"); @g-icon-business: url("../images/business.png"); @g-icon-finance: url("../images/finance.png"); @g-icon-account: url("../images/account.png"); // .... // 背景色 @g-color-grey1: #2a2f33; //黑色 @g-color-grey2: #363b3f; //深灰色 @g-color-grey3: #e5e5e5; //灰色 @g-color-grey4: #efefef; //淺灰色 @g-color-grey5: #f9f9f9; //很淺 @g-color-grey6: #ffffff; //白色 // 全局邊框 @g-border-default: #e6eaed; @g-border-active: #53b2ea; @g-border-light: #f7dfde; // 經常使用的border-box盒子模型 .border-box() { box-sizing: border-box; -ms-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -webkit-box-sizing: border-box; } // 模擬按鈕效果 .btn() { cursor: pointer; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; &:hover { opacity: .8; } &.disabled { &:hover { opacity: 1; cursor: not-allowed; } } } // 超出部分處理 .text-overflow() { overflow: hidden; text-overflow: ellipsis; -o-text-overflow: ellipsis; -webkit-text-overflow: ellipsis; -moz-text-overflow: ellipsis; white-space: nowrap; } // reset styles .reset() { // .... } // 一些原子class .atom() { .cp { cursor: pointer; } .ml-5 { margin-left: 5px; } .mr-5 { margin-right: 5px; } .ml-5p { margin-left: 5%; } .mr-5p { margin-right: 5%; } .mt-5 { margin-top: 5px; } .txt-center { text-align: center; } .txt-left { text-align: left; } .txt-right { text-align: right; } .fr { float: right; } .fl { float: left; } }
component
的less爲了下降組件的耦合性,每一個組件的less必須單獨寫,樣式跟着組件走,一個組件一個less,不要有其餘依賴,保證組件的高移植能力。
並且組件應該針對用途提供幾套樣式方案,好比button
組件,咱們能夠針對顏色提供不一樣的樣式,以樣式組合的方式提供給外部使用。
// 下面的變量能夠針對不一樣的需求進行配置 @color-primary: #459df5; @color-warning: #f7cec8; @color-success: #98cf07; @color-fail: #c21f16; .btn { cursor: pointer; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; display: inline-block; box-sizing: border-box; -webkit-box-sizing: border-box; -ms-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; text-align: center; // 鼠標放上時 &:hover { opacity: .8; } // 按鈕不可用時 &.disabled { &:hover { opacity: 1; cursor: not-allowed; } } // 填充式按鈕 &.full { color: #fff; &.primary { background-color: @color-primary; border: 1px solid @color-primary; } // .... } // 邊框式按鈕 &.border { background-color: #fff; &.primary { color: @color-primary; border: 1px solid @color-primary; } // ... } }
container
的less同上,每一個container
一個less文件,能夠複用的模塊儘可能封裝成component
,而不是偷懶複製幾行樣式過來,這樣雖然方便一時,但隨着項目的迭代,後期的冗餘代碼會多得超出你的想象。
若是遵循組件化的設計思想,你會發現container
裏其實只有一些佈局和尺寸定義相關的代碼,很是容易維護。
這是大型項目的設計要領,除此以外就是大局觀的培養,這點尤其重要,項目一拿來不要立刻就動手寫頁面,而是應該多花些時間在代碼的設計上,把全局的東西剝離出來,越細越好;把可複用的模塊設計成組件,思考組件的拓展性和不一樣的用途,記住—— 結構上儘可能減小依賴關係,保持組件的獨立性,而用途上多考慮功能的聚合,即所謂的低耦合高聚合。
不過實際項目不可能每一個組件都是獨立存在的,有時咱們爲了進一步減小代碼量,會把一些經常使用的組件整合成一個大組件來使用,即複合組件。因此每一個項目實際上存在一級組件(獨立)和二級組件(複合)。一級組件能夠隨意遷移,而二級組件是針對實際場景而生的,二者並無好壞之分,一切都爲了高效地生產代碼,存在即合理。
本項目的React代碼都用JavaScript的ES6風格編寫,代碼很是地優雅,並且語言自身支持模塊化,不再用依賴Browserify
、RequireJS
等工具了,很是爽。若是你不會ES6,建議去翻一翻阮一峯老師的《ES6標準入門》
入口模塊index.js
放在src
的根目錄,是外部調用的入口。
import React from 'react' import { render } from 'react-dom' // 引入redux import { Provider } from 'react-redux' // 引入router import { Router, hashHistory } from 'react-router' import { syncHistoryWithStore } from 'react-router-redux' import routes from './routes' import configureStore from './configureStore' const store = configureStore(hashHistory) // 路由的store const history = syncHistoryWithStore(hashHistory, store) // 路由的歷史紀錄(會寫入到瀏覽器的歷史紀錄) render( ( <Provider store={store}> <Router history={history} routes={routes} /> </Provider> ), document.getElementById('root') )
這裏主要應用了react-route
組件來製做哈希路由,使用方式很簡單,和ReactNative裏的Navigator組件相似。
import React from 'react' import { Route } from 'react-router' import Manager from './containers/manager' import Login from './containers/Login/' import Register from './containers/Register/' import Password from './containers/Password/' import Dashboard from './containers/Dashboard/' const routes = ( <Route> <Route path="" component={Manager}> // 主容器 <Route path="/" component={Dashboard} /> // 儀表盤 // .... 各模塊的container </Route> <Route path="login" component={Login} /> // 登陸 <Route path="register" component={Register} /> // 註冊 <Route path="password" component={Password} /> // 找回密碼 </Route> ) export default routes
從調用關係來看以下所示:
store.dispatch(action) --> reducer(state, action) --> final state
來個實際的例子:
// reducer方法, 傳入的參數有兩個 // state: 當前的state // action: 當前觸發的行爲, {type: 'xx'} // 返回值: 新的state var reducer = function(state, action){ switch (action.type) { case 'add_todo': return state.concat(action.text); default: return state; } }; // 建立store, 傳入兩個參數 // 參數1: reducer 用來修改state // 參數2(可選): [], 默認的state值,若是不傳, 則爲undefined var store = redux.createStore(reducer, []); // 經過 store.getState() 能夠獲取當前store的狀態(state) // 默認的值是 createStore 傳入的第二個參數 console.log('state is: ' + store.getState()); // state is: // 經過 store.dispatch(action) 來達到修改 state 的目的 // 注意: 在redux裏,惟一可以修改state的方法,就是經過 store.dispatch(action) store.dispatch({type: 'add_todo', text: '讀書'}); // 打印出修改後的state console.log('state is: ' + store.getState()); // state is: 讀書 store.dispatch({type: 'add_todo', text: '寫做'}); console.log('state is: ' + store.getState()); // state is: 讀書,寫做
store:對flux有了解的同窗應該有所瞭解,store在這裏表明的是數據模型,內部維護了一個state變量,用例描述應用的狀態。store有兩個核心方法,分別是getState、dispatch。前者用來獲取store的狀態(state),後者用來修改store的狀態。
// 建立store, 傳入兩個參數 // 參數1: reducer 用來修改state // 參數2(可選): [], 默認的state值,若是不傳, 則爲undefined var store = redux.createStore(reducer, []); // 經過 store.getState() 能夠獲取當前store的狀態(state) // 默認的值是 createStore 傳入的第二個參數 console.log('state is: ' + store.getState()); // state is: // 經過 store.dispatch(action) 來達到修改 state 的目的 // 注意: 在redux裏,惟一可以修改state的方法,就是經過 store.dispatch(action) store.dispatch({type: 'add_todo', text: '讀書'});
action:對行爲(如用戶行爲)的抽象,在redux裏是一個普通的js對象。redux對action的約定比較弱,除了一點,action必須有一個type字段來標識這個行爲的類型。因此,下面的都是合法的action
{type:'add_todo', text:'讀書'} {type:'add_todo', text:'寫做'} {type:'add_todo', text:'睡覺', time:'晚上'}
reducer:一個普通的函數,用來修改store的狀態。傳入兩個參數 state、action。其中,state爲當前的狀態(可經過store.getState()得到),而action爲當前觸發的行爲(經過store.dispatch(action)調用觸發)。reducer(state, action) 返回的值,就是store最新的state值。
// reducer方法, 傳入的參數有兩個 // state: 當前的state // action: 當前觸發的行爲, {type: 'xx'} // 返回值: 新的state var reducer = function(state, action){ switch (action.type) { case 'add_todo': return state.concat(action.text); default: return state; } }
在沒有遁入React以前,我是一個DOM操做控,不管是jQuery
仍是zepto
,我在頁面交互的實現上用的最多的就是DOM操做,把複雜的交互一步一步經過選擇器和事件委託綁定到document上,而後逐個連貫起來。
$(document).on('event', 'element', function(e){ e.preventDefault(); var that = this; var parent = $(this).parent(); var siblings = $(this).siblings(); var children = $(this).children(); // ..... });
這是jQuery
式的編程思惟,React
和它大相徑庭。React
的設計是基於組件化的,每一個組件經過生命週期維護統一的state
,state
改變,組件便update
,從新觸發render
,即從新渲染頁面。而這個過程操做的實際上是內存裏的虛擬DOM
,而不是真正的DOM節點,加上其內部的差別更新算法,因此性能上比傳統的DOM操做要好。
舉個簡單的例子:
如今要實現一個模態組件,若是用jQuery式的編程思惟,很習慣這麼寫:
/** * @desc 全局模態窗口 **/ var $ = window.$; var modal = { confirm: function(opts) { var title = opts.title || '提示', content = opts.content || '提示內容', callback = opts.callback; var newNode = [ '<div class="mask" id="J_mask">', '<div class="modal-box">', '<h2>', title, '</h2>', '<p>', content, '</p>', '<div class="mask-btns">', '<span id="J_cancel">取消</span>', '<span id="J_confirm">肯定</span>', '</div>', '</div>', '</div>', ].join(''); $('#J_mask').remove(); $('body').append(newNode); $('#J_cancel').on('click', function() { $('#J_mask').remove(); }); $('#J_confirm').on('click', function() { if (typeof callback === 'function') { callback(); } $('#J_mask').remove(); }); } }; module.exports = modal;
而後在頁面的JavaScript裏經過選擇器觸發模態和傳遞參數。
var Modal = require('modal'); var $ = window.$; var app = (function() { var init = function() { eventBind(); }; var eventBind = function() { $(document).on('click', '#btnShowModal', function() { Modal.confirm({ title: '提示', content: '你好!世界', callback: function() { console.log('Hello World'); } }); }); }; init(); })();
若是採用React
式的編程思惟,它應該是這樣的:
/** * @desc 全局模態組件 Component * @author Jafeney * @createDate 2016-05-17 * */ import React, { Component } from 'react' import './index.less' class Modal extends Component { constructor() { super() this.state = { jsMask: 'mask hidden' } } show() { this.setState({ jsMask: 'mask' }) } close() { this.setState({ jsMask: 'mask hidden' }) } confirm() { this.props.onConfirm && this.props.onConfirm() } render() { return ( <div className={this.state.jsMask}> <div className="modal-box" style={this.props.style}> <div className="header"> <h3>{ this.props.title }</h3> <span className="icon-remove closed-mask" onClick={()=>this.close()}></span> </div> <div className="content"> { this.props.children } </div> <div className="mask-btns"> <span className="btn-full-danger" onClick={()=>this.confirm()}>{ this.props.confirmText || '肯定' }</span> { this.props.showCancel && (<span className="btn-border-danger" onClick={()=>this.close()}>取消</span>) } </div> </div> </div> ); } } export default Modal
而後在container
的render()
函數裏經過標籤的方式引入,並經過點擊觸發。
import {React, component} from 'react'; import Modal from 'Modal'; class App extends Component { render() { <div> <button onClick = {()=> {this.refs.modal.show()}} <Modal title={"提示"} style={{width: 420, height: 200}} ref={(ref)=> this.modal = ref} onConfirm={()=>this.onModalConfirm()}> <p className="tips">Hello world!</p> </Modal> </div> } } export default App
你會發現,上面的代碼並無刻意地操做某個DOM元素的樣式,而是經過改變組件的state
去觸發自身的渲染函數。換句話說,咱們不須要寫繁瑣的DOM操做,而是靠改變組件的state
控制組件的交互和各類變化。這種思惟方式的好處等你熟悉React
以後天然會明白,能夠大大地減小後期的代碼量。
前面提到組件的state
改變即觸發render()
,React
內部雖然作了一些算法上的優化,可是咱們能夠結合Immutable
作進一步的渲染優化,讓頁面更新渲染速度變得更快。
/** * @desc PureRender 優化渲染 **/ import React, { Component } from 'react' import Immutable from 'immutable'; export default { // 深度比較 deepCompare: (self, nextProps, nextState) => { return !Immutable.is(self.props, nextProps) || !Immutable.is(self.state, nextState) }, // 阻止不必的渲染 loadDetection: (reducers=[])=> { for (let r of reducers) { if (!r.get('preload')) return (<div />) } } }
這樣咱們在container
的render()
函數裏就能夠調用它進行渲染優化
import React, { Component } from 'react' import PureRenderMixin from '../../mixins/PureRender'; class App extends Component { render() { let { actions, account, accountLogs, bankBind } = this.props; // 數據導入檢測 let error = PureRenderMixin.loadDetection([account, accountLogs, bankBind]) // 若是和上次沒有差別就阻止組件從新渲染 if (error) return error return ( <div> // something ... </div> ); } }
其實Redux
最大的做用就是有效減小代碼量,把繁瑣的操做經過 action ----> reducer ----> store
進行抽象,最後維護統一的state
。對於頁面的全局模塊,簡單地封裝成mixin
來調用仍是不夠的,好比全局的request
模塊,下面介紹如何用Redux
進行改造。
首先在types.js
裏進行聲明:
// request export const REQUEST_PEDDING = 'REQUEST_PEDDING'; export const REQUEST_DONE = 'REQUEST_DONE'; export const REQUEST_ERROR = 'REQUEST_ERROR'; export const REQUEST_CLEAN = 'REQUEST_CLEAN'; export const REQUEST_SUCCESS = 'REQUEST_SUCCESS';
而後編寫action
:
/** * @desc 網絡請求模塊的actions **/ // fetch 須要使用 Promise 的 polyfill import { pendingTask, // The action key for modifying loading state begin, // The action value if a "long" running task begun end // The action value if a "long" running task ended } from 'react-redux-spinner'; import 'babel-polyfill' import fetch from 'isomorphic-fetch' import Immutable from 'immutable' import * as CONFIG from './config'; //請求的配置文件 import * as TYPES from './types'; export function request(route, params, dispatch, success=null, error=null, { method='GET', headers={}, body=null } = {}) { dispatch({type: TYPES.REQUEST_PEDDING, [ pendingTask ]: begin}) // 處理query const p = params ? '?' + Object.entries(params).map( (i)=> `${i[0]}=${encodeURI(i[1])}` ).join('&') : '' const uri = `${ CONFIG.API_URI }${ route }${ p }` let data = {method: method, headers: headers} if (method!='GET') data.body = body fetch(uri, data) .then((response) => { dispatch({type: TYPES.REQUEST_DONE, [ pendingTask ]: end}) return response.json() }) .then((data) => { if (String(data.code) == '0') { if (method !== 'GET' ) dispatch({type: TYPES.REQUEST_SUCCESS}); success && success(data); } else { console.log(data.error) dispatch({type: TYPES.REQUEST_ERROR, ...data}) error && error(data) } }) .catch((error) => { console.warn(error) }) } export function requestClean() { return { type: TYPES.REQUEST_CLEAN } }
而後編寫對應的reducer
操做state
:
import Immutable from 'immutable'; import * as TYPES from '../actions/types'; import { createReducer } from 'redux-immutablejs' export default createReducer(Immutable.fromJS({status: null, error: null}), { [TYPES.REQUEST_ERROR]: (state, action) => { return state.merge({ status: 'error', code: action.code, error: Immutable.fromJS(action.error), }) }, [TYPES.REQUEST_CLEAN]: (state, action) => { return state.merge({ status: null, error: null, }) }, [TYPES.REQUEST_SUCCESS]: (state, action) => { return state.merge({ status: 'success', error: null, }) } })
而後在reducers
的index.js
裏對外暴露接口
export request from './request'
爲何要作這一步呢?由於咱們須要在configureStore.js
裏利用combineReducers
對全部的reducer
進行進一步的結合處理:
import { createStore, combineReducers, compose, applyMiddleware } from 'redux' import thunkMiddleware from 'redux-thunk' import createLogger from 'redux-logger' import * as reducers from './reducers' import { routerReducer, routerMiddleware } from 'react-router-redux' import { pendingTasksReducer } from 'react-redux-spinner' export default function configureStore(history, initialState) { const reducer = combineReducers({ ...reducers, routing: routerReducer, pendingTasks: pendingTasksReducer, }) const store = createStore( reducer, initialState, compose( applyMiddleware( thunkMiddleware, routerMiddleware(history) ) ) ) return store }
接下來就能夠在container
裏使用了,好比登陸模塊:
/** * @desc 登陸模塊 container * @createDate 2016-05-16 * @author Jafeney<692270687@qq.com> **/ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { replace } from 'react-router-redux' import { login } from '../../actions/user' import { requestClean } from '../../actions/request' import CheckUserMixin from '../../mixins/CheckUser' import PureRenderMixin from '../../mixins/PureRender' import '../style.less'; class Login extends Component { constructor() { super() } shouldComponentUpdate(nextProps, nextState) { // 若是已經登陸不觸發深度比較 if (nextProps.user.getIn(['login', 'status'])=='logged') { this.toMain() return true } return PureRenderMixin.deepCompare(this, nextProps, nextState) } // 檢查登陸態 componentDidMount() { let { user } = this.props; if (CheckUserMixin.isLogged(user)) this.toMain() } // 初始化頁面 toMain() { this.props.actions.replace('/') this.props.actions.requestClean() } // 執行登陸 login() { const userName = this.refs['J_username'].value, password = this.refs['J_password'].value if (userName && password) { this.props.actions.login({username: userName, password: password}) } } // 綁定回車事件 onEnter(event) { var e = event || window.event || arguments.callee.caller.arguments[0]; if(e && e.keyCode==13) { // enter 鍵 this.login() } } render() { let { user } = this.props return ( <div className="wrapper" onKeyPress={()=>this.onEnter()}> <div className="containers"> <div className="logo"></div> <div className="content"> <div className="header">會員登陸</div> <div className="mainer"> <div className="input-group"> <input ref="J_username" type="text" placeholder="手機號碼" className="input" /> <label className="check-info" ref="J_username-check"></label> </div> <div className="input-group"> <input ref="J_password" type="password" placeholder="登陸密碼" className="input" /> <label className="check-info" ref="J_password-check"></label> </div> <div className="input-group"> <span ref="J_login" onClick={()=>this.login()} className="login-btn">登陸</span> <span className="login-info"> <a ref="J_register" href="#/register" className="register">免費註冊</a> | <a ref="J_forget" href="#/password" className="forget">忘記密碼 ?</a> </span> </div> <div className="form-error"> { user.getIn(['login', 'error', 'message']) } </div> </div> </div> </div> </div> ) } } // 下面是redux的核心方法 function mapStateToProps(state) { return { user: state.user } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ login, requestClean, replace }, dispatch) } } export default connect(mapStateToProps, mapDispatchToProps)(Login)
注意:經過以上方式,在組件內部
actions
裏掛載的方法就能夠經過this.props
取得了。
@歡迎關注個人 github
和 我的博客 -Jafeney