ant-design-pro powered by react-control-center C_C
发布于 5 年前 作者 fantasticsoul 2438 次浏览 来自 分享

Ant Design Pro Powered by react-control-center

cc版本的ant-design-pro来了,ant-design-pro powered by C_C


我们先看看redux是如何工作起来的,在来细比较ccredux的最大的不同之处

redux如何工作?订阅redux单一状态树里的部分数据源,让组件被redux接管,从而实现当订阅的数据源发生变化时才触发渲染的目的
  • 我们知道,在redux世界里,可以通过一个配置了mapStateToPropsconnect高阶函数去包裹一个组件,能够得到一个高阶组件,该高阶组件的shouldComponentUpdate会被redux接管,通过浅比较this.props!==nextProps来高效的决定被包裹的组件是否要触发新一轮的渲染,之所以能够这么直接进行浅比较,是因为在redux世界的reducer里,规定了如果用户改变了一个状态某一部分值,一定要返回一个新的完整的状态,如下所示,是一个传统的经典的connectreducer写法示例。
/** code in component/Foo.js, connect现有组件,配置需要观察的`store`树中的部分state,绑定`action` */
class Foo extends Component{
  //...
  render(){
    return (
      <div>
        <span>{this.props.fooNode.foo}</span>
        <button onClick={this.props.actions.incFoo}>incFoo</button>
      </div>
    );
  }
}
export default connect(
  state => ({
    list: state.list,
    fooNode: state.fooNode,
  }),
  dispatch => ({
    actions: bindActionCreators(fooActionCreator, dispatch)
  })
)(Foo)

/** code in action/foo.js, 配置action纯函数 */
export const incFoo = () =>{
  return {type:'INC_FOO'};
}

/** code in reducer/foo.js, 定义reducer函数 */
function getInitialState() {
  return {
    foo: 1,
    bar: 2,
  };
}

export default function (state = getInitialState(), action) {
  switch (action.type) {
    case 'INC_FOO': {
      state.foo = state.foo + 1;
      return {...state};
    }
    default:{
      return state;
    }
      
  }
}
  • ant-design-prodva世界里,dvaredux做了一层浅封装,省去了繁琐的定义action函数,connect时要绑定action函数等过程,给了一个命名空间的概览,一个命名空间下可以定义stateeffectsreducers这些概念,组件内部dispatchaction对象的type的格式形如${namespaceName}/${methodName},这样dva就可以通过解析用户调用dispatch函数时派发的action对象里的type值而直接操作effects里的函数,在effects里的某个函数块内处理完相应逻辑后,用户可以调用dva提供给用户的put函数去触发reducers里的对应函数去合成新的state,尽管流程上简化了不少,但是归根到底还是不能脱离redux的核心理念,需要合成一个新的state! 以下示例是ant-design-pro里一个经典的变种redux流程写法.
/** code in component/Foo.js, connect现有组件,配置需要观察的`store`树中的部分state */
import { connect } from 'dva';

class Foo extends Component{
  //...
  render(){
    return (
      <div>
        <span>{this.props.fooNode.foo}</span>
        <button onClick={()=>this.props.dispatch({type:'fooNode/incFoo', payload:2})}>incFoo</button>
      </div>
    );
  }
}
export default connect(
  state => ({
    list: state.list,
    fooNode: state.fooNode,
  })
)(Foo)

/** code in models/foo.js */
import logService from '@/services/log';

export default {
  namespace: 'fooNode',

  state: {
    foo: 1,
    bar: 1,
  },

  effects: {
    *query({ payload:incNumber }, { call, put }) {
      yield call(logService, incNumber);
      yield put({
        type: 'saveFoo',
        payload: incNumber,
      });
    },
  },

  reducers: {
    saveFoo(state, action) {
      return { ...state, foo:action.payload };
    },
  },
};

cc如何工作?订阅react-control-center的部分数据源,当这些部分数据源任意一个部分发生变化时,cc主动通知该组件触发渲染
  • ccredux最大的不同就是,cc接管了所有cc组件的具体引用,当用户的react组件注册成为cc组件时ccregister函数需要用户配置ccClassKeymodulesharedStateKeysglobalStateKeysstateToPropMapping等参数来告诉cc怎么对这些具体的引用进行分类,然后cc就能够高效并精确的通知哪些cc组件实例能够发生新一轮的渲染。
  • 实际上当你在cc组件实例里调用this.setState时,效果和原有的this.setState毫无差别,但是其实cc组件实例的this.setState已近不再是原来的了,这个函数已经被cc接管并做了相当多的工作,原来的已经被cc保存为reactSetState,当你调用cc组件实例的this.setState,发生的事情大概经过了以下几步
  • 因为此文主要是介绍和证明cc 的弱入侵性和灵活性,而ant-design-pro里的组件的state并不需要被接管,所以我们下面的示例写法仅仅使用cc.connect函数将组件的状态和cc.store打通,这些状态并非从state里取,而是从this.$$propState里获取,下面的示例注释掉的部分是原dva写法,新增的是cc的写法.
  • (备注:此处仅仅展示关键代码详细代码 )
/** code in src/routes/Dashboard/Analysis.js, */
import React, { Component } from 'react';
// import { connect } from 'dva';
import cc from 'react-control-center';

// @connect(({ chart, loading }) => ({
//   chart,
//   loading: loading.effects['chart/fetch'],
// }))
@cc.connect('Analysis', {
  'chart/*': '',
  'form/*': '', // this is redundant here, just for show isPropStateModuleMode's effect
}, { isPropStateModuleMode: true })
export default class Analysis extends Component {
  state = {
    loading: true,
    salesType: 'all',
    currentTabKey: '',
    rangePickerValue: [],
  }

