• webpack+react+redux+es6开发模式


    一、预备知识

      node, npm, react, redux, es6, webpack

    二、学习资源

      ECMAScript 6入门

      React和Redux的连接react-redux

      Redux 入门教程   redux middleware 详解   Redux研究

      React 入门实例教程

      webpack学习demo

      NPM 使用介绍

    三、工程搭建

      之前有写过 webpack+react+es6开发模式 ,文章里介绍了一些简单的配置,欢迎访问。后续文章请参考 webpack+react+redux+es6开发模式---续

      1.可以npm init, 创建一个新的工程。创建package.json文件,定义需要的dependency,scripts,version等等。

      2.新增webpack.config.json文件,定义插件项配置,页面入口文件,文件输出,加载器的配置,其他解决方案配置等。下面提供了简单配置的demo,更详细的讲解,请参考  webpack 入门指南: w2bc.com/Article/50764。

    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    

    四、功能开发

      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,  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页面中点击‘页面切换’,即可进入页面。

        

     五、问题解惑

       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

    六、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);
    }

    七、演示地址

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

    八、完整项目下载

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

  • 相关阅读:
    异步FIFO的Verilog实现
    二进制格雷码与二进制自然码
    握手协议
    电容充放电和开关电容
    Vivado自定义IP封装流程
    【转】warning 之 [IP_Flow 19-3153]
    【转】mipi-csi-2解读
    版本管理-link
    [转载]yuv和yCbCr的差异
    【转】用verilog实现RGB格式图像到YCbCr或YUV格式的转换及其验证方法 (RGB2YCrCb)(RGB2YUV)
  • 原文地址:https://www.cnblogs.com/hujunzheng/p/6133648.html
Copyright © 2020-2023  润新知