我們先看看redux是如何工作起來的,在來細比較cc
和redux
的最大的不同之處
redux
如何工作?訂閱redux
單一狀態樹裡的部分資料來源,讓元件被redux
接管,從而實現當訂閱的資料來源發生變化時才觸發渲染的目的
- 我們知道,在
redux
世界裡,可以通過一個配置了mapStateToProps
的connect
高階函式去包裹一個元件,能夠得到一個高階元件,該高階元件的shouldComponentUpdate
會被redux接管,通過淺比較this.props!==nextProps來高效的決定被包裹的元件是否要觸發新一輪的渲染,之所以能夠這麼直接進行淺比較,是因為在redux
世界的reducer
裡,規定了如果使用者改變了一個狀態某一部分值,一定要返回一個新的完整的狀態,如下所示,是一個傳統的經典的connect
和reducer
寫法示例。
/** 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-pro
的dva
世界裡,dva
將redux
做了一層淺封裝,省去了繁瑣的定義action
函式,connect
時要繫結action
函式等過程,給了一個名稱空間的概覽,一個名稱空間下可以定義state
、effects
、reducers
這些概念,元件內部dispatch
的action
物件的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
主動通知該元件觸發渲染
cc
和redux
最大的不同就是,cc
接管了所有cc元件
的具體引用,當使用者的react元件
註冊成為cc元件時
,cc
的register
函式需要使用者配置ccClassKey
、module
、sharedStateKeys
、globalStateKeys
、stateToPropMapping
等引數來告訴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;
}
}
複製程式碼
/** 原來的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
需要的state
和dispatch
對應函式返回的state
,都是react鼓勵的部分state
,你需要改變哪一部分的state
,就僅僅把這一部分state
交給cc
就好了。同時cc也相容redux
生態的思路,一切共享的資料來源都從props
注入,而非儲存在state
裡。
因為所有的改變state的行為
都會經過$$changeState
,所以狀態的變化依然是可預測的同時也是可以追蹤的,後面cc的迭代版本里會利用immutable.js
,來讓狀態樹可以回溯,這樣cc
就可以實現時間旅行
的功能了,敬請期待.
注意哦! 現在我僅僅先把兩個路由級別的元件交給cc處理, ant pro任然完美工作起立, 這兩個路由檔案是 routes/Dashboard/Analysis.js
和 routes/Forms/Basic.js
.
同時我也新增了一個路由元件 routes/Dashboard/CCState.js
來展示cc強大能力, 這個元件還沒有徹底寫完,將會被持續更新的, 就像 我為cc專門寫的引導示例一樣,將會很快會為大家帶來更多的精彩演示