Dva知識點(diǎn)整理&&項(xiàng)目使用總結(jié)

Dva是什么

dva 是基于現(xiàn)有應(yīng)用架構(gòu) (redux + react-router + redux-saga 等)的一層輕量封裝,沒有引入任何新概念,全部代碼不到 100 行。( Inspired by elm and choo. )

  • 框架,而非類庫
  • 基于redux, react-router, redux-soga的輕量級封裝
  • 借鑒elm的概念, Reducer, Effect和Subscription
  • ...

可以說,dva是基于react+redux最佳實(shí)踐上實(shí)現(xiàn)的封裝方案,簡化了redux和redux-saga使用上的諸多繁瑣操作。

文件結(jié)構(gòu)

官方推薦的:

├── /mock/           # 數(shù)據(jù)mock的接口文件
├── /src/            # 項(xiàng)目源碼目錄
│ ├── /components/   # 項(xiàng)目組件
│ ├── /routes/       # 路由組件(頁面維度)
│ ├── /models/       # 數(shù)據(jù)模型
│ ├── /services/     # 數(shù)據(jù)接口
│ ├── /utils/        # 工具函數(shù)
│ ├── route.js       # 路由配置
│ ├── index.js       # 入口文件
│ ├── index.less    
│ └── index.html    
├── package.json     # 定義依賴的pkg文件
└── proxy.config.js  # 數(shù)據(jù)mock配置文件

初體驗(yàn)

一個小Demo

Demo示例

5個API

  • app = dva(Opts)
  • app.use(Hooks)
  • app.models(ModelObject)
  • app.router(Function)
  • app.start([HTMLElement])

8個概念

  • State
  • Action
  • Model
  • Reducer
  • Effect
  • Subscription
  • Router
  • RouteComponent

數(shù)據(jù)流向

image

數(shù)據(jù)的改變發(fā)生通常是通過:

  • 用戶交互行為(用戶點(diǎn)擊按鈕等)
  • 瀏覽器行為(如路由跳轉(zhuǎn)等)觸發(fā)的

當(dāng)此類行為會改變數(shù)據(jù)的時候可以通過 dispatch 發(fā)起一個 action,如果是同步行為會直接通過 Reducers 改變 State ,如果是異步行為(副作用)會先觸發(fā) Effects 然后流向 Reducers 最終改變 State 。

所以在 dva 中,數(shù)據(jù)流向非常清晰簡明,并且思路基本跟開源社區(qū)保持一致。如上圖。

相關(guān)概念與理解

定義model


    namespace: 'monthCard',
    state: {
        list: [],
        total: 0,
        editData: {},
        loading: false
    },
    subscriptions: {
        setup({ dispatch, history}){

        }
    },
    effects: {},
    reducers: {}

State是整個應(yīng)用的數(shù)據(jù)層。應(yīng)用的state被存儲在一個object tree中。應(yīng)用的初始state在model中定義,也就是說,由model state組成全局state。操作的時候每次都要當(dāng)作不可變數(shù)據(jù)(immutable data)來對待,保證每次都是全新對象,沒有引用關(guān)系,這樣才能保證 State 的獨(dú)立性,便于測試和追蹤變化。

namespace是model state在全局state中所用到的key。

這里的Model非MVC中的M,是用于把數(shù)據(jù)相關(guān)的邏輯聚合到一起。

完成component


const monthCardPrice = ({
    monthCard,
    monthCard: {
        list
    },
    diapacth
 }) => {
    const queryList = () => {
        dispatch({
            type: `monthCard/query`,
            payload: {}//需要傳遞的數(shù)據(jù)
        })
    }
    return (
        <div>
            <button onclick={queryList}>查詢</button>
        </div>
    )
}

const mapStateToProps = ({
    monthCard,
    global
 }) => {
    return {
        monthCard,
        global
    }
}

export default connect(mapStateToProps)(monthCardPrice)

RouteComponent 表示 Router 里匹配路徑的 Component,通常會綁定 model 的數(shù)據(jù)

Presentational Component是獨(dú)立的純粹的,例如ant.design UI組件的react實(shí)現(xiàn),每個組件跟業(yè)務(wù)數(shù)據(jù)并沒有耦合關(guān)系,只是完成自己獨(dú)立的任務(wù),需要的數(shù)據(jù)通過 props 傳遞進(jìn)來,需要操作的行為通過接口暴露出去。 而 Container Component 更像是狀態(tài)管理器,它表現(xiàn)為一個容器,訂閱子組件需要的數(shù)據(jù),組織子組件的交互邏輯和展示。

