react之redux狀態管理

久宇詩發表於2021-11-19

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.jsEmber.js等一系列MVC架構的前端JS框架。

其實FluxReact裡的應用就類似於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的流程:

  1. 元件獲取到store中儲存的資料掛載在自己的狀態上
  2. 使用者產生了操作,呼叫actions的方法
  3. actions接收到了使用者的操作,進行一系列的邏輯程式碼、非同步操作
  4. 然後actions會建立出對應的action,action帶有標識性的屬性
  5. actions呼叫dispatcher的dispatch方法將action傳遞給dispatcher
  6. dispatcher接收到action並根據標識資訊判斷之後,呼叫store的更改資料的方法
  7. store的方法被呼叫後,更改狀態,並觸發自己的某一個事件
  8. 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的設計思想:

  1. Web 應用是一個狀態機,檢視與狀態是一一對應的。
  2. 所有的狀態,儲存在一個物件裡面(唯一資料來源)。

Redux的使用的三大原則:

  1. store:單一資料來源
    整個應用的 state 被儲存在一棵物件結構中,並且這個物件結構只存在於唯一一個 store 中
    不能直接去修改此資料來源中的資料(資料深拷貝)
  2. State:是隻讀的
    state redux中的state
    唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通物件。
    action只能是一個物件
  3. reducer:使用純函式(reducer)來執行修改
    為了描述 action 如何改變 state tree ,你需要編寫 reducer,reducer只是一些純函式,它接收先前的 state 和 action,並返回新的 state

Redux框架的使用


Reducer必須是一個純函式
純函式遵守以下一些約束。

  • 不得改寫引數
  • 不能呼叫系統 I/O 的API
  • 不能呼叫Date.now()或者Math.random()等不純的方法,因為每次會得到不一樣的結果

步驟1:建立統一資料來源

  1. 引入redux 生成createStore
  2. 建立預設資料 defaultState
  3. 建立 reduces(state,action){}
  4. 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:元件中獲取資料並設定資料

  1. 獲取資料 store.getState()
  2. 訂閱資料 store.subscribe(()=>{this.setState(state=>store.getState())})
  3. 派發任務 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需要按照不同功能去拆分

注意:

  1. 分離reducer的時候,每一個reducer維護的狀態都應該不同
  2. 通過store.getState獲取到的資料也是會按照reducers去劃分的
  3. 劃分多個reducer的時候,預設狀態只能建立在reducer中,因為劃分reducer的目的,就是為了讓每一個reducer都去獨立管理一部分狀態

react-redux(redux擴充套件庫)

React-Redux是Redux的官方針對React開發的擴充套件庫。
你可以理解為react-redux就是redux給我們提供一些高階元件
安裝

npm i -S redux react-redux

兩個核心的api

  1. Provider: 提供store
    根據單一store原則 ,一般只會出現在整個應用程式的最頂層。
  2. 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上的方法
  1. 傳統使用方式
  2. 裝飾器使用方式(推薦使用)

傳統使用方式

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)
} 

相關文章