(三)建立React、redux和antd项目

一.框架基本描述

二.建立项目

  • 根据上一节2、3点所说方试建立

    npm init

     

    npm install antd history jquery react react-dom redux react-redux react-router redux-promise-middleware redux-thunk redux-logger --save

     

    npm install autoprefixer babel babel-cli babel-core babel-loader babel-plugin-antd babel-plugin-import babel-plugin-transform-runtime babel-polyfill babel-preset-es2015 babel-preset-react babel-preset-stage-0 babel-runtime clean-webpack-plugin console-polyfill copy-webpack-plugin css-loader es3ify-loader es6-promise eventsource-polyfill extract-text-webpack-plugin file-loader html-webpack-plugin jsx-loader less less-loader object-assign postcss-loader react-hot-loader sass-loader style-loader url-loader webpack webpack-dev-server --save-dev

     

     

  • 到此基本开发所需的lib已安装完成

三.建立项目所需的基本资料夹和档案

  • 主要所需的档案和目录如下所示
  • src 【源码目录】
    • App【存放容器组件】
    • components 【jsx控件存放目录】
    • reducers 【存放reducer的配置档】
      • index.js
    • styles 【css存放目录】
    • store 【存放Redux的store配置档】
      • configureStore.js
    • templates 【index.html模板目录】
      • index.html 【html模板页面,用于生成进入的页面】
    • util 【存放JS工具库】
    • entry.js 【主应用程式】
  • build 【打包后的源码目录】
  • resources 【存放一些外部用资源档,如支持 IE8 的js库】
  • test 【存放一些测试用的程式】
  • env.js【使用变量统一配置开发环境】
  • webpack-config.js 【webpack项目管理配置档】
  • package.json 【npm init 自动产生的包管理配置档案】

四.修改package.json配置档,使用不同的环境变数进行打包设定

  • 主要增加 BUILD_LOCAL、BUILD_DEV、BUILD_UAT、BUILD_PDT四个变量进行不同环境编译的控制,其中 local_local的配置不同是在区分 linux的系统和windows系统变量配置的方式的不同,在windows的环境中,需要使用 set去配置变量。

     

  • 主要将scripts的区块更改为如下的设定:

     

    "scripts": {
      "local": "BUILD_LOCAL=1 webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "local_build": "BUILD_LOCAL=1 webpack -p",
      "dev": "BUILD_DEV=1 webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "dev_build": "BUILD_DEV=1 webpack -p",
      "uat": "BUILD_UAT=1 webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "uat_build": "BUILD_UAT=1 webpack -p",
      "pdt": "BUILD_PDT=1 webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "pdt_build": "BUILD_PDT=1 webpack -p",
      "_local": "set BUILD_LOCAL=1&&webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "_local_build": "set BUILD_LOCAL=1&&webpack -p",
      "_dev": "set BUILD_DEV=1&&webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "_dev_build": "set BUILD_DEV=1&&webpack -p",
      "_uat": "set BUILD_UAT=1&&webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "_uat_build": "set BUILD_UAT=1&&webpack -p",
      "_pdt": "set BUILD_PDT=1&&webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
      "_pdt_build": "set BUILD_PDT=1&&webpack -p",
      "test": "echo \"Error: no test specified\" && exit 1"
    },

     

    五.修改webpack.config.js和env.js的配置

  • webpack.config.js配置档案主要增加 definePlugin 的配置,范例如下

     

/**
 * 载入webpack需要的js模组
 **/
var path = require('path');
var webpack = require('webpack');

//设定项目的根目录
var ROOT_PATH = path.resolve(__dirname);
//设定 source path
var SRC_PATH = path.resolve(__dirname, './src');
//设定应用程式进入JS档案
var APP_PATH = path.resolve(__dirname, './src/entry.js');
//设定打包结果目录
var BUILD_PATH = path.resolve(__dirname, './build');

