玩物圈前端技術棧總結(React+Redux)

本文代碼模版:react-redux-webpack-boilerplatejavascript

好記性不如爛筆頭,以前陸續寫過幾篇關於玩物圈前端所用到技術棧的總結,如今在 玩物圈PC版 上線以前,將玩物圈前端用到技術棧總體簡單總結梳理下。css

前端基本框架圖html

圖片描述

一、webpack

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

Babel是一個普遍使用的轉碼器,能夠將ES6代碼轉爲ES5代碼,JSX語法代碼轉爲ES5代碼。
項目中主要使用Babel將源代碼ES六、JSX轉碼爲ES5。

三、React

React提供應用的 View 層,表現爲組件,具體參考官方文檔

主要知識點:

  • JSX (可選的)

  • 組件(props、state、生命週期、事件、Form、幾個api)

  • Virtual Dom

參考:

四、Redux(單向數據流)

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。自己跟react沒有任何關係。

Redux 除了和 React 一塊兒用外,還支持其它界面庫。

4.1 基本思想

能夠參考Redux:一種更優雅的 Flux 實現

  • Action(普通Action、異步Action)

    • 普通Action,本質是JS普通對象

    • 異步Action,使用了 Thunk middleware 異步 action

  • Reducer

    • ( previousState, action ) => newState

    • 處理數據邏輯

    • 拆分和合並reducer(用 ES6 的 import、export 語法,很是方便)

  • Store

    • 聯繫Action與Reducer的對象,爲應用提供state

4.2 中間件Middleware

相似 Express 或 Koa 框架中的中間件。它提供的是位於 action 被髮起以後,到達 reducer 以前的擴展。
中間件的設計使用了很是多的函數式編程的思想,包括:高階函數,複合函數,柯里化和ES6語法,源碼僅僅20行左右。
項目中主要使用了三個中間件,分別解決不一樣的問題。

  • thunkMiddleware:處理異步Action

  • apiMiddleware:統一處理API請求。通常狀況下,每一個 API 請求都至少須要 dispatch 三個不一樣的 action(請求前、請求成功、請求失敗),經過這個中間件能夠很方便處理。

  • loggerMiddleware:開發環境調試使用,控制檯輸出應用state日誌

參考:

五、react-redux

react-redux的做用是鏈接(connect)store和容器組件的。store是redux提供的,容器組件是react提供的。

5.1 組織應用的組件

  • 組織應用的組件

    • 容器組件

    • 展現組件

容器組件:位於應用最頂層的組件,用來與redux鏈接的。從redux中獲取數據做爲props。
展現組件:位於應用的中間或者子組件,是純粹的組件,與redux沒有關係。他們從本身的父組件獲取數據做爲props,他們的共同根組件是應用的惟一的容器組件。展現組件能夠維持少許的自身狀態信息。

5.2 鏈接Store與組件

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)

參考:

六、ES6

目前主流的框架(Angular2,React,Koa,Redux)全面轉向ES6。項目中使用了部分ES6的明星特性。一開始我是拒絕的,不習慣,如今的感受是:很是方便,很是爽。

6.1 Class和Module

模塊化:組件按模塊編寫以及使用、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 };

6.2 變量結構賦值

var {
      types,
      url = '',
      mockUrl = '',
      method = 'GET',
      dataType = 'json',
      data = {}
      } = action;

6.3 函數的擴展:箭頭函數、函數參數的默認值

  • 箭頭函數:箭頭函數在項目中也用得比較多,簡化函數的編寫,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);
...

6.4 字符串擴展:模板字符串

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']
  };

6.5 對象的擴展:屬性的簡潔表示法

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() {
  }
};

6.6 let和const

const noop = ()=> false;

let createItem = (item, index) =><Order order={item} key={index}/>;

參考:

七、Gulp

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']);

參考:

相關文章
相關標籤/搜索