quick-start demo: github.com/fantasticso…
衆所周知,react自己只是很是優雅的解決了視圖層渲染工做,可是隨着應用愈來愈大,龐大的react組件羣體之間狀態相互其實並非孤立的,須要一個方案管理把這些狀態集中管理起來,從而將model和view的邊界劃分得更加清楚,針對於此,facebook官方對於react狀態管理給了一個flux架構並有一套本身的實現,可是社區裏並不知足於此,基於對flux的理解各個第三方作着給出了的本身的解決方案,狀態管理框架的戰爭今後拉開序幕,隨着redux橫空出世,你們默默接受了redux的 dispatch action、hit reducer、comibine new state、render new view的理念,在redux世界裏,組件須要關心的狀態變化都由props注入進來,connect做爲中間的橋樑將react組件與redux關聯起來,經過mapStateToProps將redux裏定義好的state映射到組件的props上,以此達到讓react組件訂閱它須要關心的state變化的目的。vue
隨着redux生態逐漸完善,你們默認的把redux當作了react狀態管理的首選解決方案,因此redux已經在react狀態管理框架裏一統天下,經過github star發現,另外一個流行的狀態管理框架mobx頁已經鎖定第二的位置,用另外一種思路來給你們展現原來狀態能夠這麼管理,狀態管理的格局彷佛基本已經洗牌完成,但是做爲redux重度使用者的我並不知足與此,以爲在redux世界裏,經過props層層穿透數據,經過provider包裹整個react app應用,只是解決了狀態的流動問題,而組件的通訊任然很是間接與尷尬,依靠redux來完成不是不能夠,只是對比vue,任然以爲少了些什麼......react
react-control-center並非簡單的立足於狀態管理,而是想爲你提供更多的有趣玩法,由於現有的狀態管理解決方案已經很是成熟(可是在某些場景未必真的好用),因此cc從一開始設計就讓其api對現有的組件入侵很是之小,你能夠在redux項目裏局部使用cc來把玩cc的狀態管理思路,能夠從一個組件開始,慢慢在開始漸進式的修改到其餘地方,僅僅使用一個register函數,將你的react類註冊爲cc類,那麼從cc類生成的cc實例,將給你帶來如下新的特性、新的概念、新的思路。git
1 全部cc實例都擁有 emit 和 on 的能力,不管組件間嵌套關係多複雜,實現組件間通訊將會是如此輕鬆。github
實際上實例還擁有更精準的emitIdentity, emitWith, onIdentity方法,讓用戶基於更精準的力度去發射或者接收。
emitIdentity(eventName:string, identity:string, ...args), 第一位參數是事件名,第二位參數是認證串,剩餘參數是on的handler函數的實際接收參數,當不少相同組件(如以CcClass:BookItem生成了多個CcInstance)訂閱了同一個事件,可是你只但願通知其中一個觸發handler調用時,emitIdentity就能派上用場了。
onIdentity(eventName:string, identity:string, hancler:function),監聽emitIdentity發射的事件。
emitWith(eventName:string, option?:{module?:string, ccClassKey?:string, identity?:string})從一個更精準的角度來發射事件,尋找指定模塊下,指定cc類名的,指定identity的監控函數去觸發執行,具體過程這裏先略過,先看下面關於模塊和cc類的介紹,在回過頭來理解這裏更容易。
off(eventName:string, option?:{module?:string, ccClassKey?:string, identity?:string}),取消監聽。
這些函數在cc的頂層api裏都有暴露,當你的cc app運行起來以後,你能夠打開console,輸入cc並回車,你會發現這些函數已經所有綁定在window.cc對象下了,你能夠直接調用他們來完成快速驗證哦,而非經過ccInstance去觸發^_^json
import cc from 'react-control-center';
import React,{Component, Fragment} from 'react';
@cc.register('Foo')
class Foo extends Component{
componentDidMount(){
this.$$on('fooSignal',(signal, from)=>{
this.setState({signal, from});
});
//cc是不容許一個cc實例裏對同一個事件名監聽屢次的,這裏fooSignal監聽了兩次,cc會默認使用最新的監聽函數,因此上面個監聽變成了無效的監聽
this.$$on('fooSignal',(signal, from)=>{
this.setState({signal, from:`--${from}--`});
});
this.$$on('fooSignalWithIdentity', 'xxx_id_wow',()=>{
this.setState({signal, from});
})
}
}
@cc.register('Bar')
class Bar extends Component{
render(){
<div>
<button onClick={()=>this.$$emit('fooSignal', 'hello', 'Bar')}>emit</button>
<button onClick={()=>this.$$emit('fooSignal', 'xxx_id_wow', hello', 'Bar')}>emitIdentity</button> <button onClick={()=>this.$$off('fooSignal')}>off event fooSignal</button> </div> } } 複製代碼
2 全部cc實例均可以針對本身的state的任意key定義 computed 函數,cc會在key的值發生變化自動計算新的computed值並緩存起來,在實例裏定義的computed會收集到實例的refComputed對象裏。redux
import cc from 'react-control-center';
import React,{Component, Fragment} from 'react';
@cc.register('Foo')
class Foo extends Component{
constructor(props, context){
super(props, context);
this.state = {woo:'woo cc!'};
}
$$computed(){
return {
wow(wow){
return `computed wow ${wow}`;
}
}
}
componentDidMount(){
this.$$on('fooSignal',(signal, from)=>{
this.setState({signal, from});
});
}
changeWow = (e)=>{
this.setState({wow: e.currentTarget.value});
}
render(){
return (
<div>
<span>{this.state.wow}</span>
<span>{this.$$refComputed.wow}</span>
<input value={this.state.wow} onChange={this.changeWow}/>
</div>
);
}
}
複製代碼
3 註冊爲cc類的時候,爲該cc類設定了一個該cc類所屬的 模塊 ,並經過sharedStateKeys聲明關心該模塊裏哪些key(能夠是任意的key,也能夠是這個模塊的全部key)的變化,則由改cc類產生的cc實例共同監聽着這些key對應值的變化,任何一個cc實例改變了這些sharedStateKeys裏的值,其餘cc實例都能感知到它的變化並自動被cc觸發渲染。後端
import cc from 'react-control-center';
import React,{Component, Fragment} from 'react';
class Foo extends Component{
render(){
return <div>any jsx fragment here</div>
}
}
//將Foo註冊爲一個共享FooOfM1模塊全部key變化的cc類FooOfM1
const FooOfM1 = cc.register('FooOfM1', {module:'M1', sharedStateKeys:'all'})(Foo);
//將Foo註冊爲一個共享FooOfM2模塊key1和key2變化的cc類FooOfM2
const FooOfM2 = cc.register('FooOfM2', {module:'M2',sharedStateKeys:['key1','key2']})(Foo);
//將Foo註冊爲一個共享FooOfM2模塊key1和key2變化,且共享global模塊g1變化的cc類FooOfM2G
const FooOfM2G = cc.register('FooOfM2', {module:'M2',sharedStateKeys:['key1','key2','key3'],globalStateKeys:['g1']})(Foo);
//不設定任何參數,只寫cc類名,cc會把Foo註冊爲一個屬於default模塊的cc類
const JustWantToOwnCcAbility = cc.register('JustWantToOwnCcAbility')(Foo);
//cc同時也爲register提供簡寫函數
//const FooOfM2G = cc.r('FooOfM2',{m:'M2',s:['key1','key2','key3'],g:['g1']})(Foo})
複製代碼
4 注意在3裏咱們提到一個概念 模塊,對於cc來講一個完整的模塊包括如下屬性:state、reducer、init、computed,這些參數都是調用cc.startup時注入,注意,cc雖然不須要用戶像redux那樣要給頂層App組件包裹一層<Provider/>可是要求用戶在app入口文件的第一句話那裏觸發cc.startup 讓整個cc運行起來,store、reducer、init、computed就是cc.startup須要的參數
api
- store是一個object對象,store裏的各個key就表示模塊名,對應的值就是各個模塊對應的state,一個cc實例除了setState方法可以觸發修改state,還能夠經過dispatch方法派發action對象去修改state,此時具體的數據合成邏輯就體如今下面要說的reducer裏了
- recuder是一個object對象,recuder裏的各個key表示reducer的模塊名,一般用戶能夠定義和state的模塊名保持一致,可是能夠定義另外的模塊名,因此這裏的模塊指的是reducerModuel,不強求用戶定義時和stateModule保持一致,stateModule對應的值一個普通的json對象,key爲函數名,值爲處理函數,即處理舊state併合成新state的方法,cc支持函數爲普通函數、生成器函數、async函數。
上面提到了dispatch函數須要傳遞一個action對象,一個標準的action必須包含type、payload 2個屬性,表示cc要去查recuder裏某個模塊下type映射函數去修改某個模塊的state,具體是什麼模塊的type映射函數和什麼模塊對應的state,參見action剩餘的兩個可缺省的屬性module和reducerModule的設定規則,注意,這裏再一次提到了reducerModule,如下規則就體現了爲何cc容許reducer模塊名能夠自由定義:
不指定module和reducerModule的話,cc去查reducer裏當前cc實例所屬模塊下的type映射函數去修改當前cc實例所屬模塊的state。
指定了module,而不指定reducerModule的話,cc去查reducer裏module下的type映射函數去修改module模塊的state。
不指定module,指定reducerModule的話,cc去查reducer裏reducerModule下type映射函數去修改當前觸發dispatch函數的cc實例所屬的module模塊的state。 指定了module,同時也指定了reducerModule的話,cc去查reducer裏reducerModule下type映射函數去修改module模塊的state。
之因此這樣設計是由於考慮到讓用戶能夠自由選擇reducer的模塊描述方式,由於對於cc來講,dispatch派發的action只是爲了準確找到reducer裏的處理函數,而reducer的模塊定義並不須要強制和state保持一致給了用戶更多的選擇去劃分reducer的領域緩存
- init是一個object對象,key是模塊名,嚴格對應stateModule,值是一個函數,若是用戶爲某個模塊定義了init函數表示用戶但願有機會再次初始化某個模塊的state,一般是異步請求後端來的數據從新賦值給模塊對應的state
- computed是一個object對象,key是模塊名,嚴格對應stateModule,值是一個moduleComputedObject,moduleComputedObject的key指的就是某個module的某個key,value就是爲這個key定義的計算函數,函數的第一爲參數就是key的原始值,cc實例裏經過moduleComputed對象取到計算後的新值,特別地,爲global模塊定義的moduleComputedObject對象,在cc實例裏經過globalComputed對象取到計算後的新值
//code in index.js
import api from '@foo/bar/api';
cc.startup({
isModuleMode:true,//表示cc以模塊化方式啓動,默認是false,cc鼓勵用戶使用模塊化管理狀態,更容易劃分領域的邊界
store:{
$$global{//$$global是cc的內置模塊,用戶若是沒有顯式的定義,cc會自動注入一個,只不過是一個不包含任何key的對象
themeColor:'pink',
},
m1:{
name:'zzk',
age:30,
books:[],
error:'',
},
m2:{
wow:'wow',
signal:'haha',
}
},
reducer:{
m1:{
//state表示調用dispatch的cc實例對應的state,moduleState只描述的是cc實例所屬的模塊的state,更多的解釋看下面的4 5 6 7 8這些點。
//特別的注意,若是該方法是由於某個reducer的函數裏調用的dispatch函數而被觸發調用的,此時的state始終指的是最初的那個在cc實例裏觸發dispatch時那個cc實例的state,而moduleState始終指向的是指定的module的的state!!!
changeName:function({payload,state,moduleState,dispatch}){
const newName = payload;
dispatch({module:'m2',type:'changeSignal',payload:'wow!dispatch in reducer function block'});
return {name:newName};
},
//支持生成器函數
changeAge:function*({payload,state,moduleState,dispatch}){
const newAge = payload;
const result = yield api.verifyAge(newAge);
if(result.error)return({error:result.error});
else return {name:newName};
},
//支持async
changeAge:async function({payload:{pageIndex,pageSize}}){
const books = yield api.getBooks(pageIndex, pageSize);
return {books};
}
},
m2:{
changeSignal:function({payload:signal,dispatch}){
//注意m1/changeName裏指定了修改m2模塊的數據,其實這裏能夠一次性return {signal, wow:'just show reducerModule'}來修改數據,
//可是故意的調用dispatch找whatever/generateWow來觸發修改m2的wow值,是爲了演示顯示的指定reducerModule的做用
dispatch({module:'m2',reducerModule:'whatever',type:'generateWow',payload:'just show reducerModule'})
return {signal};
}
},
whatever:{//一個刻意和stateModule沒有保持一致的reducerModule
generateWow:function({payload:wow}){
return {wow};
}
},
$$global:{//爲global模塊指定reducer函數
changeThemeColor:function({payload:themeColor}){
return {themeColor}
}
}
},
init:{
$$global:setState=>{//爲global模塊指定state的初始化函數
api.getThemeColor().then(themeColor=>{
setState({themeColor})
}).catch(err=>console.log(err))
}
},
computed:{
m1:{
name(name){//reverse name
return name.split('').reverse().join('');
}
}
}
})
複製代碼
4 注意第3點裏,註冊一個react類到某個模塊裏成爲cc類時,sharedStateKeys能夠是這個模塊裏的任意key,由於cc容許註冊不一樣的react類到同一個模塊,例如模塊M裏擁有5個key爲f一、f二、f三、f四、f5, ccClass1經過sharedStateKeys觀察模塊M的f一、f2, ccClass2經過sharedStateKeys觀察模塊M的f二、f三、f4,當ccClass1的某個實例改變了f2的值,那麼ccClass1的其餘實例和ccClass2的全部實例都能感知到f2的變化並被cc觸發渲染。
5 cc有一個內建的global模塊,全部的ccClass都天生的擁有觀察global模塊key值變化的能力,註冊成爲cc類時經過globalStateKeys觀察模塊global裏的任意key。
6 全部cc實例上能夠經過prop ccOption設定storedStateKeys,表示該實例上的這些key是須要被cc存儲的,這樣在該cc實例銷燬而後再次掛載回來的時候,cc能夠把這些key的值恢復回來。
7 一個cc實例的state的key除了上面所提到的global、 shared、stored這三種類型,剩下的一種key就是默認的temporary類型了,這種key對應的值隨着組件銷燬就丟失了,再次掛載cc實例時會讀取state裏的默認值。
8 結合4 5 6 7來看,cc實例裏的state是由cc類上申明的sharedStateKeys、globalStateKeys,和cc實例裏ccOption申明的storedStateKeys對應的值,再加上剩下的默認的temporaryStateKeys對應的值合併得出來。
bash
9 和react實例同樣,觸發cc實例render方法,依然是經典的setState方法,以及上面提到的dispatch定位reducer方法去修改,除了這兩種cc還有更多自由的選擇,如invoke,effect,xeffect容許用戶直接調用本身定義的函數去修改state,同reducer函數同樣,能夠是普通函數、generator函數、async函數。
這樣的方式讓用戶有了更多的選擇去觸發修改state,cc並不強制用戶使用哪種方式,讓用戶本身摸索和組合更多的最佳實踐invoke必定是修改當前cc實例的state,只須要傳入第一位參數爲具體的用戶自定義執行函數,剩餘的其餘參數都是執行函數須要的參數。
effect容許用戶修改其餘模塊的state,第一位參數是moduleName,第二位參數爲具體的用戶自定義執行函數,剩餘的其餘參數都是執行函數須要的參數。
xeffect和effect同樣,容許用戶修改其餘模塊的state,第一位參數是moduleName,第二位參數爲具體的用戶自定義執行函數,剩餘的其餘參數都是執行函數須要的參數,和effect不同的地方是xeffect調用的執行函數的參數列表,第一位是cc注入的ExecuteContext對象,裏面包含了module, state, moduleState, xeffect,剩下的參數纔對應的是是用戶調用xeffect是除第一第二位參數之外的其餘參數
import React,{Component, Fragment} from 'react';
import cc from 'react-control-center';
function* loginFn(changedBy, p1, p2 ,p3){
return {changedBy, p1:p1+'--tail', p2:'head--'+p2 ,p3}
}
function* forInvoke(changedBy, p1, p2 ,p3){
const result = yield loginFn(changedBy, p1, p2 ,p3);
return result;
}
function* forEffect(changedBy, p1, p2 ,p3){
const result = yield loginFn(changedBy, p1, p2 ,p3);
return result;
}
function* forXeffect({module, state, moduleState, xeffect}, changedBy, p1, p2 ,p3){
const result = yield loginFn(changedBy, p1, p2 ,p3);
return result;
}
@cc.register('Foo')
class Foo extends Component{
constructor(props, context){
super(props, context);
this.state = {changedBy:'none', p1:'', p2:'' ,p3:''};
}
render(){
const {changedBy, p1, p2, p3} = this.state;
//注,該cc類模塊沒有顯式的聲明模塊,會被cc當作$$default模塊的cc類
return (
<Fragment>
<div>changedBy {changedBy}</div>
<div>p1 {p1} p2 {p2} p3 {p3}</div>
<button onClick={()=>this.$$invoke(forInvoke, 11,22,33)}>invoke</button>
<button onClick={()=>this.$$effect('$$default',forEffect, 11,22,33)}>effect</button>
<button onClick={()=>this.$$xeffect('$$default',forXeffect, 11,22,33)}>xeffect</button>
</Fragment>
);
}
}
複製代碼
10 cc定位的容器型組件的狀態管理,一般狀況一些組件和model很是的有業務關係或者從屬關係,咱們會把這些react類註冊爲某個moudle的cc類,觀察這個模塊中的狀態變化,可是有些組件例如一個Workpace類的確須要觀察不少模塊的狀態變化,不算是某個模塊對應的視圖組件,此時除了用上面說的sharedToGlobalMapping功能,將須要觀察各個模塊的部分狀態映射到global裏,而後註冊Workpace時爲其設定globalStateKeys,就能達到觀察多個模塊的狀態變化的目的以外,cc還提供另外一種思路,註冊Workpace時設定stateToPropMapping,就能夠觀察恩義模塊的任意key的值變化,和sharedToGlobalMapping不一樣之處在於,stateToPropMapping要從this.$$propState裏取值,sharedToGlobalMapping是從this.state取值,固然stateToPropMapping不須要模塊主動的將某些key映射到global裏,就能達到跨模塊觀察狀態變化的目錄,cc鼓勵用戶精確對狀態歸類,並探索最佳組合和最佳實踐
// these code written in https://github.com/fantasticsoul/rcc-simple-demo/tree/master/src/cc-use-case/WatchMultiModule
// you can update the rcc-simple-demo lastest version, and run it, then switch tab watch-multi-module, you will see what happen
import React from 'react';
import cc from 'react-control-center';
class WatchMultiModule extends React.Component {
render() {
console.log('%c@@@ WatchMultiModule', 'color:green; border:1px solid green;');
console.log(`type cc.setState('todo',{todoList:[{id:Date.now()+'_1',type:'todo',content:'nono'},{id:Date.now()+'_2',type:'todo',content:'nono'}]}) in console`);
const { gbc, alias_content, counter_result, todoList } = this.$$propState;
return (
<div style={{width:'100%',height:'600px', border:'1px solid darkred'}}>
<div>open your console</div>
<div>type and then enter to see what happen <span style={{paddingLeft:'28px',color:'red'}}>cc.setState('counter',{result : 888} )</span></div>
<div>type and then enter to see what happen <span style={{paddingLeft:'28px',color:'red'}}>cc.setGlobalState( {content:'wowowo'} );</span></div>
<div>{gbc}</div>
<div>{alias_content}</div>
<div>{counter_result}</div>
<div>{todoList.length}</div>
</div>
);
}
}
const stateToPropMapping = {
'$$global/borderColor': 'gbc',
'$$global/content': 'alias_content',
'counter/result': 'counter_result',
'todo/todoList': 'todoList',
};
//two way to declare watching multi module cc class
export default cc.connect('WatchMultiModule', stateToPropMapping)(WatchMultiModule);
//export default cc.register('WatchMultiModule', {stateToPropMapping})(WatchMultiModule);
複製代碼
github地址:github.com/fantasticso…
gitee地址:gitee.com/nick_zhong/…
quick-start demo: github.com/fantasticso…
期待你們試用並給出修改意見,真心但願可以親愛你的可以感覺的cc的魅力和強大,由於做爲redux使用者的我(3年了快),不論是用了原生的redux仍是dva封裝後的redux,我的都以爲沒有cc使用那麼的爽快......先從示例項目開始體驗吧^_^,期待着你和我有同樣的感覺