//使用 extract-text-webpack-plugin 可以把 css 从 js 中独立抽离出来
var ExtractTextPlugin=require("extract-text-webpack-plugin");
//使用 CommonsChunkPlugin 抽取公共代码
var CommonsChunkPlugin=require("webpack/lib/optimize/CommonsChunkPlugin");
//使用 HtmlWebpackPlugin 可以帮助生成HTML文件,在body元素中,使用 script 来包含所有你的 webpack bundles
var HtmlWebpackPlugin = require('html-webpack-plugin'); //依模板生成html
// 使用 CleanPlugin 删除你以前build过的文件
var CleanPlugin = require('clean-webpack-plugin');
// 使用 CopyWebpackPlugin 拷贝资源文件到 BUILD_PATH中
var CopyWebpackPlugin = require("copy-webpack-plugin");

var definePlugin = new webpack.DefinePlugin({
    __LOCAL__: JSON.stringify(JSON.parse(process.env.BUILD_LOCAL || 'false')),
    __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'false')),
    __UAT__: JSON.stringify(JSON.parse(process.env.BUILD_UAT || 'false')),
    __PDT__: JSON.stringify(JSON.parse(process.env.BUILD_PDT || 'false')),
    __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'true'))
});

module.exports = {
  /* 提供source-map方便Debug用,若要部署至正式机,请关闭此选项 */
  devtool: 'eval-source-map',
  /* 配置应用程式进入js档案 */
  entry: {
    main:[APP_PATH],
    vendor: ['jquery']
  },
  /* 配置输出预设的路径,和依需载入打包【将每个组件打包成自己的js,在使用时才进行载入】 */
  output: {
    path:BUILD_PATH,
    filename:'[name].js',
    chunkFilename:'components/[name].[chunkhash:5].min.js'
  },
  /* 表示这个依赖项是外部lib,遇到require 或 import它不需要编译,且在浏览器端对应window.React*/
  externals: [{
    'antd': 'window.antd',
    'react': 'window.React',
    'react-dom': 'window.ReactDOM',
    'jquery': 'window.jQuery',
  }],
  /* 设定打包时查找的类型,增加查询打包速度 */
  resolve:{
    root:['js','jsx','css','scss','less']
  },
  /* 配置开启 Debug 模式,暂不知到如何使用 */
  debug: true,
  /* 模组Loader配置 */
  module: {
    loaders: [
      /* 档案匹配 .js 进行 babel es6转换 */
      { test: /\.js$/,
        loader: "babel", include: /src/,exclude: /node_modules/
      },
      /* 档案匹配 .jsx 进行 babel es6转换,然后再进行 jsx 语法转换【转换规则由右至左】*/
      { test: /\.jsx$/,
        loader: "jsx!babel", include: /src/,exclude: /node_modules/
      },
      /* 档案匹配 .css 进行 【css样式加前缀】语法转换,【转换规则由右至左】*/
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract("style", "css!postcss")/*,include: /src/,exclude: /node_modules/*/
      },
      /* 档案匹配 .sass 进行 sass 语法转换为 css,【转换规则由右至左】*/
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract('style', 'css!postcss!sass'),include: /src/,exclude: /node_modules/
      },
      /* 档案匹配 .less 进行 less 语法转换为 css,【转换规则由右至左】*/
      {
        test:/\.less$/,
        loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader"),include: /src/,exclude: /node_modules/
      },
      /* url-loader 的配置,将小于80K的静态图片转为Base64字符串,减少网路请求压力 */
      {
        test:/\.(png|jpg)$/,
        loader:'url?limit=8192&name=images/[hash:8].[name].[ext]',include: /src//*,exclude: /node_modules/*/
      }],
    /* 项目兼容IE8时,需要进行 ES3的语法转换
    postLoaders: [{
        test: /\.js$/,
        loaders: ['es3ify-loader']
      },{
        test: /\.jsx$/,
        loaders: ['es3ify-loader']
    }]*/
  },
  /* presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装 【预设使用 ES2015(ES6)转码规则、ES7第0阶段的转码规则和react转码规则】*/
  babel: {
    presets: ['es2015', 'stage-0', 'react'],
  },
  /* 调用autoprefixer插件,css3自动补全 */
  postcss: [
    require('autoprefixer')
  ],
  /* 配置webpack开发服务器设定的Port */
  devServer:{
    port:8084,
    host:"0.0.0.0",
    colors: true,  //终端中输出结果为彩色
    historyApiFallback: true,  //不跳转
    inline: true,  //实时刷新
    outputPath:BUILD_PATH,
    /* 代理服务器的配置 */
    proxy:{
      '/api/*': {
        target: 'http://192.168.4.208:8083',
        pathRewrite: {'^/api' : '/'},
        changeOrigin: true
      },
      '/api/*': {
        target: 'ws://192.168.4.208:8083',
        pathRewrite: {'^/api' : '/'},
        ws:true,
        changeOrigin: true
      }
    }
  },
  /**
   * webpack 常用插件配置
  **/
  plugins: [
    definePlugin,
    //new CleanPlugin(BUILD_PATH),

    new CommonsChunkPlugin({
       name: 'vendor',
       filename: '[name]-[hash].min.js'
    }),

    new ExtractTextPlugin('[name].css'),

    new HtmlWebpackPlugin({  //根据模板插入css/js等生成最终HTML
      filename: './index.html', //生成的html存放路径,相对于 path
      template: './src/templates/index.html', //html模板路径
      hash: false,
    }),

    new webpack.optimize.UglifyJsPlugin(),

    new webpack.ProvidePlugin({
      ENV: ROOT_PATH+"/env.js",
      Moment: 'moment', //直接从node_modules中获取
      $:"jquery",
      jQuery:"jquery",
      "window.jQuery":"jquery"
    }),

    new CopyWebpackPlugin([{
        from: 'resources',
        to: ROOT_PATH + '/build'
      },{
        from: 'node_modules/jquery/dist/jquery.min.js',
        to: ROOT_PATH + '/build/js'
      },{
        from: 'node_modules/react/dist/react.min.js',
        to: ROOT_PATH + '/build/js'
      },{
        from: 'node_modules/react-dom/dist/react-dom.min.js',
        to: ROOT_PATH + '/build/js'
      },{
        from: 'node_modules/antd/dist/antd.min.css',
        to: ROOT_PATH + '/build/styles'
      },{
        from: 'node_modules/antd/dist/antd.min.js',
        to: ROOT_PATH + '/build/js'
      },{
        from: 'test',
        to: ROOT_PATH + '/build'
      }
    ])
  ]
};

 

  • env.js是环境配置模块,其配置内容如下:

     

