React作為一個前端核心庫,建立了良好的JS庫生態,出現了大量的第三方JS庫用來支持React構建適合自己項目的框架。這篇內容,我們將使用redux與wepack來構建一個MVVM模式的React腳手架
開發環境
構建好的目錄
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語法
- 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的腳手架。