我認為MVC模式雖然已經(jīng)誕生了許多年,也有無數(shù)前端框架遵循了MVC模式,但我們在前端開發(fā)時凿跳,很多時候還是忽略了這個模式蘊含的思想坑傅。該思想的核心就是職責(zé)分離僵驰,這種分離又隱含了“信息專家模式”的意義,直白地說,就是“專業(yè)的事情應(yīng)該交給專業(yè)的人去做”矢渊。
MVC(Model-View-Controller)的三個角色其實是各司其職:
- model持有UI要展現(xiàn)的數(shù)據(jù)
- View即UI的展現(xiàn)
- Controller用于控制
以React來說继准,它就應(yīng)該只專注于View的呈現(xiàn),并將這些展現(xiàn)元素封裝為Component矮男。這些Component要展現(xiàn)的props可以視為Model所持有的數(shù)據(jù)移必。
那么,什么情況下會導(dǎo)致View產(chǎn)生變化呢毡鉴?從表象上看崔泵,似乎引起變化的原因是由于客戶端的某種請求或交互操作產(chǎn)生的事件。實則從業(yè)務(wù)上說猪瞬,其實就是要改變Model的值憎瘸,而UI的交互操作不過是對這種變化的界面展現(xiàn)罷了。換言之陈瘦,View的變化其實應(yīng)該通過Model的變化來傳遞幌甘。
當(dāng)我們需要改變View時,一種做法是直接在View上做文章痊项,通過編寫針對UI元素的控制邏輯去改變View锅风。另一種做法就是遵循MVC模式,應(yīng)該通過Controller去改變Model的結(jié)構(gòu)鞍泉,然后通知View去改變自己(或者理解為View偵聽到Model的變化皱埠,從而改變自己)。
React結(jié)合Redux框架做的正是這樣的事情咖驮。在設(shè)計React Component
時边器,我們需要通過UI的Layout來規(guī)劃我們的Component,包括Component的分解與組合托修。呈現(xiàn)Component的過程就可以抽象為一個函數(shù)忘巧,這個函數(shù)接收一個輸入對象model,返回一個包裹了HTML元素與Model的
DOM`結(jié)構(gòu)诀黍。如以下偽代碼:
const render = (model) => DOM
如果業(yè)務(wù)邏輯要求操作View的DOM袋坑,其實就是對DOM包裹的Model進行操作,例如添加或修改某個<li>
眯勾,其本質(zhì)是要添加或修改<li>
元素中的值枣宫,這個值來自于Model。在Redux中吃环,其實就是發(fā)起一個action
也颤。
執(zhí)行action
的目的雖然是修改Model
,不過在Redux中郁轻,我們盡量希望遵循FP的思想設(shè)計出所謂的“純函數(shù)”翅娶,于是Redux就引入了reducer
函數(shù)文留,這個函數(shù)要做的事情其實就是對Model
進行transform
(可以考慮引入immutable.js來存儲和操作Modle)。一旦Model
對象發(fā)生了變化(并不是真正發(fā)生了變化竭沫,而是產(chǎn)生了一個新的Model)燥翅,Redux就會通知React Component根據(jù)新獲得的Model
去重新Render。
顯然蜕提,React扮演的是View的角色森书,Redux則是Controller,至于Model就是Redux Store中存儲的State谎势。我們要從MVC模式的角度去思考React+Redux開發(fā)凛膏,把代碼需要做的每件事情想清楚,明確是誰的職責(zé)脏榆,如此才不至于在實現(xiàn)時走歪路猖毫,不討好地去編寫大量View的控制邏輯,尤其是那些牽涉到parent-child組件的遞歸關(guān)系時须喂,可能會讓前端代碼燉成一鍋粥吁断。
舉個實例。
我們要在前端編寫一個過濾器镊折,UI展現(xiàn)與控制邏輯類似Logiform胯府,如下圖所示:
這個過濾器可以理解為以Condition
為根的一個遞歸嵌套樹形結(jié)構(gòu)介衔,枝為Group
恨胚,而葉為Expression
。Group
還可以嵌套Group
或者Expression
炎咖≡吲荩可以添加、刪除Group
或Expression
乘盼,也可以調(diào)整它們在樹中所處的位置升熊。
針對這樣的需求,如果我們企圖在React Component中直接去操控和管理這些邏輯绸栅,就需要考慮Component的父子關(guān)系级野,還需要考慮添加或刪除Dom節(jié)點對整棵樹的影響。
如果我們站在前述MVC模式的角度來考慮過濾器樹的呈現(xiàn)與界面控制粹胯,其實不過就是針對Condition
對象模型的操作罷了蓖柔。這個時候,我們可以不用去操心DOM節(jié)點之間的關(guān)系风纠,而是直接用React Component去render模型對象况鸣。對象的粗略結(jié)構(gòu)如下所示:
{
"id": 1,
"operator": "and",
"conditions": [
{
"id": 2,
"type": "expression",
"operator": "=",
"fieldId": "11000",
"value": 3
},
{
"id": 3,
"type": "group",
"operator": "or",
"conditions": [
{
"id": 4,
"type": "expression",
"operator": "<=",
"fieldId": "11001",
"value": 20
}
]
}]
}
由于render是一種只讀的操作,要在React Component中去render這樣的結(jié)構(gòu)是非常容易的竹观。如上镐捧,當(dāng)我們要刪除id
為2的Expression
時潜索,其實就是去編寫一個reducer,將其轉(zhuǎn)換為如下的對象:
{
"id": 1,
"operator": "and",
"conditions": [
{
"id": 3,
"type": "group",
"operator": "or",
"conditions": [
{
"id": 4,
"type": "expression",
"operator": "<=",
"fieldId": "11001",
"value": 20
}
]
}]
}
render
對UI的呈現(xiàn)與控制邏輯完全相同懂酱,并不需要再去控制復(fù)雜的DOM竹习。
概況下來,React+Redux的主體流程為:
- 通過action獲得model列牺,并將其作為state存儲到Store中由驹;
- 傳遞給React Component,按照某種設(shè)計呈現(xiàn)model數(shù)據(jù)昔园;
- 調(diào)用action發(fā)起update請求蔓榄,從而調(diào)用reducer生成新的state存儲到Store中;
- redux通知React Component重新Render默刚。
這是MVC三種角色各司其職相互協(xié)作的結(jié)果甥郑。