if(__LOCAL__){
  module.exports = {
    baseUrl: 'http://localhost:8084/api/' ,
    wsUrl: 'ws://localhost:8084/api/'
  };
}

if(__DEV__){
  module.exports = {
    baseUrl: 'http://192.168.4.109:8084/api/' ,
    wsUrl: 'ws://192.168.4.109:8084/api/'
  };
}

if(__UAT__){
  module.exports = {
    baseUrl: 'http://192.168.4.109:8080/api/' ,
    wsUrl: 'ws://192.168.4.109:8080/api/'
  };
}

if(__PDT__){
  module.exports = {
    baseUrl: 'http://192.168.4.109:8080/api/' ,
    wsUrl: 'ws://192.168.4.109:8080/api/'
  };
}

 

六.撰写项目启动的entry.js程序

  • entry.js 的程序主要是引入App/index.js的容器组件,其内容如下:

     

require('console-polyfill');
require('es6-promise');
require('babel-polyfill');

/**
 * 载入应用程式主要容器组件
**/
require('./App/index.jsx');

 

七.撰写App/index.jsx主要容器组件

  • App/index.jsx组件会使用到Redux的store和reducer,我们也需要进行相关的配置
    • 在src目录下建立store子目录和档案configureStore.js
    • configureStore.js的内容配置如下:

       

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

