1、傳統MVC框架的缺陷
模型(model)-檢視(view)-控制器(controller)的縮寫
V
即View檢視:使用者看到並與之互動的介面。
M
即Model模型是管理資料:很多業務邏輯都在模型中完成。在MVC的三個部件中,模型擁有最多的處理任務。
C
即Controller控制器:接受使用者的輸入並呼叫模型和檢視去完成使用者的需求,控制器本身不輸出任何東西和做任何處理。它只是接收請求並決定呼叫哪個模型構件去處理請求,然後再確定用哪個檢視來顯示返回的資料。
缺點
MVC框架的資料流很理想,請求先到Controller, 由Controller呼叫Model中的資料交給View進行渲染.
但是在實際的專案中,又是允許Model和View直接通訊的。
2、Flux
在2013年,Facebook讓React
亮相的同時推出了Flux框架,React
的初衷實際上是用來替代jQuery
的,Flux
實際上就可以用來替代Backbone.js
,Ember.js
等一系列MVC
架構的前端JS框架。
其實Flux
在React
裡的應用就類似於Vue
中的Vuex
的作用,但是在Vue
中,Vue
是完整的mvvm
框架,而Vuex
只是一個全域性的外掛。
React
只是一個MVC中的V(檢視層),只管頁面中的渲染,一旦有資料管理的時候,React
本身的能力就不足以支撐複雜元件結構的專案,在傳統的MVC
中,就需要用到Model和Controller。Facebook對於當時世面上的MVC
框架並不滿意,於是就有了Flux
, 但Flux
並不是一個MVC
框架,他是一種新的思想
- View: 檢視層
- ActionCreator(動作創造者):檢視層發出的訊息(比如mouseClick)
- Dispatcher(派發器):用來接收Actions、執行回撥函式
- Store(資料層):用來存放應用的狀態,一旦發生變動,就提醒Views要更新頁面
Flux的流程:
- 元件獲取到store中儲存的資料掛載在自己的狀態上
- 使用者產生了操作,呼叫actions的方法
- actions接收到了使用者的操作,進行一系列的邏輯程式碼、非同步操作
- 然後actions會建立出對應的action,action帶有標識性的屬性
- actions呼叫dispatcher的dispatch方法將action傳遞給dispatcher
- dispatcher接收到action並根據標識資訊判斷之後,呼叫store的更改資料的方法
- store的方法被呼叫後,更改狀態,並觸發自己的某一個事件
- store更改狀態後事件被觸發,該事件的處理程式會通知view去獲取最新的資料
3、Redux
注意:flux、redux都不是必須和react搭配使用的,因為flux和redux是完整的架構,在學習react的時候,只是將react的元件作為redux中的檢視層去使用了。
React 只是 DOM 的一個抽象層,並不是 Web 應用的完整解決方案。
有兩個方面,它沒涉及:
- 程式碼結構
- 元件之間的通訊
2013年 Facebook 提出了 Flux 架構的思想,引發了很多的實現。2015年,Redux 出現,將 Flux 與函數語言程式設計結合一起,很短時間內就成為了最熱門的前端架構。
使用場景設計思路
不需要redux情況
- 使用者的使用方式非常簡單
- 使用者之間沒有協作
- 不需要與伺服器大量互動,也沒有使用 WebSocket
- 檢視層(View)只從單一來源獲取資料
需要redux情況
- 使用者的使用方式複雜
- 不同身份的使用者有不同的使用方式(比如普通使用者和管理員)
- 多個使用者之間可以協作
- 與伺服器大量互動,或者使用了WebSocket
- View要從多個來源獲取資料
從元件層面考慮,什麼樣子的需要Redux:
- 某個元件的狀態,需要共享
- 某個狀態需要在任何地方都可以拿到
- 一個元件需要改變全域性狀態
- 一個元件需要改變另一個元件的狀態
Redux的設計思想:
- Web 應用是一個狀態機,檢視與狀態是一一對應的。
- 所有的狀態,儲存在一個物件裡面(唯一資料來源)。
Redux的使用的三大原則:
- store:單一資料來源
整個應用的 state 被儲存在一棵物件結構中,並且這個物件結構只存在於唯一一個 store 中
不能直接去修改此資料來源中的資料(資料深拷貝) - State:是隻讀的
state redux中的state
唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通物件。
action只能是一個物件 - reducer:使用純函式(reducer)來執行修改
為了描述 action 如何改變 state tree ,你需要編寫 reducer,reducer只是一些純函式,它接收先前的 state 和 action,並返回新的 state
Redux框架的使用
Reducer必須是一個純函式
純函式遵守以下一些約束。
- 不得改寫引數
- 不能呼叫系統 I/O 的API
- 不能呼叫Date.now()或者Math.random()等不純的方法,因為每次會得到不一樣的結果
步驟1:建立統一資料來源
- 引入redux 生成createStore
- 建立預設資料 defaultState
- 建立 reduces(state,action){}
- createStore(reduces)建立資料來源
//引入建立倉庫方法
import {createStore} from 'redux'
//預設資料來源資料,不能直接修改
const defaultStore={
count:1
}
//reducer出函式
function reducers(state=defaultStore,action){
if(action.type=='incr'){
return{
count:state.count+1
}
}
return state
}
//建立唯一倉庫
const store = createStore(
reducers,
)
export default store
步驟2:元件中獲取資料並設定資料
- 獲取資料 store.getState()
- 訂閱資料 store.subscribe(()=>{this.setState(state=>store.getState())})
- 派發任務 store.dispatch(actionCreator)
import React,{Component} from 'react'
import store from './store'
export default class App extends Component{
constructor(props){
super(props)
this.state = store.getState()
store.subscribe(()=>{
this.setState(state=>store.getState())
})
}
render(){
return(
<div>
<button onClick={this.incr}>++</button>
</div>
)
}
incr=()=>{
const actionCreator=>{
type:'incr',
payLoad:1
}
store.dispatch(actionCreator)
}
}
劃分reducer
原因:一個應用只有一個state,單個reducer對資料操作很臃腫,so需要按照不同功能去拆分
注意:
- 分離reducer的時候,每一個reducer維護的狀態都應該不同
- 通過store.getState獲取到的資料也是會按照reducers去劃分的
- 劃分多個reducer的時候,預設狀態只能建立在reducer中,因為劃分reducer的目的,就是為了讓每一個reducer都去獨立管理一部分狀態
react-redux(redux擴充套件庫)
React-Redux是Redux的官方針對React開發的擴充套件庫。
你可以理解為react-redux就是redux給我們提供一些高階元件
安裝
npm i -S redux react-redux
兩個核心的api
- Provider: 提供store
根據單一store原則 ,一般只會出現在整個應用程式的最頂層。 - connect: 用於連線容器元件和展示元件
語法格式為:
connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)
一般來說只會用到前面兩個,它的作用是:- 把
store.getState()
轉化為展示元件的props
- 把
actionCreators
轉化為展示元件props
上的方法
- 把
使用
步驟1:定義Provider
- 主程式index.js中定義Provider
- 讓全域性的元件共享store中的資料
import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' import store from './store' import App from './App' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
步驟2:子程式中使用connect
- 把
store.getState()
轉化為展示元件的props
- 把
actionCreators
轉化為展示元件props
上的方法
- 傳統使用方式
- 裝飾器使用方式(推薦使用)
傳統使用方式
import React ,{Component} from 'react'
import {connect} from 'react-redux'
import * as actions frm './countAction'
class App exteds Component{
render(){
return (
<div>
{this.props.count}
<button onClick={this.incr}>++</button>
</div>
)
}
incr=()=>{
this.props.incr()
}
}
const mapStateProps=state=>{
return {count:state.count}
}
const mapPropsToDIspatch=dispatch=>{
return{
incr(){
dispatch(actions.incr())
}
}
}
export default connect(mapStateProps,mapPropsToDIspatch)(App)
裝飾器使用方式(推薦使用)
import React ,{Component} from 'react'
import {connect} from 'react-redux'
import * as actions frm './countAction'
const mapStateProps=state=>{
return {count:state.count}
}
const mapPropsToDIspatch=dispatch=>{
return{
incr(){
dispatch(actions.incr())
}
}
}
@connect(mapStateProps,mapPropsToDIspatch)
class App exteds Component{
render(){
return (
<div>
{this.props.count}
<button onClick={this.incr}>++</button>
</div>
)
}
incr=()=>{
this.props.incr()
}
}
Redux非同步操作(redux-thunk)
通常情況下,action只是一個物件,不能包含非同步操作,這導致了很多建立action的邏輯只能寫在元件中,程式碼量較多也不便於複用,同時對該部分程式碼測試的時候也比較困難.
常見的非同步庫:
- Redux-thunk
- Redux-saga
- Redux-effects
- Redux-side-effects
- Redux-loop
- Redux-observable
基於Promise的非同步庫:
- Redux-promise
- Redux-promises
- Redux-simple-promise
- Redux-promise-middleware
安裝
npm i -S redux-thunk
使用
在createStore例項store中使用
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import reducer from './countReducer'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
export default store
countReducer.js
const incrAction = num=>({
type:'incr',
payload:num
})
export const incr=>90=>dispatch=>{
setTimeout(()=>{
let num=10
dispatch(incrAction(num))
},1000)
}