webpack+react+redux+es6開發模式

1、預備知識

  node, npm, react, redux, es6, webpackcss

2、學習資源

  ECMAScript 6入門html

  React和Redux的鏈接react-reduxnode

  Redux 入門教程   redux middleware 詳解   Redux研究react

  React 入門實例教程webpack

  webpack學習demogit

  NPM 使用介紹es6

3、工程搭建

  以前有寫過 webpack+react+es6開發模式 ,文章裏介紹了一些簡單的配置,歡迎訪問。後續文章請參考 webpack+react+redux+es6開發模式---續github

  1.能夠npm init, 建立一個新的工程。建立package.json文件,定義須要的dependency,scripts,version等等。web

  2.新增webpack.config.json文件,定義插件項配置,頁面入口文件,文件輸出,加載器的配置,其餘解決方案配置等。下面提供了簡單配置的demo,更詳細的講解,請參考  webpack 入門指南: w2bc.com/Article/50764。ajax

var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
 
module.exports = {
    //插件項
    plugins: [commonsPlugin],
    //頁面入口文件配置
    entry: {
        bundle: './index.js'
    },
    //入口文件輸出配置
    output: {
        path: './build/',
        filename: '[name].js'
    },
    module: {
        //加載器配置
        loaders: [
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            { test: /\.js$/, loader: 'jsx-loader?harmony' },
            { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
            { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
        ]
    },
    //其它解決方案配置
    resolve: {
        root: '******', //絕對路徑
        extensions: ['', '.js', '.json', '.scss'],
        alias: {
            AppStore : 'js/stores/AppStores.js',
            ActionType : 'js/actions/ActionType.js',
            AppAction : 'js/actions/AppAction.js'
        }
    }
};
View Code

  3.編寫若是文件 main.js。這裏建立了provider,store,history,router。實現頁面的路由以及react組件以及組件間的state交互。關於react-redux內容請參考 react-redux概念理解關於react-router內容請參考 React Router 使用教程 

var React = require('react');
var ReactDOM = require('react-dom');
var { Provider } = require('react-redux');
import { Router } from 'react-router';
import routes from 'routes';
import { createHashHistory, useBasename } from 'history';
import { syncReduxAndRouter } from 'redux-simple-router';
import { routeReducer } from 'redux-simple-router';
var configureStore = require('./stores/configureStore');

// Run our app under the /base URL.
const history = useBasename(createHashHistory)({
  basename: '/',
});
const store = configureStore(window.__INITIAL_STATE__);

syncReduxAndRouter(history, store);

ReactDOM.render
(
  <Provider store={store}>
      <Router history={history}>
      {routes}
    </Router>
  </Provider>,
   document.getElementById('root')
);
View Code

  4.建立工程的各個模塊

|--demo1
    |--src  //源碼
        |--actions     // 存放當前觸發Redux的動做行爲
        |--components  // 存放工程內部的公共組件
        |--modules     // 存放工程各模塊代碼
        |--constants   // action動做常量
        |--reducers    // 存放reducer函數,用來修改store狀態
        |--routes      // 放置頁面路由 react router
        |--stores      // 放置stores配置文件
        |--main.js       // 入口js
        |--index.html  // 工程入口文件html
        |--node_modules  // 存放依賴的第三方模塊庫,使用命令 npm install
        |--build  //打包文件存放的目錄
        |--webpack.config.js
        |--package.json    

4、功能開發

  1.作一個簡單的Home頁面

  (1).在modules文件夾新建Home.js, 使用antd 的Menu組件, 展現咱們要演示的功能。

import React from 'react';
import 'less/home.less';
import { Scrollbars } from 'react-custom-scrollbars';
import {Menu} from 'antd';

//首頁
export class Home extends React.Component{
  constructor(props) {
    super(props);
    this.changeRoute = this.changeRoute.bind(this);
  }
  componentDidMount() {
  }

  changeRoute(e) {
    this.context.history.pushState({}, e.key);
  }

  render() {
    return (
      <div className='home'>
        <Scrollbars style={{ height: 600 }}>
            <Menu className='menu' onClick={this.changeRoute}>
              <Menu.Item key='showSelfMsg'>頁面渲染展現信息</Menu.Item>
              <Menu.Item key='frontAndRearInteractive'>模擬先後臺交互</Menu.Item>
              <Menu.Item key='pageExchange'>頁面切換</Menu.Item>
              <Menu.Item key='extend'>子組件擴展</Menu.Item>
            </Menu>
        </Scrollbars>
      </div>
    );
  }
}
Home.contextTypes = {
  history: React.PropTypes.object.isRequired,
};
module.exports = Home;
View Code

  (2).註冊Home頁面的路由,對應routes/index.js加入以下代碼。

<Route path="/" component={ModuleRouters}>
    <IndexRoute component={Home} />
</Route>

  (3).啓動工程, npm run dev, 瀏覽器中輸入 http://localhost:8000/demo1,便可預覽咱們的Home頁面。

  

 

  2.單頁面渲染,完成數據的展現和隱藏

  (1).在component目錄下新建ShowSelfMsg.js, 經過改變state狀態,從新渲染頁面.

import React from 'react';
import {connect} from 'react-redux';
import {Button} from 'antd';
import 'less/common.less';
var mapStateToProps = function(state){
    
};

class ShowSelfMsg extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            showContent: false
        };
        this.showContent = this.showContent.bind(this);
    }

    showContent() {
        this.setState({
            showContent: !this.state.showContent
        });
    }

    componentDidMount() {
        const { dispatch} = this.props;
        //加載該頁面的數據
    }

    componentWillReceiveProps(nextProps) {
    }

    render() {
        let showContent = this.state.showContent;
        return (
            <div className='main'>
                <div className='content'>
                    <Button type="ghost" onClick={this.showContent}>{!this.state.showContent ? '單擊顯示內容' : '單擊隱藏內容'}</Button>
                    {
                        showContent ? (<div><span>你們好,我是hjzgg</span></div>) : (null)
                    }
                    <div className='back'>
                        <Button type="ghost" onClick={()=>this.context.history.pushState({}, '/')}>返回</Button>
                    </div>
                </div>
            </div>
        );
    }
}

