本文代碼模版:react-redux-webpack-boilerplatejavascript
好記性不如爛筆頭,以前陸續寫過幾篇關於玩物圈前端所用到技術棧的總結,如今在 玩物圈PC版 上線以前,將玩物圈前端用到技術棧總體簡單總結梳理下。css
前端基本框架圖html
webpack是一款模塊加載器兼打包工具,具體使用參考官方文檔,很詳細。前端
項目中的主要做用:java
模塊管理:模塊化管理js、css、image等文件node
按照模板生成html:主要使用了html-webpack-plugin插件,按照模版生成html文件,並注入指定的chunksreact
靜態資源管理,md5,路徑重定位webpack
生產配置git
var webpack = require('webpack'); var path = require('path'); var entry = require('./entry.js'); var templateConfig = require('./html.template.config.js').pro; var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('vendor', 'static/js/vendor.[hash:8].js'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var config = { entry: entry, output: { path: __dirname + '/product', publicPath: 'http://cdn.xx.com/', filename: 'static/js/[name].[chunkhash:8].js' }, resolve: { extensions: ['', '.js', '.jsx'],// 配置能夠不書寫的後綴名 root: path.join(__dirname, 'public/') //配置絕對路徑,alias、entry中會使用 }, module: { loaders: [ { test: /\.js[x]?$/, include: path.resolve(__dirname, 'public'), exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.(jpg|png|gif)$/, loader: 'url?limit=1024&name=static/images/[hash].[ext]'//小於1kb的圖片轉化爲base64,css中其餘的圖片地址會被體會爲打包的地址,此處用到了publicPath }, {test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?sourceMap')} ] }, plugins: [ commonsPlugin, new ExtractTextPlugin('static/css/[name].[chunkhash:8].css'), new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }), commonsPlugin ] }; for (var i = 0; i < templateConfig.length; i++) { config.plugins.push(new HtmlWebpackPlugin(templateConfig[i])); } module.exports = config;
參考文檔:github
Babel是一個普遍使用的轉碼器,能夠將ES6代碼轉爲ES5代碼,JSX語法代碼轉爲ES5代碼。
項目中主要使用Babel將源代碼ES六、JSX轉碼爲ES5。
React提供應用的 View 層,表現爲組件,具體參考官方文檔
主要知識點:
JSX (可選的)
組件(props、state、生命週期、事件、Form、幾個api)
Virtual Dom
參考:
Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。自己跟react沒有任何關係。
Redux 除了和 React 一塊兒用外,還支持其它界面庫。
Action(普通Action、異步Action)
普通Action,本質是JS普通對象
異步Action,使用了 Thunk middleware 異步 action
Reducer
( previousState, action ) => newState
處理數據邏輯
拆分和合並reducer(用 ES6 的 import、export 語法,很是方便)
Store
聯繫Action與Reducer的對象,爲應用提供state
相似 Express 或 Koa 框架中的中間件。它提供的是位於 action 被髮起以後,到達 reducer 以前的擴展。
中間件的設計使用了很是多的函數式編程的思想,包括:高階函數,複合函數,柯里化和ES6語法,源碼僅僅20行左右。
項目中主要使用了三個中間件,分別解決不一樣的問題。
thunkMiddleware:處理異步Action
apiMiddleware:統一處理API請求。通常狀況下,每一個 API 請求都至少須要 dispatch 三個不一樣的 action(請求前、請求成功、請求失敗),經過這個中間件能夠很方便處理。
loggerMiddleware:開發環境調試使用,控制檯輸出應用state日誌
參考:
react-redux的做用是鏈接(connect)store和容器組件的。store是redux提供的,容器組件是react提供的。
組織應用的組件
容器組件
展現組件
容器組件:位於應用最頂層的組件,用來與redux鏈接的。從redux中獲取數據做爲props。
展現組件:位於應用的中間或者子組件,是純粹的組件,與redux沒有關係。他們從本身的父組件獲取數據做爲props,他們的共同根組件是應用的惟一的容器組件。展現組件能夠維持少許的自身狀態信息。
react-redux僅僅提供兩個關鍵模塊:Provider和connect。
源碼:
import Provider from './components/Provider' import connect from './components/connect' export { Provider, connect }
Provider:是一個組件,接受一個store屬性和一個子組件(也就是上面說到的:store是redux提供的,容器組件是是react提供的。)
例子:
ReactDOM.render( <Provider store={store}> {/* note "routerState" here: important to pass it down */} <Handler routerState={routerState} /> </Provider>, document.getElementById('root') );
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]):connect返回一個函數,它接受一個React組件的構造函數做爲鏈接對象,最終返回鏈接好的組件構造函數。
例子:
import * as actionCreators from './actionCreators' function mapStateToProps(state) { return { todos: state.todos } } export default connect(mapStateToProps, actionCreators)(MyRootComponent)
參考:
目前主流的框架(Angular2,React,Koa,Redux)全面轉向ES6。項目中使用了部分ES6的明星特性。一開始我是拒絕的,不習慣,如今的感受是:很是方便,很是爽。
模塊化:組件按模塊編寫以及使用、Action和Reducer按模塊拆分合並、使用第三方模塊,這些在項目中都是使用的是ES6的Module特性,其中編寫React組件使用了ES6的Class特性。
例子:
import {Types} from '../constants/base/order'; export * from './base/user'; export {fetchCart} from './base/shopCart'; export {fetchOrder} from './base/order'; export function fetchPayResult(id) { return { url: '/mall/order/payResult/' + id, method: 'GET', types: ['REQUEST', Types.FETCH_PAY_RESULT, 'FAILURE'] }; } export function changePayType(payType) { return { type: Types.SELECT_PAY_TYPE, payType }; }
export class Counter extends React.Component { constructor(props) { super(props); this.state = {count: props.initialCount}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } } Counter.propTypes = { initialCount: React.PropTypes.number }; Counter.defaultProps = { initialCount: 0 };
var { types, url = '', mockUrl = '', method = 'GET', dataType = 'json', data = {} } = action;
箭頭函數:箭頭函數在項目中也用得比較多,簡化函數的編寫,React Stateless function components 的編寫。
const noop = ()=> false; let createItem = (item, index) =><Order order={item} key={index}/>; const Coupon = (props) => ( <li> <div className="coupon-tit">抵用券</div> <div className="coupon-price"><span>¥</span><strong>{props.coupon.price}</strong></div> <div className="coupon-info"> <p>{props.coupon.code}</p> <p className="time">{props.coupon.endTime}前可用</p> </div> </li> );
函數參數的默認值:典型的應用是編寫Reducer。
export function address(state = {}, action) { switch (action.type) { case Types.SELECT_ADDRESS: return objectAssign({}, action.payload); ...
href={`/pc/mall/order/confirm.html?${param}`}
return { mockUrl: '/static/mock/user.address.save.json', url: `/user/address/${id}`, method: 'PUT', data: {id, isDefault}, types: ['REQUEST', Types.SET_DEFAULT_ADDRESS, 'FAILURE'] };
export const setVisibilityFilter = (filter) => { return { type: 'SET_VISIBILITY_FILTER', filter } }
return { mockUrl: '/static/mock/user.address.save.json', url: `/user/address/${id}`, method: 'PUT', data: {id, isDefault}, types: ['REQUEST', Types.SET_DEFAULT_ADDRESS, 'FAILURE'] };
App.defaultProps = { user: {}, tips: {visible: false}, carts: [], visibleDropCart: false, visibleLoginDialog: false, switchLoginDialog() { }, switchTips() { }, switchDropCart() { } };
const noop = ()=> false; let createItem = (item, index) =><Order order={item} key={index}/>;
參考:
Gulp與Grunt同樣,也是一個自動任務運行器。它充分借鑑了Unix操做系統的管道(pipe)思想,不少人認爲,在操做上,它要比Grunt簡單。
項目中主要用到的功能:結合webpack使用、壓縮js、ESLint代碼檢查、壓縮css。
var gulp = require("gulp"); var gutil = require("gulp-util"); var minifyCss = require('gulp-minify-css'); var uglify = require('gulp-uglify'); var eslint = require('gulp-eslint'); var reporter = require('eslint-html-reporter'); var fs = require('fs'); var path = require('path'); var webpack = require("webpack"); var webpackConfigProduct = require("./webpack.production.config.js"); var webpackConfigDevelop = require("./webpack.development.config.js"); gulp.task("webpack", function(callback) { webpack(webpackConfigProduct, function(err, stats) { if (err) throw new gutil.PluginError("webpack", err); callback(); }); }); gulp.task("webpackDevelop", function(callback) { webpack(webpackConfigDevelop, function(err, stats) { if (err) throw new gutil.PluginError("webpack", err); callback(); }); }); var srcJsDir = './public/static/js/'; gulp.task('lint', function() { return gulp.src([srcJsDir + '**/*.js']) .pipe(eslint()) .pipe(eslint.format(reporter, function(results) { fs.writeFileSync(path.join(__dirname, 'lint-report.html'), results); }) ); }); gulp.task("minifyJs", ['webpack'], function() { return gulp.src("./product/**/*.js") .pipe(uglify({ output: { max_line_len: 100 } })) .pipe(gulp.dest("./product")); }); gulp.task("minifycssPro", ['webpack'], function() { return gulp.src("./product/**/*.css") .pipe(minifyCss()) .pipe(gulp.dest("./product")); }); gulp.task("minifycssDev", ['webpackDevelop'], function() { return gulp.src("./development/**/*.css") .pipe(minifyCss()) .pipe(gulp.dest("./development")); }); gulp.task('copyJson', function() { return gulp.src('./public/static/mock/**/*.json') .pipe(gulp.dest('./development/static/mock/')); }); gulp.task('product', ['minifycssPro', 'minifyJs']); gulp.task('default', ['minifycssDev', 'copyJson']);
參考: