純手工構建React+Redux+Webpack腳手架

React作為一個前端核心庫,建立了良好的JS庫生態,出現了大量的第三方JS庫用來支持React構建適合自己項目的框架。這篇內容,我們將使用redux與wepack來構建一個MVVM模式的React腳手架

開發環境

  • macOS
  • node
  • npm
  • atom
    注:如連接無法打開請使用代理

構建好的目錄

react_cli
├── build 
├── node_modules
├── src
├──├──assets
├──├──components
├──├──config
├──├──containers
├──├──redux
├──├──├──actions
├──├──├──configureStore
├──├──├──constants
├──├──├──reducers
├──├──index.jsx
├── test
├── .babelrc
├── package.json
├── webpack.config.js

npm初始化package.json

? mkdir react-cli       #新建項目文件夾
? cd react-cli          #進入
? npm init  #初始化package.json

name: (react_cli) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: Asir
license: (ISC) 

根據提示輸入內容,也可以直接回車,然后在package.json文件中修改

安裝React,基礎框架

? npm install --save react react-dom

├── react@15.4.2
├── react-dom@15.4.2

安裝webpack和webpack-dev-server

? npm install --save webpack webpack-dev-server

├── webpack@2.3.2 #用于將代碼整體打包壓縮輸出的工具
├── webpack-dev-server@2.4.2 #用于開發模式代碼調試與熱更新

安裝配置webpack用到的庫

  • babel相關的庫,用來打包編譯轉換為目前主流瀏覽器支持的JS語法
? npm install --save-dev babel babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-3 
├── babel@6.23.0 
├── babel-loader@6.4.1 
├── babel-preset-es2015@6.24.0  #打包編譯ES6轉ES5的庫
├── babel-preset-react@6.23.0   #打包編譯jsx語法
├── babel-preset-stage-3@6.22.0 #打包編譯async|await語法

更多babel相關請查閱官方文檔中文網

  • style相關庫,用來打包css,編譯less、sass
? npm install --save-dev  style-loader css-loader less-loader sass-loader less node-sass 
├── style-loader@0.16.1 
├── css-loader@0.27.3 
├── less-loader@4.0.2 
├── sass-loader@6.0.3 
├── less@2.7.2
├── node-sass@4.5.1
  • path 用于拼接路徑
? npm install --save-dev path 
├── path@0.12.7
  • html-webpack-plugin 模板插件用于導出html時,可以使用模板
? npm install --save-dev html-webpack-plugin 
├── html-webpack-plugin@2.28.0

創建并配置webpack.config.js

? atom webpack.config.js

var path = require('path');
var webpack = require('webpack');
var HtmlwebpackPlugin = require('html-webpack-plugin');

var ROOT_PATH = path.resolve(__dirname);
var APP_PATH = path.resolve(ROOT_PATH, 'src');
var BUILD_PATH = path.resolve(ROOT_PATH, 'build');

module.exports= {
  entry:  path.resolve(APP_PATH, 'index.jsx'),
  output: {
    path: BUILD_PATH,
    filename: 'bundle.js'
  },
  //babel重要的loader在這里
  module: {
    rules: [
      {
        test: /\.(less|scss|css)$/,
        loaders: ['css-loader', 'sass-loader', 'less-loader']
      },
      {
        test: /\.jsx?$/,
        loader: "babel-loader",
        include: APP_PATH,
      }
    ]
  },

  devtool: 'eval-source-map', //開發環境

  devServer: {
   compress: true, // 啟用Gzip壓縮
   historyApiFallback: true, // 為404頁啟用多個路徑
   hot: true, // 模塊熱更新,配置HotModuleReplacementPlugin
   https: false, // 適用于ssl安全證書網站
   noInfo: true, // 只在熱加載錯誤和警告
   // ...
 },

 plugins: [
   new HtmlwebpackPlugin({
    title: '記賬本',
    template: 'build/template/index.html'
  })
 ],
}

更多webpack配置查看http://webpack.github.io/

創建.babelrc,用于babel調用的插件配置

? atom .babelrc

{
  "presets": ["es2015","react", "stage-3"]
}

更多babel配置查看https://babeljs.io/

安裝Redux及相關組件

npm install --save react-redux
npm install --save redux redux-thunk redux-logger 
npm install --save redux-immutablejs immutable

關于immutable的使用請參考http://www.lxweimin.com/p/dec712858b27