const logger = createLogger();
//applyMiddleware来自redux可以包装 store 的 dispatch
//thunk作用是使action创建函数可以返回一个function代替一个action对象
const createStoreWithMiddleware = compose(
  //配至Redux使用的中间件,我们在开发中用到的中间件是redux-thunk和redux-logger
  applyMiddleware(
      thunk,
      logger
  ),
  //可以帮助我们自动生成chrome插件redux的devtool界面,用于Redux Debug用,再开发期间可已使用,上正式部署时请记得关闭
  window.devToolsExtension ? window.devToolsExtension() : f => f
)(createStore)

/**
 * 配置Redux的store用于存放组件state
**/
export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(reducer, initialState)

  //热替换选项
  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers')
      store.replaceReducer(nextReducer)
    })
  }

  return store
}

 

    • 在src目录下建立reducers子目录和档案index.js
    • 的内容如下:

       

import { combineReducers } from 'redux';
import counter from '../App/Counter/counter-action.js';

//使用redux的combineReducers方法将所有action的reducer打包起来
const rootReducer = combineReducers({
  counter
});

export default rootReducer;

 

    • 在src/App下建立一个index.jsx的App容器组件
    • 其内容如下:

       

const React = require('react');
const ReactDOM = require('react-dom');

import {Router, Link, browserHistory} from 'react-router';
import {createHashHistory} from 'history';
import { Provider } from 'react-redux';

import configureStore from '../store/configureStore';

// 引入Antd的导航组件
import { Menu, Icon } from 'antd';
const SubMenu = Menu.SubMenu;

require('../styles/app.scss');

//ES6 语法建立App组件
export default class App extends React.Component{
  constructor(props){
    super(props);
    //
    this.state = {
        current: '',
        username: '管理者'
    };
  }

  handleClick(e) {
      this.setState({
          current: e.key
      });
  }

  render(){
    return (
      <div>
          <div id="leftMenu">
              <img src='images/logo.png' width="50" id="logo"/>
              <Menu theme="dark"
                  onClick={this.handleClick.bind(this)}
                  style={{ width: 200 }}
                  mode="inline"
              >
                  <Menu.Item key="0"><Link to="/"><Icon type="home" />首页</Link></Menu.Item>
                    <SubMenu key="sub9000" title={<span><Icon type="bars" /><span>测试导航</span></span>}>
                        <Menu.Item key="9100"><Link to="/counter">计数器</Link></Menu.Item>
                    </SubMenu>
              </Menu>
          </div>
          <div id="rightWrap">
              <Menu mode="horizontal">
                  <SubMenu title={<span><Icon type="user"/>{this.state.username}</span>}>
                      <Menu.Item key="setting:1">退出</Menu.Item>
                  </SubMenu>
              </Menu>
              <div className="right-box">
                  { this.props.children }
              </div>
          </div>
      </div>

    )
  }
}

//路由配置
const rootRoute = {
  path: '/',
  getComponent(nextState, cb) {
    require.ensure([], (require) => {
      cb(null, require('./index.jsx').default)
    }, 'App')
  },
  childRoutes: [
    require('./Counter/counter-route.js'),
  ]
}

const store = configureStore();

const root = (
  <Provider store={store} key="provider">
    <Router history={browserHistory} routes={rootRoute}></Router>
  </Provider>
);

ReactDOM.render((
  root
),document.getElementById('app'));

八.建立Counter组件

  • 组件的建立,一般我们需要建立三个档案,其中有redux使用的action档案、react-router使用的route档案和组件的JSX档案,依现在开发的范例来说,基本就需要counter-action.js、counter-route.js和counter.jsx这三个档案来组成一个元件,下面我们针对各个档案进行说明;

  • counter-action.js 此档案是用于组件行为控制,依Redux的要求,要控制组件的行为是使用action的type的宣告进行action行为的控制,由reducer针对type进行相应的state更新,范例如下:
    【state的更新原则使用的是new 新的state,和action所带data进行合并】

     

     