  componentDidMount() {
    this.$$dispatch({
      module: 'chart', type: 'fetch'
    }).then(() => this.setState({ loading: false }));
    // this.props.dispatch({
    //   type: 'chart/fetch',
    // }).then(() => this.setState({ loading: false }));
  }

  componentWillUnmount() {
    // const { dispatch } = this.props;
    // dispatch({
    //   type: 'chart/clear',
    // });
    // this.$$dispatch({ module: 'chart', type: 'clear' });
  }

  handleRangePickerChange = (rangePickerValue) => {
    this.setState({
      rangePickerValue,
    });

    // this.props.dispatch({ type: 'chart/fetchSalesData'});
    this.$$dispatch({ module: 'chart', type: 'fetchSalesData' });
  }

  selectDate = (type) => {
    this.setState({
      rangePickerValue: getTimeDistance(type),
    });

    // this.props.dispatch({ type: 'chart/fetchSalesData' });
    this.$$dispatch({ module: 'chart', type: 'fetchSalesData' });
  }


  render() {
    const { rangePickerValue, salesType, currentTabKey, loading } = this.state;
    console.log('%c@@@ Analysis !!!', 'color:green;border:1px solid green;');
    const {
      visitData,
      visitData2,
      salesData,
      searchData,
      offlineData,
      offlineChartData,
      salesTypeData,
      salesTypeDataOnline,
      salesTypeDataOffline,
    } = this.$$propState.chart;
    // } = this.props.chart;
    
  }
}

  • models的替换
/** 原来的model,code in src/models/chart */
export default {
  namespace: 'chart',

  state: {
    visitData: [],
    visitData2: [],
    salesData: [],
    searchData: [],
    offlineData: [],
    offlineChartData: [],
    salesTypeData: [],
    salesTypeDataOnline: [],
    salesTypeDataOffline: [],
    radarData: [],
  },

  effects: {
    *fetch(_, { call, put }) {
      const response = yield call(fakeChartData);
      yield put({
        type: 'save',
        payload: response,
      });
    },
    *fetchSalesData(_, { call, put }) {
      const response = yield call(fakeChartData);
      yield put({
        type: 'save',
        payload: {
          salesData: response.salesData,
        },
      });
    },
  },

  reducers: {
    save(state, { payload }) {
      return {
        ...state,
        ...payload,
      };
    },
    setter(state, { payload }) {
      return {
        ...state,
        ...payload,
      };
    },
    clear() {
      return {
        visitData: [],
        visitData2: [],
        salesData: [],
        searchData: [],
        offlineData: [],
        offlineChartData: [],
        salesTypeData: [],
        salesTypeDataOnline: [],
        salesTypeDataOffline: [],
        radarData: [],
      };
    },
  },
};

/** cc定义的model,code in src/cc-models/chart  */
function getInitialState() {
  return {
    wow: 'wow',
    visitData: [],
    visitData2: [],
    salesData: [],
    searchData: [],
    offlineData: [],
    offlineChartData: [],
    salesTypeData: [],
    salesTypeDataOnline: [],
    salesTypeDataOffline: [],
    radarData: [],
  }
}

export default {
  module:'',
  state:getInitialState(),
  reducer:{
    *fetch() {
      const response = yield fakeChartData();
      return response;
    },
    *fetchSalesData() {
      const response = yield fakeChartData();
      const salesData = response.salesData;
      return { salesData };
    },
    clear(){
      const originalState = getInitialState();
      return originalState;
    }
  }
}
由上可以发现,cc里的setState需要的statedispatch对应函数返回的state,都是react鼓励的部分state,你需要改变哪一部分的state,就仅仅把这一部分state交给cc就好了。同时cc也兼容redux生态的思路,一切共享的数据源都从props注入,而非存储在state里。
因为所有的改变state的行为都会经过$$changeState,所以状态的变化依然是可预测的同时也是可以追踪的,后面cc的迭代版本里会利用immutable.js,来让状态树可以回溯,这样cc就可以实现时间旅行的功能了,敬请期待.

注意哦! 现在我仅仅先把两个路由级别的组件交给cc处理, ant pro任然完美工作起立, 这两个路由文件是 routes/Dashboard/Analysis.jsroutes/Forms/Basic.js.
同时我也新增了一个路由组件 routes/Dashboard/CCState.js 来展示cc强大能力, 这个组件还没有彻底写完,将会被持续更新的, 就像 我为cc专门写的引导示例一样,将会很快会为大家带来更多的精彩演示

希望我亲爱的朋友们花一点时间了解react-control-center并探索它更多有趣的玩法

6 回复

其实react 搞了这么多,不到3个月,就能发现社区有推崇的更好的写法。。。 要不要跟进? 要不要重构?

累了? 那老老实实用 angular 全家桶 …
ts rxjs 那一套,其他框架迟早得上…

@zuohuadong 其实react真的很美妙,骚年

@fantasticsoul 折腾自己是很美妙 ~

@waitingsong no,no, 不是折腾,真的是醍醐灌顶、大彻大悟版的享受,这种感觉一单你体会到了就会爱上了

@fantasticsoul 折腾了多少年分离了逻辑和样式,jsx 又搞回去了,受不了。

@fantasticsoul react 技术栈更新太频繁, redux mobx apollo 甚至 后来的hooks 解决来解决去,你就发现 angular 全家桶早就解决这些问题了,只不过 你当时觉得 angular 太重。

回到顶部