ShowSelfMsg.contextTypes = {
  history: React.PropTypes.object.isRequired,
};
module.exports = connect(mapStateToProps)(ShowSelfMsg);
View Code

  (2).註冊路由,在routes/index.js中加入以下代碼。

<Route path="/showSelfMsg" component={ShowSelfMsg} />

  (3).在Home頁面中點擊 ‘頁面渲染展現信息’,便可進入這個頁面。

  

 

  3.模擬先後臺交互

  (1).代碼編寫以下。

    (I).在constants新建ActoinTypesjs,定動做類型;

    (II).在actions目錄中新建simulationRquest.js, 定義要分發的動做;

    (III)在reducers目錄新建simulationRquest.js,存放reducer函數,用來修改store狀態,而後將該函數放入到reducers/index.js中的combineReducers函數中,最終會合併成一個新的reducer;

    (IV)components目錄中新建FrontAndRearInteractive.js, dispatch 自定義的動做,實現模擬先後臺交互功能。

  ActionType.js

export const SIMULATION_REQUEST_SUCCESS = 'SIMULATION_REQUEST_SUCCESS';
export const SIMULATION_REQUEST_FAIL = 'SIMULATION_REQUEST_FAIL';
export const INIT_EXTEND_DATA_SUCCESS = 'INIT_EXTEND_DATA_SUCCESS';
export const INIT_EXTEND_DATA_FAIL = 'INIT_EXTEND_DATA_FAIL';
export const SAVE_EXTEND_DATA_SUCCESS = 'SAVE_EXTEND_DATA_SUCCESS';
View Code

  FrontAndRearInteractive.js

import React from 'react';
import {connect} from 'react-redux';
import {Button} from 'antd';
import {simulationRquestAction} from 'actions/simulationRequest';
var mapStateToProps = function(state){
    return {
        myRequest: state.myRequest,
    }
};

class FrontAndRearInteractive extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            showContent: false
        };
        this.simulationRequest = this.simulationRequest.bind(this);
    }

    simulationRequest() {
        const {dispatch} = this.props;
        console.log('props>>>dispath:' + dispatch);
        dispatch(simulationRquestAction());
    }

    componentDidMount() {
        const { dispatch} = this.props;
        //加載該頁面的數據
    }

    componentWillReceiveProps(nextProps) {
        const { myRequest } = nextProps;
        if(myRequest.code && myRequest.msg)
            alert('請求結果:code=' + myRequest.code + ', msg=' + myRequest.msg);
    }

    render() {
        const { myRequest } = this.props;
        return (
            <div className='main'>
                <div className='content'>
                    <Button type="ghost" onClick={this.simulationRequest}>模擬請求</Button>
                    {
                        myRequest && myRequest.data ? (<div><span>{myRequest.data}</span></div>) : (null)
                    }
                    <div className='back'>
                        <Button type="ghost" onClick={()=>this.context.history.pushState({}, '/')}>返回</Button>
                    </div>
                </div>
            </div>
        );
    }
}
FrontAndRearInteractive.contextTypes = {
  history: React.PropTypes.object.isRequired,
};
module.exports = connect(mapStateToProps)(FrontAndRearInteractive);
View Code

  actions/simulationRquest.js

