zoukankan      html  css  js  c++  java
  • react技术栈实践(2)

    本文来自网易云社区

    作者:汪洋


    这时候还没完,又有两个问题引出来了。 

    1. 按照上面的配置,第三方库 antd 竟然也被编译了,导致样式失败。

    2. react中,一旦包裹了子组件,子组件没办法直接使用 styleName。

    第2个问题,还好解决,查了下 react-css-modules 资料,子组件中通过props获取

          const template = (        <div className={this.props.styles['loadingBox']}>
              <Loading />
            </div>);

    第1个问题纠结了好久,后来找了个折中的方案,好心酸。 在entry.jsx中引入的antd组件样式,改成 

    import 'antd/dist/antd.css';

    对,直接引入 css文件,跳过less编译。
    然后在webpack中新增配置

          {
            test: /.(css|less)$/,
            use: ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use: [            'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',            'less-loader'
              ]
            }),
            exclude: /node_modules/
          },
          {
            test: /.(css)$/,
            use: ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use: [            'css-loader'
              ]
            }),
            include: /node_modules/
          },

    到这一步,大家应该明白我的方案了,就是 node_modules 文件夹中的 css文件不启动 cssmoduls,其它文件夹中 启动 cssmoduls。

    接下来就是第4个大问题待解决,路由按需加载。
    作为新手,当然首先是搜索一下 react-router 4.x 如何实现按需加载的,果然好多答案。至于如何选择,当然是哪个方便哪个来的原则。 react-loadable 这个插件,当然这个货得依赖 babel-plugin-syntax-dynamic-import 包。
    webpack配置,加入 babel的 syntax-dynamic-import插件

      module: {
        rules: [
          {
            test: /.(js|jsx)$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'babel-loader',
                query: {
                  presets: ['es2015', 'react', 'stage-0'],
                  plugins: ['syntax-dynamic-import']
                }
              }
            ]
          },
          ...

    react中使用 react-loadable,特别方便

    import Loadable from 'react-loadable';
    ...const MyLoadingComponent = ({isLoading, error, pastDelay}) => {  // Handle the loading state
      if (pastDelay) {    return <div>Loading...</div>;
      }  // Handle the error state
      else if (error) {    return <div>Sorry, there was a problem loading the page.</div>;
      }  else {    return null;
      }
    }const AsyncTestManager = Loadable({
      loader: () => import('./pages/TestManager/Index'),
      loading: MyLoadingComponent
    });
    
    ReactDOM.render(  <Provider store={Store}>
        <BrowserRouter basename="/" forceRefresh={!supportsHistory} keyLength={12}>
          <div>
              <Route exact path="/testManager" component={AsyncTestManager}/>
          </div>
        </BrowserRouter>
      </Provider>,
      document.getElementById('root')
    );

    这个插件具体使用大家查看相关文档,很方便强大。记得上线打包的时候,webpack要启动hash

      output: {
        filename: '[name][chunkhash].js',
        path: BUILD_PATH,
        chunkFilename: '[name][chunkhash].js',
        publicPath: './'
      },

    至此,脚手架搭建走过的坑结束了。
    顺便提下

      output: {
        ...
        publicPath: '../'
      },

    这里一定要配置为 ../ ,不要配置为 ./,因为不小心配错,导致路由按需加载的时候,js路径错误了。


    实战阶段

    这里要介绍下 redux的一个中间件,redux-thunk。何为中间件,以及 redux-thunk的作用,大家可以参考下阮一峰的一篇教程《Redux 入门教程(二):中间件与异步操作》 。 正常情况下,actions返回的只是一个对象,但是我们想发送数据前最好能处理下,所以呢,就需要重写下Store.dispath方法了。中间件就是这样的作用,改写 dispatch,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。比如异步操作:发起ajax请求。视图发起一个action,触发了一个请求,但是action不能返回函数,这时候redux-thunk就起作用了。


    Store初始化

    这个过程,就是把 reducer跟Store绑定在一起,同时引入需要的中间件

    import { createStore, applyMiddleware } from 'redux';
    import thunkMiddleware from 'redux-thunk';
    import reducers from '../reducers';const store = applyMiddleware(
      thunkMiddleware
    )(createStore)(reducers);
    
    export default store;

    applyMiddleware 方法它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。 createStore 方法创建一个 Store。 至于这个参数写法,其实就是es6的柯里化语法。用es3,es5实现其实原理很简单,就是利用了闭包保存了上一次的数据,实现过单列模式的同学应该很清楚。

    function add(number1) {  return function(number2) {    return number1 + number2;
      };
    }var addTwo = add(1)(2);


    Reducer实例

    至于Reducer,其实很好实现,它其实就是单纯的函数。
    例如:

    import * as CONSTANTS from '../../constants/TestControl';const initialState = {};const testControl = (state = initialState, action) => {  switch (action.type) {    case CONSTANTS.GET_DETAILS_PENDING:      return {
            ...state,
            isFetching: true,
            data: action.payload,
            success: false
          };    case CONSTANTS.GET_DETAILS_SUCCEEDED:      return {
            ...state,
            isFetching: false,
            data: action.data.relatedObject,
            success: true
          };    case CONSTANTS.GET_DETAILS_FAILED:      return {
            ...state,
            isFetching: false,
            success: false,
            errorCode: action.data.errorCode
          };    default:      return state;
      }
    };
    
    export default testControl;

    大家应该注意到,这个其实是对应action的一个ajax请求,其中,action.type中 ,
    _PENDING 结尾的表示 ajax正在发起请求;
    _SUCCEEDED 结尾的表示 ajax 请求成功;
    _FAILED 结尾的表示 ajax 请求失败;
    这个我是作为ajax actions的标准命名,大家也可以用其它方式,原则就是:好理解,统一。 当然其它非ajax的actions(包括ajax的action),我的规则就是,命名要表意,常量要大写。

    由于我的项目中reduce有n个,所以 reducers/index.js 是这样的

    import { combineReducers } from 'redux';
    import testManagerList from './TestManager/list';
    import common from './Common';
    import system from './System';
    import evaluate from './Evaluate';
    import ComponentsAddLayer from './Components/addLayer';
    import testNew from './TestNew';
    import testControl from './TestControl';
    
    export default combineReducers({
      testManagerList,
      system,
      evaluate,
      ComponentsAddLayer,
      testNew,
      common,
      testControl
    });

    引入 redux 的combineReducers 方法,这样就把多个 reducer集合到一起了,调用state的时候,只要如此:

    const mapStateToProps = state => ({
      type: state.testManagerList.type
    });

    大家看明白了吧,testManagerList 是我的一个 reducer。


    actions

    Actions 我是作为存放数据的,比如ajax数据请求,视图默认数据这些。

    const testManager = {
      testManager_get_list(options) {    return (dispatch) => {      const fetchData = axios.get('/abtest/getList', options);
          dispatch({
            type: TABLE_GET_LIST_PENDING,
            payload: fetchData
          });
          fetchData.then((response) => {        if (response.data.success) {
              dispatch({
                type: TABLE_GET_LIST_SUCCEEDED,
                ...response
              });
            } else {
              dispatch({
                type: TABLE_GET_LIST_FAILED,
                ...response
              });
            }
          }).catch((error) => {
            dispatch({
              type: TABLE_GET_LIST_FAILED,
              ...error
            });
          });
        };
      },
      testManager_change_tabs(activeTabs) {    return {
          type: TABS_CHANGE,
          active: activeTabs
        };
      },
      testManager_search(value) {    return {
          type: SEARCH,
          keyWord: value
        };
      },
      testManager_parameters(options) {    return {
          type: TEST_MANAGER,
          parameters: Object.assign({}, {
            page: 1,
            pageSize: 10,
            sort: '',
            type: '',
            keyWord: ''
          }, options || {})
        };
      },
      testManager_pagination_change(noop) {    return {
          type: PAGINATION_CHANGE,
          page: noop
        };
      }
    };

    这个模块触发的actions:获取表格列表数据,搜索,分页操作,获取默认配置,很好理解,这里就不说了。 具体如何使用,请看下面的 view 实践


    View实践

    开始的时候,提出几个问题:

    1. 视图如何跟Store绑定;

    2. ACTIONS如何在视图中使用;

    3. 引入的第三方组件样式有什么好的方式修改;

    4. 视图中的props如何获取路由信息;

    先解决第3个问题,一开始我是想重写覆盖第三方的css文件的,后来一看代码量,果断放弃了。还好被我发现了 styled-components 这个插件,果然好用。

    import styled from 'styled-components';
    import Tabs from 'antd/lib/tabs';const TabsStyle = styled(Tabs)`
      float: left;
      .ant-tabs-nav-wrap {
        margin-bottom: 0;
      }
      .ant-tabs-tab {
    
        text-align: center;
        transition: background 0.3s;
        color: #666666;
        padding: 6px 12px;
        font-size: 14px;
        font-weight: 400;
        cursor: pointer;
        user-select: none;
        background-image: none;
        margin-left: -10px;
      }
    `;

    这里面跟写less一样就好了。我是这么觉得。具体大家可以查看下对应的文档。开发过react-native的同学,都很清楚这个插件的给力。

    再结晶第4个问题。react-router 官方提供了 withRouter的api,这个api就是专门为了解决这个问题。

    import CSSModules from 'react-css-modules';
    import { connect } from 'react-redux';
    import { withRouter } from 'react-router-dom';
    ......
    componentDidMount() {    // props中就可拿到路由信息了
        const { ACTIONS, match } = this.props;
        ACTIONS.TestControl_get_testing_detail({ id: match.params.id });
    }const turnCss = CSSModules(TestManager, styles, { allowMultiple: true });
    export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));

    非常方便。 

    再来说第一个问题,视图如何跟Store绑定 Store提供了三个方法
    store.getState()
    store.dispatch()
    store.subscribe()
    其中,Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。所以绑定视图,调用这个方法就好了。 不过redux作者专门针对react,封装了一个库:React-Redux,这里我就直接引用了,这样我就不用处理state了。

    import { connect } from 'react-redux';const mapStateToProps = state => ({
      isFetching: state.testControl.isFetching,
      success: state.testControl.success,
      detail: state.testControl.data
    });const mapDispatchToProps = dispath => ({
      ACTIONS: bindActionCreators(actions, dispath)
    });const turnCss = CSSModules(TestControl, styles, { allowMultiple: true });
    export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));

    这样 TestControl 视图就跟 Store绑定到一起了。 具体的API介绍,大家可以查看下文档,还是很好理解的。

    解决了第一个问题,再来看第2个问题:ACTIONS如何在视图中使用
    ACTIONS的作用,其实就是消息订阅/发布 模式中,发布那个步骤了。这样理解,大家应该明白了吧, 比如: 视图中点击了一个按钮后,回调函数中就直接调用对应的ACTIONS方法即可。
    还要介绍下redux的bindActionCreators方法:
    主要用处:
    一般情况下,我们可以通过Provider将store通过React的connext属性向下传递,bindActionCreators的唯一用处就是需要传递action creater到子组件,并且该子组件并没有接收到父组件上传递的store和dispatch。

    import { bindActionCreators } from 'redux';
    
    import actions from '../../actions';class TestControl extends Component {
      componentDidMount() {    const { ACTIONS, match } = this.props;
        ACTIONS.TestControl_get_testing_detail({ id: match.params.id });
      }  // 开始
      start() {    const { ACTIONS, match } = this.props;
        ACTIONS.TestControl_start({ id: match.params.id });
      }
      render() {
        ...
      }
    }const mapStateToProps = state => ({
      isFetching: state.testControl.isFetching,
      success: state.testControl.success,
      detail: state.testControl.data
    });const mapDispatchToProps = dispath => ({
      ACTIONS: bindActionCreators(actions, dispath)
    });const turnCss = CSSModules(TestControl, styles, { allowMultiple: true });
    export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));

    至此,redux实践结束。


    nginx配置

    因为是单页面模式,且使用了 BrowserRouter,故nginx配置如下:

            location / {
                root   E:/program/ark2/abtest-statics/build/;
                index  index.html index.htm;
                expires -1;
                try_files $uri $uri/ /entry.html;
            }


    其它

    开发一个项目,最好需要一个合理的约定,比如代码风格、模块定义、方法定义、参数定义等等,这些约定中,还要考虑如何便于写和维护单元测试这个因素。这些其实还是挺有挑战的,只能不断去完善。
    上面方案其实还有很多缺陷待解决,需要慢慢改进了。


    相关文章:react技术栈实践(1)

    网易云免费体验馆,0成本体验20+款云产品! 

    更多网易研发、产品、运营经验分享请访问网易云社区


    相关文章:
    【推荐】 教你如何选择BI数据可视化工具
    【推荐】 扫脸动画
    【推荐】 canvas 动画库 CreateJs 之 EaselJS(下篇)

  • 相关阅读:
    MyBatis中传入参数parameterType类型详解
    mybatis逆向工程无法生成带有_表明的解决办法。
    sql server 正则匹配查询数据
    Kibana按时间(小时,分钟,天)统计数据 ,
    AutoCAD 多重引线 文本 左右对齐的问题
    ObjectARXWizard2022安装
    综合练习:词频统计
    powerDesigner设计表自动生成sql语句,设置携带注释
    打jar包后,无法获取resources目录下的word模板文件
    solr新建core,并配置schema
  • 原文地址:https://www.cnblogs.com/163yun/p/9722746.html
Copyright © 2011-2022 走看看