所以在 dva 中,通常需要 connect Model的組件都是 Route Components,組織在/routes/目錄下,而/components/目錄下則是純組件(Presentational Components)。

dva架構(gòu)里component中基本不需要用到state。

  • 綁定數(shù)據(jù)
    這里利用es6結(jié)構(gòu)賦值通過props將參數(shù)傳入組件。monthCard對應(yīng)model上的state,通過connect來綁定綁定model state。這里connect來自react-redux。意味著Component里可以拿到Model中定義的數(shù)據(jù),Model中也能接收到Component里dispatch的action。實(shí)現(xiàn)了Model和Component的連接。

  • Action
    Action表示操作事件,可以是同步,也可以是異步。Action 是一個普通 javascript 對象,它是改變 State 的唯一途徑。無論是從 UI 事件、網(wǎng)絡(luò)回調(diào),還是 WebSocket 等數(shù)據(jù)源所獲得的數(shù)據(jù),最終都會通過 dispatch 函數(shù)調(diào)用一個 action,從而改變對應(yīng)的數(shù)據(jù)。

dispatch 函數(shù),通過 type 屬性指定對應(yīng)的 actions 類型,而這個類型名在 reducers(effects)會一一對應(yīng),從而知道該去調(diào)用哪一個 reducers(effects),除了 type 以外,其它對象中的參數(shù)隨意定義,都可以在對應(yīng)的 reducers(effects)中獲取,從而實(shí)現(xiàn)消息傳遞,將最新的數(shù)據(jù)傳遞過去更新 model 的數(shù)據(jù)(state)

注意:Action在model自身模型以外定義時需要加model的namespace前綴, 在model中定義不需要加。

  • 添加樣式:
    CSS Modules會給組件的className加上hash字符串,來保證className僅對引用了樣式的組件有效,如styles.normal可能會輸出為normal___39QwY。
    className的輸出格式可以通過webpack.config進(jìn)行修改。

更新state

    namespace: 'monthCard',
    state: {
        list: [],
        total: 0,
        editData: {},
        loading: false
    },
    subscriptions: {},
    effects: {},
    reducers: {
+       updateState(state, action){
+           return {
+               ...state,
+               ...action.payload
+           }
+       }
    }

reducer 是唯一可以更新 state 的地方,這個唯一性讓我們的 App 更具可預(yù)測性,所有的數(shù)據(jù)修改都有據(jù)可查。reducer 是 pure function,他接收參數(shù) state 和 action,返回新的 state,通過語句表達(dá)即 (state, action) => newState。該函數(shù)把一個集合歸并成一個單值。

Reducer 的概念來自于是函數(shù)式編程,在 dva 中,reducers 聚合積累的結(jié)果是當(dāng)前 model 的 state 對象。通過 actions 中傳入的值,與當(dāng)前 reducers 中的值進(jìn)行運(yùn)算獲得新的值(也就是新的 state)。

注意:Reducer函數(shù)必須是純函數(shù)。

Effects異步處理

+   //異步請求
    //request 是我們封裝的一個網(wǎng)絡(luò)請求庫
+   async function queryFromService(data) {
+       return request("queryFromApi", {
+           data,
+           method: "post",
+           dataType: "payload"
+       })
+   }

    namespace: 'monthCard',
    state: {
        list: [],
        total: 0,
        editData: {},
        loading: false
    },
    subscriptions: {},
    effects: {
+       * query({ payload }, { call, put }){
+           yield put ({
+               type: `updateState`,
+               payload: {
+                   loading: true
+               }
+           })
+
+           const { data } = yield call (queryFromService, parse(payload))
+
+           if(data){
+               yield put({
+                   type: 'querySuccess',
+                   payload: {
+                       loading: false,
+                       list: data.data,
+                       editData: data.data,
+                       total: data.recordsTotal
+                   }
+               })
+           } else {
+               yield put({
+                   type: 'querySuccess',
+                   payload: {
+                       data: {},
+                       loading: false
+                   }
+               })
+           }       
+       },
+       * create(){},
+       * 'delete'(){},//delete是關(guān)鍵字
+       * update(){}
        
    },
    reducers: {
        updateState(state, action){
            return {
                ...state,
                ...action.payload
            }
        },

+       querySuccess(state, action){
+           if(action.payload.data){
+               const {
+                   data: {
+                       status,
+                       message
+                   }
+               } = action.payload
+
+               if(1 == status){
+                   //
+               }else {
+                   Message.error(message)
+               }
+           }
+
+           return {
+               ...state
+           }
+       }       
    }