import {ajax} from 'utils/ajax';
import url from 'utils/Url';
import {
    SIMULATION_REQUEST_SUCCESS, SIMULATION_REQUEST_FAIL,
    } from 'constants/ActionTypes';

function simulationRquestSuccess(data, msg){
    return {
        type: SIMULATION_REQUEST_SUCCESS,
        data,
        msg,
    }
}

function simulationRquestFail(msg){
    return {
        type: SIMULATION_REQUEST_FAIL,
        msg,
    }
}

export function simulationRquestAction(args){
    return function (dispatch) {
        console.log('actions>>>dispath:' + dispatch);
        /*
            //真是請求
            ajax({
                method : 'GET',
                url :  url.QUERY_ALL_USER,
                query : {'args': args},
                type : 'json',
                success : function(data) {
                  return dispatch(simulationRquestSuccess(data));
                },
                error : function(data) {
                  return dispatch(simulationRquestFail('request fail'));
                }    
            });
        */
        //假設請求成功
        return dispatch(simulationRquestSuccess('我是後臺返回數據:hjzgg!!!', '獲取數據成功'));
  };
}
View Code

  reducers/simulationRquest.js

import {
    SIMULATION_REQUEST_SUCCESS, SIMULATION_REQUEST_FAIL,
    }  from 'constants/ActionTypes';
import assign from 'lodash/assign';

function myRequest(state = {
        data: null,
        msg: null,
        code: null,
    }, action) {
    console.log('reducer action屬性>>>>>' + JSON.stringify(action));

    switch(action.type) {
        case SIMULATION_REQUEST_SUCCESS:
            return assign({}, state, {
                msg: action.msg,
                data: action.data,
                code: 'success',
              });

        case SIMULATION_REQUEST_FAIL:
            return assign({}, state, {
                msg: action.msg,
                data: null,
                code: 'fail',
              });
        default:
            return state;
    }

}

module.exports = myRequest;
View Code

  (2).路由註冊,在routes/index.js增長以下代碼。

<Route path="/frontAndRearInteractive" component={FrontAndRearInteractive} />

  (3).在Home頁面中點擊 ‘模擬先後臺交互’,便可進入頁面。

  

  4.頁面切換

  (1).在components目錄新建PageExchange.js 和 Childpage.js,分別爲父頁面和子頁面。注意,這裏父頁面的變量信息 是經過路由的方式傳遞過去的,固然也能夠經過state方式傳遞過去。

  PageExchange.js

import React from 'react';
import {connect} from 'react-redux';
import {Button} from 'antd';
import 'less/common.less';
var mapStateToProps = function(state){

};

class PageExchange extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            showContent: false
        };
        this.gotoChildPage = this.gotoChildPage.bind(this);
    }

    gotoChildPage() {
        console.log('this.context.history>>>>>>' + JSON.stringify(this.context.history));
        this.context.history.pushState({}, 'childDemoPage/' + '我是父頁面信息');
    }

    componentDidMount() {
        const { dispatch} = this.props;
        //加載該頁面的數據
    }

    componentWillReceiveProps(nextProps) {
    }

    render() {
        let showContent = this.state.showContent;
        return (
            <div className='main'>
                <div className='content'>
                    <Button type="ghost" onClick={this.gotoChildPage}>進入子頁面</Button>
                    <div className='back'>
                        <Button type="ghost" onClick={()=>this.context.history.pushState({}, '/')}>返回</Button>
                    </div>
                </div>
            </div>
        );
    }
}

PageExchange.contextTypes = {
  history: React.PropTypes.object.isRequired,
};
module.exports = connect(mapStateToProps)(PageExchange);
View Code

  Childpage.js

import React from 'react';
import {connect} from 'react-redux';
import {Button} from 'antd';
import 'less/common.less';
var mapStateToProps = function(state){
    return {
    }
};

class ChildPage extends React.Component{
    constructor(props){
        super(props);
        this.returnParentPage = this.returnParentPage.bind(this);
    }

    componentDidMount() {
        const { dispatch} = this.props;
        //加載該頁面的數據
    }

    componentWillReceiveProps(nextProps) {
    }

    returnParentPage() {
        this.context.history.pushState(null, 'pageExchange');
    }

