[react-control-center 番外篇1] ant-design-pro powered by C_C

正楷發表於2019-01-19

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,發生的事情大概經過了以下幾步
    [react-control-center 番外篇1] ant-design-pro powered by C_C
  • 因為此文主要是介紹和證明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:{
    callAnotherMethod:function*(){
      return {wow:'changeWowValue'};
    }
    fetch:function*() {
      const response = yield fakeChartData();
      return response;
    },
    //這裡稍做修改,演示了reducer方法內如何呼叫其他reducer方法
    fetchSalesData:async function({state, moduleState, dispatch, payload}) {
      console.log(sate, moduleState, payload);
      //這裡的dispatch如果不指定module和reducerModule,就隱含的是由最初的在cc例項裡觸發$$dispatch時計算好的module和reducerModule
      await dispatch({type:'callAnotherMethod'});
      const response = await 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並探索它更多有趣的玩法

相關文章