當(dāng)數(shù)據(jù)需要從服務(wù)器獲取時,需要發(fā)起異步請求,請求到數(shù)據(jù)之后,通過調(diào)用 Reducers更新數(shù)據(jù)到全局state。dva 通過對 model 增加 effects 屬性來處理 side effect(異步任務(wù)),這是基于 redux-saga 實(shí)現(xiàn)的,語法為 generator。Generator 返回的是迭代器,通過 yield 關(guān)鍵字實(shí)現(xiàn)暫停功能。Redux-saga 中文文檔。

* query(action, {call, put, select}){}表示一個worker Saga,監(jiān)聽所有的query action,并觸發(fā)一個Api調(diào)用以獲取服務(wù)器數(shù)據(jù)。當(dāng)每個query action被發(fā)起時調(diào)用 call 和 put 都是 redux-saga 的 effects,call 表示調(diào)用異步函數(shù),put 表示 dispatch action,其他的還有 select, take, fork, cancel 等,select 則可以用來訪問其它 model。格式:*(action, effects) => void

在Effects里,Generator函數(shù)通過yield命令將異步操作同步化,無論是yield 亦或是 async 目的只有一個: 讓異步編寫跟同步一樣 ,從而能夠很好的控制執(zhí)行流程。

訂閱數(shù)據(jù)源

subscriptions: {
    setup(( dispatch, history )){
        history.listen(({ pathname, query }) => {
            if(pathToRegexp(`/y/monthCard/list`).test(pathname)) {
                dispatch({
                    type:`query`
                })
            }
        })
    }
}

Subscriptions 表示訂閱,用于訂閱一個數(shù)據(jù)源,然后按需 dispatch action。格式為 ({ dispatch, history }) => unsubscribe 。比如:當(dāng)用戶進(jìn)入 /y/monthCard/list 頁面時,觸發(fā) action query 加載數(shù)據(jù)。

如果 url 規(guī)則比較復(fù)雜,比如 /users/:userId/search,那么匹配和 userId 的獲取都會比較麻煩。這是推薦用 path-to-regexp 簡化這部分邏輯。

定義路由

路由決定進(jìn)入url渲染哪些Component。history 默認(rèn)是 hashHistory 并且?guī)в?_k 參數(shù),可以換成 browserHistory,也可以通過配置去掉 _k 參數(shù)。

工具

dva使用總結(jié)

dva將所有與數(shù)據(jù)操作相關(guān)的邏輯集中放在一個地方處理和維護(hù),在數(shù)據(jù)跟業(yè)務(wù)狀態(tài)交互比較緊密的場景下,會使我們的代碼更加清晰可控。尤其適用于數(shù)據(jù)跟業(yè)務(wù)狀態(tài)關(guān)聯(lián)性極強(qiáng)的企業(yè)級后臺信息管理系統(tǒng)。

對于一個企業(yè)級后臺管理系統(tǒng),由于要進(jìn)行大量的數(shù)據(jù)操作,在設(shè)計(jì)model時將不同類型的業(yè)務(wù)需求數(shù)據(jù)操作分開處理,便于維護(hù)。

項(xiàng)目的開發(fā)流程一般是從設(shè)計(jì)model state開始進(jìn)行抽象數(shù)據(jù),完成component后,將組件和model建立關(guān)聯(lián),通過dispatch一個action,在reducer中更新數(shù)據(jù)完成數(shù)據(jù)同步處理;當(dāng)需要從服務(wù)器獲取數(shù)據(jù)時,通過Effects數(shù)據(jù)異步處理,然后調(diào)用Reducer更新全局state。是一個單向的數(shù)據(jù)流動過程。解決了redux中代碼分散和重寫問題,總之,Dva:Build redux application easier and better。

學(xué)習(xí)資料

官方地址
Redux-saga 中文文檔
dva-knowledgemap
dva: react application arch in ant financial

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容