本篇較長圆裕,前面是目前flux開源框架的一些分析取逾,后面是架構(gòu)設(shè)計過程蜓竹。您可以直奔主題箕母。
用RN最大的難題是設(shè)計思想的轉(zhuǎn)變,以前的設(shè)計方法論已經(jīng)不太適用了俱济。而RN僅僅提供了view的框架嘶是,構(gòu)建完整app的架構(gòu)并沒有直接提供。
考慮目前遇到的如下問題蛛碌,希望架構(gòu)給出解決方案聂喇。
- 交互:如何解決組件間通信【父子、子父蔚携、兄弟等希太,特別是跨層or反向數(shù)據(jù)流動等】;用state還是接口操作組件酝蜒;
- 職責(zé):組件狀態(tài)放哪誊辉,業(yè)務(wù)邏輯放哪,數(shù)據(jù)放哪秕硝,因為太靈活了芥映,怎么做都可以實現(xiàn)功能洲尊,但是怎么做才是最好的远豺,才是最正確的呢?
todo一個問題:由于react是面向狀態(tài)編程坞嘀,相當(dāng)于react的組件只關(guān)注數(shù)據(jù)的最終狀態(tài)躯护,數(shù)據(jù)是怎么產(chǎn)生的并不關(guān)心,但是某些場景下丽涩,數(shù)據(jù)如何產(chǎn)生的是會影響到組件的一些行為的【比如一個新增行要求有動畫效果棺滞,查詢出的行就不需要等】,這在RN中很難描述矢渊。继准。。矮男。移必。
RN架構(gòu)就是為解決上述問題提供的指導(dǎo)和方法論,是通盤考慮整個開發(fā)毡鉴、測試秒赤、運維的狀況憎瘸,做出的考慮最全面的抉擇,或者為抉擇提供依據(jù)潮售。
目前為react服務(wù)的架構(gòu)也有一些了锅风,如Flux遏弱,Reflux,Redux泪姨,Relay饰抒,Marty袋坑。
Flux
flux是官方提供的架構(gòu),目的是分層解耦婆誓,職責(zé)劃分清晰也颤,誰負(fù)責(zé)干啥很明確。具體描述可以參考官方文檔文留,這里不詳述竭沫。
- action 封裝請求
- dispatcher 注冊處理器、分發(fā)請求
- store 是處理器蜕提,處理業(yè)務(wù)邏輯,保存數(shù)據(jù)
- view 根據(jù)store提供的數(shù)據(jù)進行展現(xiàn);接受用戶的輸入并發(fā)出action請求拄氯。
數(shù)據(jù)流動:Action-> Dispatcher -> Store -> Component
但我覺得解耦的太細(xì)了,干一個事镣煮,要做太多太多的額外工作了鄙麦。
光注冊監(jiān)聽動作就2次,一次是store注冊到dispatcher介衔,一次是view注冊到store中炎咖。
而且寒波,注冊到dispatcher的監(jiān)聽?wèi)?yīng)該都不叫注冊俄烁,架構(gòu)完全沒有提供任何封裝,直接暴露一個統(tǒng)一的回調(diào)方法粹胯,里面自行if else路由不同的store辰企。
Reflux
結(jié)構(gòu)上與flux架構(gòu)基本一致,去掉了flux的一些冗余操作【比如沒有了dispatcher】议忽,架構(gòu)更加簡潔和緊湊十减,用到了一些約定大于配置的理念愤估。
基本上將flux的架構(gòu)冗余都簡化了玩焰,可以說是flux的去冗余提升版,但是沒有本質(zhì)的變化蔓榄。
╔═════════╗ ╔════════╗ ╔═════════════════╗
║ Actions ║──────>║ Stores ║──────>║ View Components ║
╚═════════╝ ╚════════╝ ╚═════════════════╝
^ │
└──────────────────────────────────────┘
- 更容易的監(jiān)聽并炮。listenables和約定以on開頭的方法甥郑。等逃魄。
- 去掉了dispatcher澜搅。
- action可以進行aop編程伍俘。
- 去掉了waitfor。store可以監(jiān)聽store癌瘾。
- component提供了一系列mixin,方便注冊\卸載到store的監(jiān)聽和與store交互等饵溅。
Redux
社區(qū)內(nèi)比較受推崇妨退,因為用起來相對比較簡單
特性:
1、分層設(shè)計碧注,職責(zé)清晰。
2、要求store reducer都是頁面單例放典,易于管理奋构。
3、action為請求dto對象弥臼,是請求類型,請求數(shù)據(jù)的載體。
4纳猪、reducer是處理請求的方法氧卧。不允許有狀態(tài),必須是純方法氏堤。必須嚴(yán)格遵守輸入輸出沙绝,中間不允許有異步調(diào)用。不允許對state直接進行修改,要想修改必須返回新對象闪檬。
5星著、store
- 維持應(yīng)用的state;
- 提供 getState() 方法獲取 state粗悯;
- 提供 dispatch(action) 方法分發(fā)請求來更新 state虚循;門面模式,要求所有的請求滿足統(tǒng)一的格式【可以進行路由为黎、監(jiān)控邮丰、日志等】,統(tǒng)一的調(diào)用方式铭乾。
- 通過 subscribe(listener) 注冊監(jiān)聽器監(jiān)聽state的變化剪廉。
6、官方文檔寫的較為詳細(xì)炕檩,從設(shè)計到開發(fā)都有斗蒋,比flux要好
痛處如下,看能否接受或者解決:
1笛质、redux的原則:state不能被修改泉沾。
- 其實這個用react的state也會有同樣的問題,最好把state設(shè)計的沒有冗余妇押,盡量少出這種情況
- 解決方案:參考官方跷究,因為我們不能直接修改卻要更新數(shù)組中指定的一項數(shù)據(jù),這里需要先把前面和后面都切開敲霍。如果經(jīng)常需要這類的操作俊马,可以選擇使用幫助類 React.addons.update,updeep肩杈,或者使用原生支持深度更新的庫 Immutable柴我。最后,時刻謹(jǐn)記永遠(yuǎn)不要在克隆 state 前修改它扩然。
2艘儒、單一的龐大的reducer的拆分
- 這塊設(shè)計也不好做,會讓人疑惑
- 官方給的demo中直接按state的內(nèi)容區(qū)分夫偶,我覺得這樣做不好界睁,如果后期有跨內(nèi)容的情況,就比較奇怪了索守。官方給的combineReducers方案晕窑,也只是減少代碼量,本質(zhì)沒有變化卵佛,state還是拆分處理,路由還是業(yè)務(wù)邏輯自己來做。
- 解決方案:還是處理一整個state截汪,可以按照約定寫reducer類而不是方法疾牲,類里按照actionType建方法,架構(gòu)自動路由并調(diào)用衙解。
- 以前做java架構(gòu)阳柔,路由一定是架構(gòu)來調(diào)用的,目前感覺各大flux框架都是解決問題不徹底蚓峦。
3舌剂、官方建議設(shè)計模式:頂層容器組件才對redux有依賴,組件間通過props來傳遞數(shù)據(jù)暑椰。按照這樣設(shè)計還是沒有解決組件間交互和數(shù)據(jù)傳遞的問題霍转。官方react設(shè)計建議:react的設(shè)計建議:http://camsong.github.io/redux-in-chinese/docs/basics/UsageWithReact.html
4、使用connect將state綁定到component一汽。此處有些黑盒了避消。
5、異步action用來請求服務(wù)端數(shù)據(jù),利用middleware增強createStore的dispatch后即支持召夹。
結(jié)論
開源架構(gòu)封裝的簡單的flux會產(chǎn)生較多的冗余代碼岩喷。
開源架構(gòu)封裝的復(fù)雜的redux,其和RN綁定封裝了一些東西监憎,是一個黑盒纱意,不易理解和維護。
介于上述兩者之間的開源架構(gòu)reflux鲸阔,文檔較上述2個少偷霉,不知道其可持續(xù)性如何。如果一定要用開源架構(gòu)的話隶债,我覺得他稍加封裝是一個較為推薦的選擇腾它。
不是特復(fù)雜的程序【一般spa的程序會更復(fù)雜一些,而RN并不是spa】死讹,這些概念只會增加你的開發(fā)難度瞒滴,并且對后面維護的人要求更高。
我們繼續(xù)頭腦風(fēng)暴赞警,繼續(xù)抽象總結(jié)一下flux系列框架妓忍, flux系列框架干了什么,沒干什么愧旦,針對開篇提出的問題世剖。
- 【解決職責(zé)】flux系列框架都做到了解耦,分層笤虫,誰該干什么就干什么旁瘫,不許干別的祖凫,讓代碼讀起來更有預(yù)測性和一致性,方便維護
- 【解決通信】繼續(xù)解耦酬凳,flux系列框架采用事件機制解決各層之間通信惠况,采用props傳遞解決各組件之間通信。
事件系統(tǒng)是關(guān)鍵
flux系列架構(gòu)解決通信問題的方法是使用事件系統(tǒng)宁仔,事件系統(tǒng)中的回調(diào)函數(shù)是業(yè)務(wù)邏輯稠屠,redux是【store action reducer】,flux是【action dispacher store】翎苫。
我們真的需要事件系統(tǒng)嗎权埠?
事件系統(tǒng)的好處:
- 一個事件可以注冊多個回調(diào)函數(shù)
- 各回調(diào)函數(shù)間沒有耦合。
關(guān)于1
需要注冊多個的這種情況并不多見煎谍,不信你去翻看你已經(jīng)寫好的代碼攘蔽,是不是大部分都是注冊一個。
關(guān)于2
解耦確實很徹底粱快,但是當(dāng)我需要控制執(zhí)行順序秩彤,需要等a執(zhí)行完在執(zhí)行b,怎么辦事哭?ok你可以先注冊a在注冊b啊漫雷。那a要是一個fetch或ajax操作呢?這時候只能乖乖的在a的請求結(jié)束回調(diào)函數(shù)中進行調(diào)用b了鳍咱。又變成a依賴b了降盹。當(dāng)然,你可以繼續(xù)dispatch(b)谤辜,這就沒有耦合了蓄坏。但是你要知道注冊一個事件是要有成本的,要寫action丑念,而且這種dispatch的方式涡戳,真的不太適合人類的閱讀,dispatch一下脯倚,下一步都有誰來執(zhí)行都不知道渔彰,這哪有直接調(diào)用來的爽快。
好吧說到這推正,最后的結(jié)論也出來了恍涂,不使用開源架構(gòu),借助其好的思想植榕,替換其事件系統(tǒng)為面向?qū)ο蠼Y(jié)構(gòu)再沧,自行封裝架構(gòu)。
架構(gòu)設(shè)計
再次強調(diào):目前僅考慮如何應(yīng)用于react native
先扣題尊残,針對開篇問題的解決方案如下
交互
1炒瘸、組件對外發(fā)布:組件對外只允許使用props來暴露功能淤堵,不允許使用接口及其它一切方式
2、父子組件間:組件的子組件通過父組件傳遞的接口來與父組件通信
3什燕、兄弟組件間:
- 方案1:假設(shè)a要調(diào)用b粘勒,參考第一條的話竞端,其實就是a要改變b的props屎即,那么a只要改b的props的來源即可,b的props的來源一般就是根組件的state事富。那么根組件就要有組織和協(xié)調(diào)的能力技俐。
- 方案2:利用事件機制,基本同flux架構(gòu)统台。略復(fù)雜雕擂,且我們并不需要事件的特性,本架構(gòu)設(shè)計不推薦贱勃。
職責(zé)
1井赌、root-存放state,組織子view組件贵扰,組織業(yè)務(wù)邏輯對象等
2仇穗、子view組件-根據(jù)this.props渲染view。
3戚绕、業(yè)務(wù)邏輯對象-提供業(yè)務(wù)邏輯方法
根據(jù)以上推導(dǎo)纹坐,我將其命名為面向?qū)ο蟮腞eactNative組件架構(gòu)設(shè)計,它與flux系列架構(gòu)的最大的不同之處在于舞丛,用業(yè)務(wù)邏輯對象來代替了【store action dispatcher】or【store reducer】的事件系統(tǒng)耘子。業(yè)務(wù)邏輯對象就是一組對象,用面向?qū)ο蟮脑O(shè)計理念設(shè)計出的n個對象球切,其負(fù)責(zé)處理整個頁面的業(yè)務(wù)邏輯谷誓。
以上為推導(dǎo)過程,干貨才開始吨凑。捍歪。。怀骤。
面向?qū)ο蟮腞eactNative組件\頁面架構(gòu)設(shè)計
一個獨立完整的組件\頁面一般由以下元素構(gòu)成:
1费封、root組件,1個蒋伦,
- 負(fù)責(zé)初始化state
- 負(fù)責(zé)提供對外props列表
- 負(fù)責(zé)組合子view組件形成頁面效果
- 負(fù)責(zé)注冊業(yè)務(wù)邏輯對象提供的業(yè)務(wù)邏輯方法
- 負(fù)責(zé)管理業(yè)務(wù)邏輯對象
2弓摘、view子組件,0-n個痕届,
- 根據(jù)props進行視圖的渲染
3韧献、業(yè)務(wù)邏輯對象末患,0-n個,
- 提供業(yè)務(wù)邏輯方法
root組件
root組件由以下元素組成:
1锤窑、props-公有屬性
2璧针、state-RN體系的狀態(tài),必須使用Immutable對象
3、私有屬性
4渊啰、業(yè)務(wù)邏輯對象的引用-在componentWillMount中初始化
5探橱、私有方法-以下劃線開頭,內(nèi)部使用or傳遞給子組件使用
6绘证、公有方法【不推薦】隧膏,子組件和外部組件都可以用,但不推薦用公有方法來對外發(fā)布功能嚷那,破壞了面向狀態(tài)編程胞枕,盡可能的使用props來發(fā)布功能
子view組件
子view組件中包含:
1、props-公有屬性
2魏宽、私有屬性-強烈不建議有腐泻,除非你能理解以下幾點,建議放在父組件or業(yè)務(wù)邏輯對象中
- 絕對不允許和父組件的屬性or狀態(tài)有冗余队询。無論是顯性冗余還是計算結(jié)果冗余派桩,除非你能確定結(jié)算是性能的瓶頸。
- 此屬性只有自己會用娘摔,父組件和兄弟組件不會使用窄坦,如果你不確定這點,請把這個組件放到父組件上凳寺,方便組件間通信
3鸭津、私有方法-僅作為渲染view的使用,不許有業(yè)務(wù)邏輯
4肠缨、公有方法【不推薦逆趋,理由同root組件】
業(yè)務(wù)邏輯對象
業(yè)務(wù)邏輯對象由以下元素組成:
1、root組件對象引用-this.root
2晒奕、構(gòu)造器-初始化root對象闻书,初始化私有屬性
3、私有屬性
4脑慧、公有方法-對外提供業(yè)務(wù)邏輯
5魄眉、私有方法-以下劃線開頭,內(nèi)部使用
ps1:通用型組件只要求盡量滿足上述架構(gòu)設(shè)計
通用型組件一般為不包含任何業(yè)務(wù)的純技術(shù)組件闷袒,具有高復(fù)用價值坑律、高定制性、通常不能直接使用需要代碼定制等特點囊骤。
可以說是一個系統(tǒng)的各個基礎(chǔ)零件晃择,比如一個蒙板效果冀值,或者一個模態(tài)彈出框。
架構(gòu)的最終目的是保證系統(tǒng)整體結(jié)構(gòu)良好宫屠,代碼質(zhì)量良好列疗,易于維護。一般編寫通用型組件的人也是經(jīng)驗較為豐富的工程師浪蹂,代碼質(zhì)量會有保證抵栈。而且,作為零件的通用組件的使用場景和生命周期都和普通組件\頁面不同乌逐,所以竭讳,僅要求通用組件編寫盡量滿足架構(gòu)設(shè)計即可。
ps2:view子組件復(fù)用問題
拋出一個問題浙踢,設(shè)計的過程中,子組件是否需要復(fù)用灿渴?子組件是否需要復(fù)用會影響到組件設(shè)計洛波。
需復(fù)用,只暴露props骚露,可以內(nèi)部自行管理state【盡量避免除非業(yè)務(wù)需要】
不需復(fù)用蹬挤,只暴露props,內(nèi)部無state【因為不會單獨使用棘幸,不需要setState來觸發(fā)渲染】
其實焰扳, 一般按照不需復(fù)用的情況設(shè)計,除非復(fù)用很明確误续,但這時候應(yīng)該抽出去吨悍,變成獨立的組件存在就可以了,所以這個問題是不存在的蹋嵌。
適用場景分析
flux系列框架
flux系列框架的適用場景我覺得應(yīng)具有以下特點:
一個頁面中組件較多育瓜,組件之間較為獨立栽烂,但是重疊使用模型,模型的變化會影響很多組件的展現(xiàn)和行為焰手。
比如,開發(fā)一個類似qq的聊天頁面书妻,左側(cè)是聯(lián)系人列表,右側(cè)是與某人的消息對話框驻子,當(dāng)收到一個消息之后灿意,1要刷新左側(cè)聯(lián)系人列表的最近聯(lián)系人缤剧,2要右側(cè)的消息對話框中顯示這個消息,3要頁面title要提示新消息荒辕。這就是典型的一個新消息到來事件【消息模型發(fā)生了變化】觸發(fā)三個無關(guān)聯(lián)的組件都有行為和展現(xiàn)的變化犹褒。如果用事件系統(tǒng)來開發(fā)就做到了解耦的極致,未來如果還要加入第4種處理也不用修改原來的邏輯叠骑,就直接注冊一下就可以了,滿足了開閉原則宙枷。
面向?qū)ο蟮腞N組件架構(gòu)
面向?qū)ο蟮腞N組件架構(gòu)的使用場景特點我沒有總結(jié)出來,我覺得所有場景都可以用慰丛,只要你業(yè)務(wù)邏輯對象設(shè)計的好卓囚,都不是問題。
還拿上面聊天界面舉例子诅病,面向?qū)ο蟮腞N組件架構(gòu)其實也可以解耦的寫出寫上述場景哪亿,你完全可以將業(yè)務(wù)邏輯對象之間的交互設(shè)計成一個小的事件系統(tǒng),只是架構(gòu)沒有直接約束這種解耦贤笆,flux系列架構(gòu)直接在架構(gòu)中就強制編碼人員做到了解耦蝇棉,但是如果我不需要解耦的時候就相當(dāng)于增加了復(fù)雜度,得不償失了苏潜。
所以面向?qū)ο蟮腞N組件架構(gòu)要更靈活银萍,同時因為靈活對業(yè)務(wù)邏輯對象設(shè)計者的要求也較高,針對較為復(fù)雜or重要頁面建議進行詳細(xì)設(shè)計并leader檢查來保證質(zhì)量恤左。
如何做監(jiān)控
因為面向?qū)ο蟮腞N架構(gòu)中去掉了統(tǒng)一的業(yè)務(wù)邏輯調(diào)用facade入口dispatch贴唇,那我們?nèi)绾蝸碜霰O(jiān)控呢。
方案1:在需要監(jiān)控的地方人為加入監(jiān)控點飞袋。
這個方案對業(yè)務(wù)代碼和監(jiān)控代碼的耦合確實有點大戳气,是最差的解決方案了。不推薦巧鸭。
方案2:在基類BaseLogicObj的構(gòu)造器中對對象的所有方法進行代理-todo待驗證
這個方案對業(yè)務(wù)代碼透明署辉,但是還只是個想法瓶埋,未進行代碼測試和驗證敏沉。
完整demo代碼
此demo仿照redux提供的todolist demo編寫。
redux demo 地址:http://camsong.github.io/redux-in-chinese/docs/basics/ExampleTodoList.html
demo截圖:
todolist頁面:
'use strict'
let React=require('react-native');
let Immutable = require('immutable');
var BbtRN=require('../../../bbt-react-native');
var {
BaseLogicObj,
}=BbtRN;
let {
AppRegistry,
Component,
StyleSheet,
Text,
View,
Navigator,
TouchableHighlight,
TouchableOpacity,
Platform,
ListView,
TextInput,
ScrollView,
}=React;
//root組件開始-----------------
let Root =React.createClass({
//初始化模擬數(shù)據(jù)贸毕,
data:[{
name:'aaaaa',
completed:true,
},{
name:'bbbbb',
completed:false,
},{
name:'ccccc',
completed:false,
}
,{
name:'ddddd',
completed:true,
}],
componentWillMount(){
//初始化業(yè)務(wù)邏輯對象
this.addTodoObj=new AddTodoObj(this);
this.todoListObj=new TodoListObj(this);
this.filterObj=new FilterObj(this);
//下面可以繼續(xù)做一些組件初始化動作,比如請求數(shù)據(jù)等.
//當(dāng)然了這些動作最好是業(yè)務(wù)邏輯對象提供的夜赵,這樣root組件將非常干凈.
//例如這樣:this.todoListObj.queryData();
},
//狀態(tài)初始化
getInitialState(){
return {
data:Immutable.fromJS(this.data),//模擬的初始化數(shù)據(jù)
todoName:'',//新任務(wù)的text
curFilter:'all',//過濾條件 all no ok
}
},
//這里組合子view組件 并 注冊業(yè)務(wù)邏輯對象提供的方法到各個子view組件上
render(){
return (
<View style={{marginTop:40,flex:1}}>
<AddTodo todoName={this.state.todoName}
changeText={this.addTodoObj.change.bind(this.addTodoObj)}
pressAdd={this.addTodoObj.press.bind(this.addTodoObj)} />
<TodoList todos={this.state.data}
onTodoPress={this.todoListObj.pressTodo.bind(this.todoListObj)} />
<Footer curFilter={this.state.curFilter}
onFilterPress={this.filterObj.filter.bind(this.filterObj)} />
</View>
);
},
});
//業(yè)務(wù)邏輯對象開始-------------------------可以使用OO的設(shè)計方式設(shè)計成多個對象
//業(yè)務(wù)邏輯對象要符合命名規(guī)范:以O(shè)bj結(jié)尾
//BaseLogicObj是架構(gòu)提供的基類明棍,里面封裝了構(gòu)造器和一些常用取值函數(shù)
class AddTodoObj extends BaseLogicObj{
press(){
if(!this.getState().todoName)return;
let list=this.getState().data;
let todo=Immutable.fromJS({name:this.getState().todoName,completed:false,});
this.setState({data:list.push(todo),todoName:''});
}
change(e){
this.setState({todoName:e.nativeEvent.text});
}
}
class TodoListObj extends BaseLogicObj {
pressTodo(todo){
let data=this.getState().data;
let i=data.indexOf(todo);
let todo2=todo.set('completed',!todo.get('completed'));
this.setState({data:data.set(i,todo2)});
}
}
class FilterObj extends BaseLogicObj {
filter(type){
let data=this.getState().data.toJS();
if(type=='all'){
data.map((todo)=>{
todo.show=true;
});
}else if(type=='no'){
data.map((todo)=>{
if(todo.completed)todo.show=false;
else todo.show=true;
});
}else if(type=='ok'){
data.map((todo)=>{
if(todo.completed)todo.show=true;
else todo.show=false;
});
}
this.setState({curFilter:type,data:Immutable.fromJS(data)});
}
}
//view子組件開始---------------------------
//子view對象中僅僅關(guān)注:從this.props轉(zhuǎn)化成view
let Footer=React.createClass({
render(){
return (
<View style={{flexDirection:'row', justifyContent:'flex-end',marginBottom:10,}}>
<FooterBtn {...this.props} title='全部' name='all' cur={this.props.curFilter=='all'?true:false} />
<FooterBtn {...this.props} title='未完成' name='no' cur={this.props.curFilter=='no'?true:false} />
<FooterBtn {...this.props} title='已完成' name='ok' cur={this.props.curFilter=='ok'?true:false} />
</View>
);
},
});
let FooterBtn=React.createClass({
render(){
return (
<TouchableOpacity onPress={()=>this.props.onFilterPress(this.props.name)}
style={[{padding:10,marginRight:10},this.props.cur?{backgroundColor:'green'}:null]} >
<Text style={[this.props.cur?{color:'fff'}:null]}>
{this.props.title}
</Text>
</TouchableOpacity>
);
},
});
let AddTodo=React.createClass({
render(){
return (
<View style={{flexDirection:'row', alignItems:'center'}}>
<TextInput value={this.props.todoName}
onChange={this.props.changeText}
style={{width:200,height:40,borderWidth:1,borderColor:'e5e5e5',margin:10,}}></TextInput>
<TouchableOpacity onPress={this.props.pressAdd}
style={{backgroundColor:'green',padding:10}} >
<Text style={{color:'fff'}} >
添加任務(wù)
</Text>
</TouchableOpacity>
</View>
);
},
});
let Todo=React.createClass({
render(){
let todo=this.props.todo;
return (
todo.get("show")!=false?
<TouchableOpacity onPress={()=>this.props.onTodoPress(todo)}
style={{padding:10,borderBottomWidth:1,borderBottomColor:'#e5e5e5'}}>
<Text style={[todo.get('completed')==true?{textDecorationLine:'line-through',color:'#999'}:null]} >
{todo.get('completed')==true?'已完成 ':'未完成 '} {todo.get('name')}
</Text>
</TouchableOpacity>
:null
);
},
});
let TodoList=React.createClass({
render(){
return (
<ScrollView style={{flex:1}}>
{this.props.todos.reverse().map((todo, index) => <Todo {...this.props} todo={todo} key={index} />)}
</ScrollView>
);
},
});
module.exports=Root;
業(yè)務(wù)邏輯對象基類BaseLogicObj:
'use strict'
class BaseLogicObj{
constructor(root){
if(!root){
console.error('實例化BaseLogicObj必須傳入root組件對象.');
}
this.root=root;
}
getState(){
return this.root.state;
}
setState(s){
this.root.setState(s);
}
getRefs(){
return this.root.refs;
}
getProps(){
return this.root.props;
}
}
module.exports=BaseLogicObj;