定義Action`s type

在目錄src/redux/constants下新建index.js

export const EXAMPLE = 'EXAMPLE'

定義reducer

在目錄src/redux/reducers下新建index.js

import { combineReducers } from 'redux'
import { createReducer } from 'redux-immutablejs'
import { fromJS } from 'immutable'
import {
  EXAMPLE
} from '../constants'

const example = createReducer(fromJS({
  title: "項目構建成功"
}),{
  [EXAMPLE]: (state, action) => {
    return state.merge({
          title: action.payload.title
    })
  }
})

const rootReducer = combineReducers({
  example
})

export default rootReducer

生成store對象用于管理react中的state

在目錄src/redux/configureStore下新建index.js

? cd src/redux/configureStore && atom index.js 

index.js內容如下

import {
  createStore,
  applyMiddleware
} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
import createLogger from 'redux-logger';

const logger = createLogger({
  level: 'info',
  logger: console,
  collapsed: true
})
const createStoreWithMiddleware = process.env.NODE_ENV === 'development' ? applyMiddleware(
  thunk, logger
)(createStore) : applyMiddleware(
  thunk
)(createStore)

export default configureStore(initialState) {
  const store = createStoreWithMiddleware(reducer, initialState)
  return store
}

代碼中使用了redux中的createStore方法用于生成store對象和applyMiddleware方法用于redux中間件,中間件使用了redux-thunk用于actions擴展,也就是上一節我們留下的一個疑問。actions中除了可以寫包含type屬性的對象外,使用thunk中間件后可以在actions寫業務數據邏輯,

編輯actions

在src/redux/actions目錄下新建example.js

import {
  EXAMPLE
} from '../constants'

function example(val){
  return {
    type: EXAMPLE,
    payload: {
      title: val
    }
  }
}

export function changeTitle(val){
  return (dispatch, getState) => {
    dispatch(example(val))
  }
}

redux框架的設計原理參考http://www.lxweimin.com/p/ec820d8581cd

安裝react-router

基礎路由庫

npm install --save react-router

使用文檔:https://github.com/ReactTraining/react-router

安裝react-router-redux

npm install --save react-router-redux

使用文檔:https://github.com/reactjs/react-router-redux

Redux 配合 react-router 使用

history + store (redux) → react-router-redux → enhanced history → react-router

src/containers/rootRoutes.jsx

import React,{Component} from 'react'
import {Provider} from 'react-redux'
import { Router, Route, browserHistory} from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'

import Example from './example'
export default class Root extends Component{
    render(){
        const {store} = this.props
        const history = syncHistoryWithStore(browserHistory, store)
        return (
            <Provider store={store}>
              <div>
                <Router history={browserHistory}>
                        <Route path="/" component={Example}/>
                </Router>
              </div>
            </Provider>
        )
    }
}

configureStore/index.js

import {
  createStore,
  applyMiddleware
} from 'redux';
import thunk from 'redux-thunk';
import reducer from '../reducers';
import { createLogger } from 'redux-logger';
import { routerMiddleware} from 'react-router-redux'
import { browserHistory} from 'react-router'

const middleware = routerMiddleware(browserHistory)
const logger = createLogger({
  level: 'info',
  logger: console,
  collapsed: true
})
const createStoreWithMiddleware = process.env.NODE_ENV === 'production' ? applyMiddleware(
  middleware, thunk
)(createStore) : applyMiddleware(
  middleware, thunk, logger
)(createStore)

const configureStore = (initialState) => {
  const store = createStoreWithMiddleware(reducer, initialState)
  return store
}
export default configureStore

更多react-router使用方法查看https://github.com/ReactTraining/react-router

最后我們來寫一個使用了store數據的React組件

src/containers/example.jsx

class Example extends Component {
  constructor(props) {
    super(props);
  }
  showModal (e) {
    e.preventDefault(); // 修復 Android 上點擊穿透
    this.props.actions.changeTitle("React世界歡迎您")
  }
  render() {
    return (
      <div>
         <input readOnly onClick={this.showModal.bind(this)} value={this.props.example.title} style={{width:'100%', height:'50px', border:0, background:"#CCCCCC"}}/>
      </div>
    )
  }
}


function mapStateToProps(state) {
  return {
    example: state.example.toJS()
  }
}
function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  }
}
export default connect(
  mapStateToProps, mapDispatchToProps
)(Example)

在package.json中添加

"scripts": {
    "start": "webpack-dev-server --hot --inline --port 8080 --host 192.168.31.182",  #添加項
    "test": "echo \"Error: no test specified\" && exit 1"
  },

現在啟動webpack-dev-server, 可以在瀏覽器中看到,輸出的頁面!

? npm start

結束

至此,我們使用webpack, babel, redux, react-router及相關庫構建了基于React的腳手架。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容