摘要:監(jiān)聽發(fā)射的事件。指定了,而不指定的話,去查里下的映射函數(shù)去修改模塊的。
C_C welcom to cc world
quick-start demo: https://github.com/fantastics... github地址 歡迎大家star,給我更大的動力。簡介
硝煙四起
眾所周知,react本身只是非常優(yōu)雅的解決了視圖層渲染工作,但是隨著應用越來越大,龐大的react組件群體之間狀態(tài)相互其實并不是孤立的,需要一個方案管理把這些狀態(tài)集中管理起來,從而將model和view的邊界劃分得更加清楚,針對于此,facebook官方對于react狀態(tài)管理給了一個flux架構并有一套自己的實現(xiàn),但是社區(qū)里并不滿足于此,基于對flux的理解各個第三方做著給出了的自己的解決方案,狀態(tài)管理框架的戰(zhàn)爭從此拉開序幕,隨著redux橫空出世,大家默默接受了redux的 dispatch action、hit reducer、comibine new state、render new view的理念,在redux世界里,組件需要關心的狀態(tài)變化都由props注入進來,connect作為中間的橋梁將react組件與redux關聯(lián)起來,通過mapStateToProps將redux里定義好的state映射到組件的props上,以此達到讓react組件訂閱它需要關心的state變化的目的。
一統(tǒng)天下
隨著redux生態(tài)逐漸完善,大家默認的把redux當做了react狀態(tài)管理的首選解決方案,所以redux已經(jīng)在react狀態(tài)管理框架里一統(tǒng)天下,通過github star發(fā)現(xiàn),另一個流行的狀態(tài)管理框架mobx頁已經(jīng)鎖定第二的位置,用另一種思路來給大家展示原來狀態(tài)可以這么管理,狀態(tài)管理的格局似乎基本已經(jīng)洗牌完成,可是作為redux重度使用者的我并不滿足與此,覺得在redux世界里,通過props層層穿透數(shù)據(jù),通過provider包裹整個react app應用,只是解決了狀態(tài)的流動問題,而組件的通信任然非常間接與尷尬,依靠redux來完成不是不可以,只是對比vue,任然覺得少了些什么......
why cc
react-control-center并不是簡單的立足于狀態(tài)管理,而是想為你提供更多的有趣玩法,因為現(xiàn)有的狀態(tài)管理解決方案已經(jīng)非常成熟(但是在某些場景未必真的好用),所以cc從一開始設計就讓其api對現(xiàn)有的組件入侵非常之小,你可以在redux項目里局部使用cc來把玩cc的狀態(tài)管理思路,可以從一個組件開始,慢慢在開始漸進式的修改到其他地方,僅僅使用一個register函數(shù),將你的react類注冊為cc類,那么從cc類生成的cc實例,將給你帶來以下新的特性、新的概念、新的思路。
1 所有cc實例都擁有 emit 和 on 的能力,無論組件間嵌套關系多復雜,實現(xiàn)組件間通信將會是如此輕松。
實際上實例還擁有更精準的emitIdentity, emitWith, onIdentity方法,讓用戶基于更精準的力度去發(fā)射或者接收。
emitIdentity(eventName:string, identity:string, ...args), 第一位參數(shù)是事件名,第二位參數(shù)是認證串,剩余參數(shù)是on的handler函數(shù)的實際接收參數(shù),當很多相同組件(如以CcClass:BookItem生成了多個CcInstance)訂閱了同一個事件,但是你只希望通知其中一個觸發(fā)handler調用時,emitIdentity就能派上用場了。
onIdentity(eventName:string, identity:string, hancler:function),監(jiān)聽emitIdentity發(fā)射的事件。
emitWith(eventName:string, option?:{module?:string, ccClassKey?:string, identity?:string})從一個更精準的角度來發(fā)射事件,尋找指定模塊下,指定cc類名的,指定identity的監(jiān)控函數(shù)去觸發(fā)執(zhí)行,具體過程這里先略過,先看下面關于模塊和cc類的介紹,在回過頭來理解這里更容易。
off(eventName:string, option?:{module?:string, ccClassKey?:string, identity?:string}),取消監(jiān)聽。
這些函數(shù)在cc的頂層api里都有暴露,當你的cc app運行起來之后,你可以打開console,輸入cc并回車,你會發(fā)現(xiàn)這些函數(shù)已經(jīng)全部綁定在window.cc對象下了,你可以直接調用他們來完成快速驗證哦,而非通過ccInstance去觸發(fā)^_^
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實例里對同一個事件名監(jiān)聽多次的,這里fooSignal監(jiān)聽了兩次,cc會默認使用最新的監(jiān)聽函數(shù),所以上面?zhèn)€監(jiān)聽變成了無效的監(jiān)聽 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(){} }
2 所有cc實例都可以針對自己的state的任意key定義 computed 函數(shù),cc會在key的值發(fā)生變化自動計算新的computed值并緩存起來,在實例里定義的computed會收集到實例的refComputed對象里。
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 ({this.state.wow} {this.$$refComputed.wow}); } }
3 注冊為cc類的時候,為該cc類設定了一個該cc類所屬的 模塊 ,并通過sharedStateKeys聲明關心該模塊里哪些key(可以是任意的key,也可以是這個模塊的所有key)的變化,則由改cc類產(chǎn)生的cc實例共同監(jiān)聽著這些key對應值的變化,任何一個cc實例改變了這些sharedStateKeys里的值,其他cc實例都能感知到它的變化并自動被cc觸發(fā)渲染。
import cc from "react-control-center"; import React,{Component, Fragment} from "react"; class Foo extends Component{ render(){ returnany jsx fragment here} } //將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); //不設定任何參數(shù),只寫cc類名,cc會把Foo注冊為一個屬于default模塊的cc類 const JustWantToOwnCcAbility = cc.register("JustWantToOwnCcAbility")(Foo); //cc同時也為register提供簡寫函數(shù) //const FooOfM2G = cc.r("FooOfM2",{m:"M2",s:["key1","key2","key3"],g:["g1"]})(Foo})
4 注意在3里我們提到一個概念 模塊,對于cc來說一個完整的模塊包括以下屬性:state、reducer、init、computed,這些參數(shù)都是調用cc.startup時注入,注意,cc雖然不需要用戶像redux那樣要給頂層App組件包裹一層但是要求用戶在app入口文件的第一句話那里觸發(fā)cc.startup 讓整個cc運行起來,store、reducer、init、computed就是cc.startup需要的參數(shù)
store是一個object對象,store里的各個key就表示模塊名,對應的值就是各個模塊對應的state,一個cc實例除了setState方法能夠觸發(fā)修改state,還可以通過dispatch方法派發(fā)action對象去修改state,此時具體的數(shù)據(jù)合成邏輯就體現(xiàn)在下面要說的reducer里了
recuder是一個object對象,recuder里的各個key表示reducer的模塊名,通常用戶可以定義和state的模塊名保持一致,但是可以定義另外的模塊名,所以這里的模塊指的是reducerModuel,不強求用戶定義時和stateModule保持一致,stateModule對應的值一個普通的json對象,key為函數(shù)名,值為處理函數(shù),即處理舊state并合成新state的方法,cc支持函數(shù)為普通函數(shù)、生成器函數(shù)、async函數(shù)。
上面提到了dispatch函數(shù)需要傳遞一個action對象,一個標準的action必須包含type、payload 2個屬性,表示cc要去查recuder里某個模塊下type映射函數(shù)去修改某個模塊的state,具體是什么模塊的type映射函數(shù)和什么模塊對應的state,參見action剩余的兩個可缺省的屬性module和reducerModule的設定規(guī)則,注意,這里再一次提到了reducerModule,以下規(guī)則就體現(xiàn)了為什么cc允許reducer模塊名可以自由定義:
不指定module和reducerModule的話,cc去查reducer里當前cc實例所屬模塊下的type映射函數(shù)去修改當前cc實例所屬模塊的state。
指定了module,而不指定reducerModule的話,cc去查reducer里module下的type映射函數(shù)去修改module模塊的state。
不指定module,指定reducerModule的話,cc去查reducer里reducerModule下type映射函數(shù)去修改當前觸發(fā)dispatch函數(shù)的cc實例所屬的module模塊的state。
指定了module,同時也指定了reducerModule的話,cc去查reducer里reducerModule下type映射函數(shù)去修改module模塊的state。
之所以這樣設計是因為考慮到讓用戶可以自由選擇reducer的模塊描述方式,因為對于cc來說,dispatch派發(fā)的action只是為了準確找到reducer里的處理函數(shù),而reducer的模塊定義并不需要強制和state保持一致給了用戶更多的選擇去劃分reducer的領域init是一個object對象,key是模塊名,嚴格對應stateModule,值是一個函數(shù),如果用戶為某個模塊定義了init函數(shù)表示用戶希望有機會再次初始化某個模塊的state,通常是異步請求后端來的數(shù)據(jù)重新賦值給模塊對應的state
computed是一個object對象,key是模塊名,嚴格對應stateModule,值是一個moduleComputedObject,moduleComputedObject的key指的就是某個module的某個key,value就是為這個key定義的計算函數(shù),函數(shù)的第一為參數(shù)就是key的原始值,cc實例里通過moduleComputed對象取到計算后的新值,特別地,為global模塊定義的moduleComputedObject對象,在cc實例里通過globalComputed對象取到計算后的新值
//code in index.js import api from "@foo/bar/api"; cc.startup({ isModuleMode:true,//表示cc以模塊化方式啟動,默認是false,cc鼓勵用戶使用模塊化管理狀態(tài),更容易劃分領域的邊界 store:{ $$global{//$$global是cc的內(nèi)置模塊,用戶如果沒有顯式的定義,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的函數(shù)里調用的dispatch函數(shù)而被觸發(fā)調用的,此時的state始終指的是最初的那個在cc實例里觸發(fā)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}; }, //支持生成器函數(shù) 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模塊的數(shù)據(jù),其實這里可以一次性return {signal, wow:"just show reducerModule"}來修改數(shù)據(jù), //但是故意的調用dispatch找whatever/generateWow來觸發(fā)修改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函數(shù) changeThemeColor:function({payload:themeColor}){ return {themeColor} } } }, init:{ $$global:setState=>{//為global模塊指定state的初始化函數(shù) 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為f1、f2、f3、f4、f5, ccClass1通過sharedStateKeys觀察模塊M的f1、f2, ccClass2通過sharedStateKeys觀察模塊M的f2、f3、f4,當ccClass1的某個實例改變了f2的值,那么ccClass1的其他實例和ccClass2的所有實例都能感知到f2的變化并被cc觸發(fā)渲染。
5 cc有一個內(nèi)建的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對應的值合并得出來。
9 和react實例一樣,觸發(fā)cc實例render方法,依然是經(jīng)典的setState方法,以及上面提到的dispatch定位reducer方法去修改,除了這兩種cc還有更多自由的選擇,如invoke,effect,xeffect允許用戶直接調用自己定義的函數(shù)去修改state,同reducer函數(shù)一樣,可以是普通函數(shù)、generator函數(shù)、async函數(shù)。
這樣的方式讓用戶有了更多的選擇去觸發(fā)修改state,cc并不強制用戶使用哪一種方式,讓用戶自己摸索和組合更多的最佳實踐invoke一定是修改當前cc實例的state,只需要傳入第一位參數(shù)為具體的用戶自定義執(zhí)行函數(shù),剩余的其他參數(shù)都是執(zhí)行函數(shù)需要的參數(shù)。
effect允許用戶修改其他模塊的state,第一位參數(shù)是moduleName,第二位參數(shù)為具體的用戶自定義執(zhí)行函數(shù),剩余的其他參數(shù)都是執(zhí)行函數(shù)需要的參數(shù)。
xeffect和effect一樣,允許用戶修改其他模塊的state,第一位參數(shù)是moduleName,第二位參數(shù)為具體的用戶自定義執(zhí)行函數(shù),剩余的其他參數(shù)都是執(zhí)行函數(shù)需要的參數(shù),和effect不一樣的地方是xeffect調用的執(zhí)行函數(shù)的參數(shù)列表,第一位是cc注入的ExecuteContext對象,里面包含了module, state, moduleState, xeffect,剩下的參數(shù)才對應的是是用戶調用xeffect是除第一第二位參數(shù)以外的其他參數(shù)
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 (); } } changedBy {changedBy}p1 {p1} p2 {p2} p3 {p3}
10 cc定位的容器型組件的狀態(tài)管理,通常情況一些組件和model非常的有業(yè)務關系或者從屬關系,我們會把這些react類注冊為某個moudle的cc類,觀察這個模塊中的狀態(tài)變化,但是有些組件例如一個Workpace類的確需要觀察很多模塊的狀態(tài)變化,不算是某個模塊對應的視圖組件,此時除了用上面說的sharedToGlobalMapping功能,將需要觀察各個模塊的部分狀態(tài)映射到global里,然后注冊Workpace時為其設定globalStateKeys,就能達到觀察多個模塊的狀態(tài)變化的目的之外,cc還提供另一種思路,注冊Workpace時設定stateToPropMapping,就可以觀察恩義模塊的任意key的值變化,和sharedToGlobalMapping不同之處在于,stateToPropMapping要從this.$$propState里取值,sharedToGlobalMapping是從this.state取值,當然stateToPropMapping不需要模塊主動的將某些key映射到global里,就能達到跨模塊觀察狀態(tài)變化的目錄,cc鼓勵用戶精確對狀態(tài)歸類,并探索最佳組合和最佳實踐
// 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 (); } } 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);open your consoletype and then enter to see what happen cc.setState("counter",{result : 888} )type and then enter to see what happen cc.setGlobalState( {content:"wowowo"} );{gbc}{alias_content}{counter_result}{todoList.length}
github地址:https://github.com/fantastics...
gitee地址:https://gitee.com/nick_zhong/...
quick-start demo: https://github.com/fantastics...
期待大家試用并給出修改意見,真心希望能夠親愛你的能夠感受的cc的魅力和強大,因為作為redux使用者的我(3年了快),不管是用了原生的redux還是dva封裝后的redux,個人都覺得沒有cc使用那么的爽快......先從示例項目開始體驗吧^_^,期待著你和我有一樣的感受
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/101035.html
手挽手帶你學React入門四檔,用人話教你react-redux,理解redux架構,以及運用在react中。學完這一章,你就可以開始自己的react項目了。 之前在思否看到過某個大神的redux搭建 忘記了大神的名字 這里只記得內(nèi)容了 憑借記憶和當時的學習路線寫下本文 隔空感謝 本人學習react-redux的時候遇到了很多坎,特別是不理解為什么這么用,這是什么東西,用來做什么。加上各種名詞讓人...
摘要:袁鳴把價格數(shù)量一一進行記錄,做了質檢記錄合格后辦理了入庫放入冰箱。但這回家宴與上次的家宴有什么不一樣嗎袁鳴首先把做每道菜的整個過程,用什么資源物料多長時間邏輯關系等等都分別錄入到模塊中。 此文已由作者王攀授權網(wǎng)易云社區(qū)發(fā)布。 歡迎訪問網(wǎng)易云社區(qū),了解更多網(wǎng)易技術產(chǎn)品運營經(jīng)驗。 引言: 目前我們團隊在做的供應鏈協(xié)同決策系統(tǒng)(簡稱河洛,取河洛交匯,河圖洛書之意),定位相當于一個計劃、控制與...
摘要:隨著以服務器端的桌面端的和原生移動端為代表的全棧迅猛發(fā)展,真正生產(chǎn)環(huán)境中的前端技術全棧化已經(jīng)逐漸變?yōu)榭赡堋2贿^在一段時間之內(nèi),還是會繼續(xù)向前沖。在剛剛結束的大會上,的作者宣布成為的技術顧問。 隨著以服務器端的NodeJS、桌面端的Electron和原生移動端React Native為代表的全棧JS迅猛發(fā)展,真正生產(chǎn)環(huán)境中的JS/前端技術全棧化已經(jīng)逐漸變?yōu)榭赡堋1M管在前端以外的領域里,J...
摘要:隨著以服務器端的桌面端的和原生移動端為代表的全棧迅猛發(fā)展,真正生產(chǎn)環(huán)境中的前端技術全棧化已經(jīng)逐漸變?yōu)榭赡堋2贿^在一段時間之內(nèi),還是會繼續(xù)向前沖。在剛剛結束的大會上,的作者宣布成為的技術顧問。 隨著以服務器端的NodeJS、桌面端的Electron和原生移動端React Native為代表的全棧JS迅猛發(fā)展,真正生產(chǎn)環(huán)境中的JS/前端技術全棧化已經(jīng)逐漸變?yōu)榭赡堋1M管在前端以外的領域里,J...
摘要:隨著以服務器端的桌面端的和原生移動端為代表的全棧迅猛發(fā)展,真正生產(chǎn)環(huán)境中的前端技術全棧化已經(jīng)逐漸變?yōu)榭赡堋2贿^在一段時間之內(nèi),還是會繼續(xù)向前沖。在剛剛結束的大會上,的作者宣布成為的技術顧問。 隨著以服務器端的NodeJS、桌面端的Electron和原生移動端React Native為代表的全棧JS迅猛發(fā)展,真正生產(chǎn)環(huán)境中的JS/前端技術全棧化已經(jīng)逐漸變?yōu)榭赡堋1M管在前端以外的領域里,J...
閱讀 3076·2021-10-27 14:16
閱讀 2885·2021-09-24 10:33
閱讀 2293·2021-09-23 11:21
閱讀 3236·2021-09-22 15:14
閱讀 823·2019-08-30 15:55
閱讀 1685·2019-08-30 15:53
閱讀 1754·2019-08-29 11:14
閱讀 2195·2019-08-28 18:11