傳統(tǒng)MVC框架的缺陷
什么是MVC裕偿?
MVC
的全名是Model View Controller
引润,是模型(model)-視圖(view)-控制器(controller)的縮寫,是一種軟件設計典范栈幸。
V
即View視圖是指用戶看到并與之交互的界面愤估。
M
即Model模型是管理數(shù)據(jù) ,很多業(yè)務邏輯都在模型中完成侦镇。在MVC的三個部件中灵疮,模型擁有最多的處理任務。
C
即Controller控制器是指控制器接受用戶的輸入并調(diào)用模型和視圖去完成用戶的需求壳繁,控制器本身不輸出任何東西和做任何處理震捣。它只是接收請求并決定調(diào)用哪個模型構件去處理請求,然后再確定用哪個視圖來顯示返回的數(shù)據(jù)闹炉。
MVC只是看起來很美
MVC框架的數(shù)據(jù)流很理想蒿赢,請求先到Controller, 由Controller調(diào)用Model中的數(shù)據(jù)交給View進行渲染,但是在實際的項目中渣触,又是允許Model和View直接通信的羡棵。然后就出現(xiàn)了這樣的結果:
Flux
在2013年,F(xiàn)acebook讓React
亮相的同時推出了Flux框架嗅钻,React
的初衷實際上是用來替代jQuery
的皂冰,Flux
實際上就可以用來替代Backbone.js
,Ember.js
等一系列MVC
架構的前端JS框架养篓。
其實Flux
在React
里的應用就類似于Vue
中的Vuex
的作用秃流,但是在Vue
中,Vue
是完整的mvvm
框架柳弄,而Vuex
只是一個全局的插件舶胀。
React
只是一個MVC中的V(視圖層),只管頁面中的渲染碧注,一旦有數(shù)據(jù)管理的時候嚣伐,React
本身的能力就不足以支撐復雜組件結構的項目,在傳統(tǒng)的MVC
中萍丐,就需要用到Model和Controller轩端。Facebook對于當時世面上的MVC
框架并不滿意,于是就有了Flux
, 但Flux
并不是一個MVC
框架逝变,他是一種新的思想船万。
View: 視圖層
ActionCreator(動作創(chuàng)造者):視圖層發(fā)出的消息(比如mouseClick)
Dispatcher(派發(fā)器):用來接收Actions、執(zhí)行回調(diào)函數(shù)
Store(數(shù)據(jù)層):用來存放應用的狀態(tài)骨田,一旦發(fā)生變動耿导,就提醒Views要更新頁面
Flux的流程:
組件獲取到store中保存的數(shù)據(jù)掛載在自己的狀態(tài)上
用戶產(chǎn)生了操作,調(diào)用actions的方法
actions接收到了用戶的操作态贤,進行一系列的邏輯代碼舱呻、異步操作
然后actions會創(chuàng)建出對應的action,action帶有標識性的屬性
actions調(diào)用dispatcher的dispatch方法將action傳遞給dispatcher
dispatcher接收到action并根據(jù)標識信息判斷之后,調(diào)用store的更改數(shù)據(jù)的方法
store的方法被調(diào)用后箱吕,更改狀態(tài)芥驳,并觸發(fā)自己的某一個事件
store更改狀態(tài)后事件被觸發(fā),該事件的處理程序會通知view去獲取最新的數(shù)據(jù)
Redux
React 只是 DOM 的一個抽象層茬高,并不是 Web 應用的完整解決方案兆旬。有兩個方面,它沒涉及怎栽。
代碼結構
組件之間的通信
2013年 Facebook 提出了 Flux 架構的思想丽猬,引發(fā)了很多的實現(xiàn)德谅。2015年凯正,Redux 出現(xiàn),將 Flux 與函數(shù)式編程結合一起凄诞,很短時間內(nèi)就成為了最熱門的前端架構强饮。
如果你不知道是否需要 Redux由桌,那就是不需要它
只有遇到 React 實在解決不了的問題,你才需要 Redux
簡單說邮丰,如果你的UI層非常簡單行您,沒有很多互動,Redux 就是不必要的剪廉,用了反而增加復雜性邑雅。
用戶的使用方式非常簡單
用戶之間沒有協(xié)作
不需要與服務器大量交互,也沒有使用 WebSocket
視圖層(View)只從單一來源獲取數(shù)據(jù)
需要使用Redux的項目:
用戶的使用方式復雜
不同身份的用戶有不同的使用方式(比如普通用戶和管理員)
多個用戶之間可以協(xié)作
與服務器大量交互妈经,或者使用了WebSocket
View要從多個來源獲取數(shù)據(jù)
從組件層面考慮,什么樣子的需要Redux:
某個組件的狀態(tài)捧书,需要共享
某個狀態(tài)需要在任何地方都可以拿到
一個組件需要改變?nèi)譅顟B(tài)
一個組件需要改變另一個組件的狀態(tài)
Redux的設計思想:
Web 應用是一個狀態(tài)機吹泡,視圖與狀態(tài)是一一對應的。
所有的狀態(tài)经瓷,保存在一個對象里面(唯一數(shù)據(jù)源)爆哑。
注意:flux、redux都不是必須和react搭配使用的舆吮,因為flux和redux是完整的架構揭朝,在學習react的時候,只是將react的組件作為redux中的視圖層去使用了色冀。
Redux的使用的三大原則:
Single Source of Truth(唯一的數(shù)據(jù)源)
State is read-only(狀態(tài)是只讀的)
Changes are made with pure function(數(shù)據(jù)的改變必須通過純函數(shù)完成)
自己實現(xiàn)Redux
不使用react潭袱,直接使用原生的html/js來寫一個簡易的的redux
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Redux principle 04</title>
</head>
<body>
<h1>redux principle</h1>
<div class="counter">
<span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
<span class="count" id="count"></span>
<span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
</div>
<script>
// 定義一個方法,用于集中管理state和dispatch, changeState改名了锋恬,專業(yè)的叫法是reducer
const createStore = (reducer) => {
// 定義一個初始的state
let state = null
// getState用于獲取狀態(tài)
const getState = () => state
// 定義一個監(jiān)聽器屯换,用于管理一些方法
const listeners = []
const subscribe = (listener) => listeners.push(listener)
// 定義一個dispatch方法,讓每次有action傳入的時候返回reducer執(zhí)行之后的結果
const dispatch = (action) => {
// 調(diào)用reducer來處理數(shù)據(jù)
state = reducer(state, action)
// 讓監(jiān)聽器里的所有方法運行
listeners.forEach(listener => listener())
}
// 初始化state
dispatch({})
return {
getState,
dispatch,
subscribe
}
}
// 定義一個計數(shù)器的狀態(tài)
const countState = {
count: 10
}
// 定一個方法叫changeState,用于處理state的數(shù)據(jù)彤悔,每次都返回一個新的狀態(tài)
const changeState = (state, action) => {
// 如果state是null, 就返回countState
if (!state) return countState
switch(action.type) {
// 處理減
case 'COUNT_DECREMENT':
return {
...state,
count: state.count - action.number
}
// 處理加
case 'COUNT_INCREMENT':
return {
...state,
count: state.count + action.number
}
default:
return state
}
}
// 創(chuàng)建一個store
const store = createStore(changeState)
// 定義一個方法用于渲染計數(shù)器的dom
const renderCount = () => {
const countDom = document.querySelector('#count')
countDom.innerHTML = store.getState().count
}
// 初次渲染數(shù)據(jù)
renderCount()
// 監(jiān)聽嘉抓,只要有dispatch,renderCount就會自動運行
store.subscribe(renderCount)
</script>
</body>
</html>
使用Redux框架
Redux的流程:
1.store通過reducer創(chuàng)建了初始狀態(tài)
2.view通過store.getState()獲取到了store中保存的state掛載在了自己的狀態(tài)上
3.用戶產(chǎn)生了操作晕窑,調(diào)用了actions 的方法
4.actions的方法被調(diào)用抑片,創(chuàng)建了帶有標示性信息的action
5.actions將action通過調(diào)用store.dispatch方法發(fā)送到了reducer中
6.reducer接收到action并根據(jù)標識信息判斷之后返回了新的state
7.store的state被reducer更改為新state的時候,store.subscribe方法里的回調(diào)函數(shù)會執(zhí)行杨赤,此時就可以通知view去重新獲取state
Reducer必須是一個純函數(shù):
Reducer 函數(shù)最重要的特征是敞斋,它是一個純函數(shù)。也就是說望拖,只要是同樣的輸入渺尘,必定得到同樣的輸出。Reducer不是只有Redux里才有说敏,數(shù)組方法reduce
, 它的第一個參數(shù)就是一個reducer
純函數(shù)是函數(shù)式編程的概念鸥跟,必須遵守以下一些約束。
不得改寫參數(shù)
不能調(diào)用系統(tǒng) I/O 的API
不能調(diào)用Date.now()或者Math.random()等不純的方法盔沫,因為每次會得到不一樣的結果
由于 Reducer 是純函數(shù)医咨,就可以保證同樣的State,必定得到同樣的 View架诞。但也正因為這一點拟淮,Reducer 函數(shù)里面不能改變 State,必須返回一個全新的對象谴忧,請參考下面的寫法很泊。
// State 是一個對象
function reducer(state = defaultState, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一個數(shù)組
function reducer(state = defaultState, action) {
return [...state, newItem];
}
關于action/reducer/store的更多概念,請查看官網(wǎng)
注意:
- 分離reducer的時候沾谓,每一個reducer維護的狀態(tài)都應該不同
- 通過store.getState獲取到的數(shù)據(jù)也是會按照reducers去劃分的
- 劃分多個reducer的時候委造,默認狀態(tài)只能創(chuàng)建在reducer中,因為劃分reducer的目的均驶,就是為了讓每一個reducer都去獨立管理一部分狀態(tài)
因為一個應用中只能有一個大的state昏兆,這樣的話reducer中的代碼將會特別特別的多,那么就可以使用combineReducers方法將已經(jīng)分開的reducer合并到一起
劃分reducer:
我們可以通過在createStore中傳入第二個參數(shù)來設置默認的state妇穴,但是這種形式只適合于只有一個reducer的時候爬虱。
最好把 State 對象設成只讀。要得到新的 State腾它,唯一辦法就是生成一個新對象跑筝。這樣的好處是,任何時候瞒滴,與某個 View 對應的 State 總是一個不變(immutable)的對象继蜡。
Redux異步**
通常情況下,action只是一個對象,不能包含異步操作稀并,這導致了很多創(chuàng)建action的邏輯只能寫在組件中仅颇,代碼量較多也不便于復用,同時對該部分代碼測試的時候也比較困難碘举,組件的業(yè)務邏輯也不清晰忘瓦,使用中間件了之后,可以通過actionCreator異步編寫action引颈,這樣代碼就會拆分到actionCreator中耕皮,可維護性大大提高,可以方便于測試蝙场、復用凌停,同時actionCreator還集成了異步操作中不同的action派發(fā)機制,減少編碼過程中的代碼量
常見的異步庫:
- Redux-thunk(就用這個)
- Redux-saga
- Redux-effects
- Redux-side-effects
- Redux-loop
- Redux-observable
- …
基于Promise的異步庫:
- Redux-promise
- Redux-promises
- Redux-simple-promise
- Redux-promise-middleware
- …
容器組件(Smart/Container Components)和展示組件(Dumb/Presentational Components)
展示組件 | 容器組件 | |
---|---|---|
作用 | 描述如何展現(xiàn)(骨架售滤、樣式) | 描述如何運行(數(shù)據(jù)獲取罚拟、狀態(tài)更新) |
直接使用 Redux | 否 | 是 |
數(shù)據(jù)來源 | props | 監(jiān)聽 Redux state |
數(shù)據(jù)修改 | 從 props 調(diào)用回調(diào)函數(shù) | 向 Redux 派發(fā) actions |
調(diào)用方式 | 手動 | 通常由 React Redux 生成 |
使用react-redux
可以先結合context
來手動連接react和redux。
react-redux提供兩個核心的api:
- Provider: 提供store
- connect: 用于連接容器組件和展示組件
-
Provider
根據(jù)單一store原則 完箩,一般只會出現(xiàn)在整個應用程序的最頂層赐俗。
-
connect
語法格式為
connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)
一般來說只會用到前面兩個,它的作用是:
- 把
store.getState()
的狀態(tài)轉化為展示組件的props
- 把
actionCreators
轉化為展示組件props
上的方法
- 把
特別強調(diào):
官網(wǎng)上的第二個參數(shù)為mapDispatchToProps, 實際上就是actionCreators
只要上層中有Provider
組件并且提供了store
, 那么弊知,子孫級別的任何組件阻逮,要想使用store
里的狀態(tài),都可以通過connect
方法進行連接秩彤。如果只是想連接actionCreators
叔扼,可以第一個參數(shù)傳遞為null
Mobx
作為了解的內(nèi)容,在項目中使用redux的情況更多漫雷。
Mobx是一個功能強大瓜富,上手非常容易的狀態(tài)管理工具。redux的作者也曾經(jīng)向大家推薦過它珊拼,在不少情況下可以使用Mobx來替代掉redux。
這張圖來自于官網(wǎng)流炕,把這張圖理解清楚了澎现。基本上對于mobx的理解就算入門了每辟。
官網(wǎng)有明確的核心概念使用方法剑辫,并配有egghead的視頻教程。這里就不一一贅述了渠欺。
要特別注意當使用 mobx-react
時可以定義一個新的生命周期鉤子函數(shù) componentWillReact
妹蔽。當組件因為它觀察的數(shù)據(jù)發(fā)生了改變,它會安排重新渲染,這個時候 componentWillReact
會被觸發(fā)胳岂。這使得它很容易追溯渲染并找到導致渲染的操作(action)编整。
componentWillReact
不接收參數(shù)componentWillReact
初始化渲染前不會觸發(fā) (使用componentWillMount
替代)componentWillReact
對于 mobx-react@4+, 當接收新的 props 時并在setState
調(diào)用后會觸發(fā)此鉤子要觸發(fā)
componentWillReact
必須在render里面用到被觀察的變量使用Mobx之后不會觸發(fā)
componentWillReceiveProps