    render() {
        const parentPageMsg = this.props.params.parentPageMsg;
        return (
            <div className='main'>
                <div className='content'>
                    <Button type="ghost" onClick={this.returnParentPage}>返回父頁面</Button>
                    {
                        parentPageMsg ? (<div><span>{parentPageMsg}</span></div>) : (null)
                    }
                </div>
            </div>
        );
    }
}

ChildPage.contextTypes = {
  history: React.PropTypes.object.isRequired,
};
module.exports = connect(mapStateToProps)(ChildPage);
View Code

  (2).註冊路由,在routes/index.js中加入以下代碼。

<Route path="/pageExchange" component={PageExchange} />
<Route path="/childDemoPage(/:parentPageMsg)" component={ChildPage}/>

  (3).在Home頁面中點擊‘頁面切換’,便可進入頁面。

  

  5.自定義擴展組件

   (1).先說一下應用場景:多個頁面可能須要相似的擴展功能,經過自定義擴展組件,完成對信息的加載。主頁面信息保存時,通知擴展組件要保存信息了,擴展組件將最新修改的信息告知主頁面,主頁面獲取到所有信息後,一塊兒將數據傳給後臺,完成主頁面信息和擴展信息的保存。

  (2).在components目錄下新建Page.js和ExtendPage.js,分別爲主頁面和自定義擴展組件。

  Page.js

import React from 'react';
import {connect} from 'react-redux';
import {Button, Input, Form} from 'antd';
import ExtendPage from 'components/ExtendPage';
import 'less/common.less';
const FormItem = Form.Item;
var mapStateToProps = function(state){
    return {
        extendStore: state.extendStore
    }
};

class Page extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            childState: false,
        }
        this.handleSubmit = this.handleSubmit.bind(this);
        this.onSaveExtendPage = this.onSaveExtendPage.bind(this);
    }

    componentDidMount() {
        const { dispatch} = this.props;
        //加載該頁面的數據
    }

    componentWillReceiveProps(nextProps) {
    }

    //通知擴展組件,準備保存了
    onSaveExtendPage() {
        if(this.state.childState) {
            this.setState({
                childState: false,
            });
        }
    }

    save(values) {
        //打印父級和子級文本
        alert(JSON.stringify(values));
    }

    handleSubmit() {
        var self = this;
        this.props.form.validateFields((err, values) => {
          if (!err) {//表單符合標準
            //values 爲當前父頁面的數據,接下來獲取子頁面的數據
            this.setState({childState: true}, function() {
                const { extendStore } = self.props;
                values.extendData = extendStore && extendStore.data || extendStore;
                self.save(values);
            });
          }
        });
    }

    render() {
        const { getFieldProps } = this.props.form;
        const inputProps = getFieldProps('inputText', {
            initialValue: '',
            rules: [
                    {required: true, message: 'the input is required' },
                ],
            validateTrigger: "onBlur"
        });
        return (
            <div style={{marginTop: 50, width: 600, marginLeft: 'auto', marginRight: 'auto'}}>
                <Form onSubmit={this.handleSubmit}>
                  <FormItem {...{labelCol: { span: 6 }, wrapperCol: { span: 14 }}} label="父級文本: ">
                      <Input {...inputProps} id='inputText' type='text'/>
                  </FormItem>
                  <FormItem wrapperCol={{ span: 12, offset: 6 }}>
                      <Button type="primary" htmlType="submit">提交</Button>
                     </FormItem>
                </Form>

                <ExtendPage
                    childState={this.state.childState}
                    callBack={this.onSaveExtendPage}
                />

                <div style={{float: 'right'}}>
                    <Button type="ghost" onClick={()=>this.context.history.pushState({}, '/')}>返回</Button>
                </div>
            </div>
        );
    }
}
Page.contextTypes = {
  history: React.PropTypes.object.isRequired,
};
Page = Form.create()(Page);
module.exports = connect(mapStateToProps)(Page);
View Code

  ExtendPage.js

import React from 'react';
import {connect} from 'react-redux';
import {Button, Form, Input, message} from 'antd';
const FormItem = Form.Item;
import {initExtendData, saveExtendDataAction} from 'actions/extendPage';
var mapStateToProps = function(state){
    return {
        extendStore: state.extendStore
    }
};

class ExtendPage extends React.Component{
    constructor(props){
        super(props);
        this.state = {

        }

        this.saveExtendData = this.saveExtendData.bind(this);
        this.checkText = this.checkText.bind(this);
    }

    checkText(rule, value, callBack) {
        if(/\s+/.test(value)) {
            callBack("不能有空白字符");
        } else {
            callBack();
        }
     }