export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'

// initial state 初始化Profile控件state
export const initialState = {
    counter:0
};

//加一的Action
export function doIncrement() {
  return {
    type: INCREMENT_COUNTER
  }
}
//减一的Action
export function doDecrement() {
  return {
    type: DECREMENT_COUNTER
  }
}
//奇数加一的Action,该方法返回一个方法,包含dispatch和getState两个参数,dispatch用于执行action的方法,getState返回state
export function doIncrementIfOdd() {
  return (dispatch, getState) => {
    //获取state对象中的counter属性值
    const { counter } = getState()

    //偶数则返回
    if (counter % 2 === 0) {
      return
    }
    //没有返回就执行加一
    dispatch(doIncrement())
  }
}
//异步加一的Action,包含一个默认参数delay,返回一个方法,一秒后加一
export function doIncrementAsync(delay = 1000) {
  return dispatch => {
    setTimeout(() => {
      dispatch(doIncrement())
    }, delay)
  }
}
//上述的这些Action,在其他文件导入时候,使用import * as actions 就可以生成一个actions对象包含所有的export


//reducer其实也是个方法而已,参数是state和action,返回值是新的state
export default function counter(state = initialState, action) {
  switch (action.type) {
    case INCREMENT_COUNTER:
      return Object.assign({},state,{counter:state.counter+1});
    case DECREMENT_COUNTER:
      return Object.assign({},state,{counter:state.counter-1});
    default:
      return state
  }
}

 

- 配置counter-route.js,route是配置组件绑定的URL,我们配置的方式是使用路由依需载入,其内容如下:

 

module.exports = {
  //配置组件使用的URL
  path: 'counter',
  //配置组件动态载入jsx档案,主要是使用require进行动态载入
  getComponent(nextState, callback) {
    require.ensure([], (require) => {
      callback(null, require('./counter.jsx').default)
    }, 'Counter')
  }
}

 

- 接下来是主要的组件设计档案 counter.jsx 其内容如下:

 

import React, { Component, PropTypes } from 'react'

class Counter extends Component {
  constructor(props){
    super(props);
    this.props.doIncrement();
  }

  componentWillMount(){

  }

  render() {
    //从组件的props属性中导入四个方法和一个变量
    const { doIncrement, doIncrementIfOdd, doIncrementAsync, doDecrement, counter } = this.props;

    console.log('doIncrement action:',this.props);

    //渲染组件,包括一个数字,四个按钮
    return (

      <p>
        Clicked: {counter.counter} times
        {' '}
        <button onClick={doIncrement}>+</button>
        {' '}
        <button onClick={doDecrement}>-</button>
        {' '}
        <button onClick={doIncrementIfOdd}>Increment if odd</button>
        {' '}
        <button onClick={() => doIncrementAsync()}>Increment async</button>
      </p>
    )
  }
}

//限制组件的props安全
Counter.propTypes = {
  //increment必须为fucntion,且必须存在
  doIncrement: PropTypes.func.isRequired,
  doIncrementIfOdd: PropTypes.func.isRequired,
  doIncrementAsync: PropTypes.func.isRequired,
  doDecrement: PropTypes.func.isRequired,
  //counter必须为数字,且必须存在
  counter: PropTypes.number.isRequired
};

/**
 * React组件和Redux的绑定
 */
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as CounterActions from './counter-action';

//将state.counter绑定到props的counter
function mapStateToProps(state) {
  return {
    counter:state.counter
  }
}
//将counter-action的所有方法绑定到props上
function mapDispatchToProps(dispatch) {
  return bindActionCreators(CounterActions, dispatch)
}

//通过react-redux提供的connect方法将我们需要的state中的数据和actions中的方法绑定到props上
export default connect(mapStateToProps, mapDispatchToProps)(Counter)

到此React和Redux整合的基本范例已结束,谢谢

猜你喜欢

转载自bigdragon.iteye.com/blog/2373402