    saveExtendData() {
        this.props.callBack();//保存成功後,更改父頁面的childState的狀態
        this.props.form.validateFields((err, values) => {
          if (!err) {//表單符合標準
            console.log('save ExtendPage values: ' + JSON.stringify(values));
            const {dispatch} = this.props;
            dispatch(saveExtendDataAction(values));
          }
        });
    }

    componentDidMount() {
        const { dispatch} = this.props;
        //初始化擴展頁的數據
        dispatch(initExtendData());
    }

    componentWillReceiveProps(nextProps) {
        const { extendStore, childState } = nextProps;
        if(extendStore && extendStore.msg) {
            message.info(extendStore.msg, 5);
            extendStore.msg = null;
        }

        if(childState) {//父頁面 改變 子頁面的狀態
            this.saveExtendData();
        }
    }


    render() {
        const { getFieldProps } = this.props.form;
        const { extendStore } = this.props;
        const inputValue = extendStore && extendStore.data && extendStore.data.extendInputText || null;
        const inputProps = getFieldProps('extendInputText', {
            initialValue: inputValue,
            rules: [
                    {required: true, message: 'the input is required' },
                    {validator: this.checkText}
                ],
            validateTrigger: "onBlur"
        });
        return (
            <div>
                <Form>
                  <FormItem {...{labelCol: { span: 6 }, wrapperCol: { span: 14 }}} label="擴展本文: ">
                      <Input {...inputProps} type="text" id="extendInputText"/>
                  </FormItem>
                </Form>
            </div>
        );
    }
}
ExtendPage = Form.create()(ExtendPage);
module.exports = connect(mapStateToProps)(ExtendPage);
View Code

  (3).說一下組件的擴展機制

  (I).擴展組件自身會維護更新本身state狀態,在觸發擴展組件保存時,擴展組件將自身數據經過dispatch進行分發,最後經過對應的reducer(這個reducer會經過combineReducers函數合併成一個新的reducer)進行處理,根據邏輯生成新的state。

  >>定義動做類型

  

   >>分發動做

  

  >>reducer處理動做,返回新的state

  

  >>自定義的reducer函數經過combineReducers函數進行合併

  

   (II).父級組件如何獲取擴展組件的狀態?

  

  也就是store中的狀態樹變化的時候,組件能夠經過 mapStateToProps 函數從狀態樹中獲取最新的state。

  (III).父級組件如何通知擴展組件 準備保存數據了?

  

  >>擴展組件接收父級組件兩個參數:childState, 通知擴展組件狀態發生變化; callBack, 修改childState狀態,擴張組件通知父級組件更新完成。

 

  

  >>父級組件保存數據時,首先獲取到本身的數據,而後經過setState()方法改變childState的值,通知擴展組件。最後經過setState方法傳入的回調函數(該函數在組件更新完成以後調用)獲取到擴展組件的最新state。

  

  

  >>擴展組件接收到父級組件的通知,刷新store中的state。這樣父級組件和擴展組件自身均可以經過mapStateToProps方法獲取到最新的state。

    (4).註冊路由,在routes/index.js中加入以下代碼。

  

  (5).在Home頁面中點擊‘頁面切換’,便可進入頁面。

    

 

 

 5、問題解惑

   1.module.filename、__filename、__dirname、process.cwd():  http://www.tuicool.com/articles/bQre2a
   2.node.js之path模塊: http://www.jianshu.com/p/fe41ee02efc8
   3.react-router: http://www.ruanyifeng.com/blog/2016/05/react_router.html?utm_source=tool.lu
     4.出現以下錯誤:Cannot sync router: route state does not exist. Did you install the routing reducer,參考:

    http://stackoverflow.com/questions/34039619/redux-simple-router-react-router-error-route-state-does-not-exist

  5.module.exprots, export, export default區別:

export default variation

import variation from 'js file'
 

export variation

import {variation} from 'js file'


module.exports=variation

import variation from 'js file'

  參考:

  http://www.2cto.com/kf/201412/360211.html

  http://www.jb51.net/article/33269.htm

  http://blog.csdn.net/zhou_xiao_cheng/article/details/52759632

  http://blog.csdn.net/zhou_xiao_cheng/article/details/52759632

6、dispath疑問

  react-router相關API

  react-redux 之 connect 方法詳解

  驗證一下 redux store.dispatch  和 react組件 props中的dispath,的確是同樣的。

  

dispath:function (action) {
      return typeof action === 'function' ? action(dispatch, getState) : next(action);
}

7、演示地址

  http://study.hujunzheng.cn:8000/DEMO_FRONT/

8、完整項目下載

  https://github.com/hjzgg/webpack-react-redux

相關文章
相關標籤/搜索