Dva知识点整理&&项目使用总结

Dva是什么

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )

  • 框架,而非类库
  • 基于redux, react-router, redux-soga的轻量级封装
  • 借鉴elm的概念, Reducer, Effect和Subscription
  • ...

可以说,dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作。

文件结构

官方推荐的:

├── /mock/           # 数据mock的接口文件
├── /src/            # 项目源码目录
│ ├── /components/   # 项目组件
│ ├── /routes/       # 路由组件(页面维度)
│ ├── /models/       # 数据模型
│ ├── /services/     # 数据接口
│ ├── /utils/        # 工具函数
│ ├── route.js       # 路由配置
│ ├── index.js       # 入口文件
│ ├── index.less    
│ └── index.html    
├── package.json     # 定义依赖的pkg文件
└── proxy.config.js  # 数据mock配置文件

初体验

一个小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

数据流向

image

数据的改变发生通常是通过:

  • 用户交互行为(用户点击按钮等)
  • 浏览器行为(如路由跳转等)触发的

当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State 。

所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致。如上图。

相关概念与理解

定义model


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

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

State是整个应用的数据层。应用的state被存储在一个object tree中。应用的初始state在model中定义,也就是说,由model state组成全局state。操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。

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

这里的Model非MVC中的M,是用于把数据相关的逻辑聚合到一起。

完成component


const monthCardPrice = ({
    monthCard,
    monthCard: {
        list
    },
    diapacth
 }) => {
    const queryList = () => {
        dispatch({
            type: `monthCard/query`,
            payload: {}//需要传递的数据
        })
    }
    return (
        <div>
            <button onclick={queryList}>查询</button>
        </div>
    )
}

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

export default connect(mapStateToProps)(monthCardPrice)

RouteComponent 表示 Router 里匹配路径的 Component,通常会绑定 model 的数据

Presentational Component是独立的纯粹的,例如ant.design UI组件的react实现,每个组件跟业务数据并没有耦合关系,只是完成自己独立的任务,需要的数据通过 props 传递进来,需要操作的行为通过接口暴露出去。 而 Container Component 更像是状态管理器,它表现为一个容器,订阅子组件需要的数据,组织子组件的交互逻辑和展示。

所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/目录下,而/components/目录下则是纯组件(Presentational Components)。

dva架构里component中基本不需要用到state。

  • 绑定数据
    这里利用es6结构赋值通过props将参数传入组件。monthCard对应model上的state,通过connect来绑定绑定model state。这里connect来自react-redux。意味着Component里可以拿到Model中定义的数据,Model中也能接收到Component里dispatch的action。实现了Model和Component的连接。

  • Action
    Action表示操作事件,可以是同步,也可以是异步。Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。

dispatch 函数,通过 type 属性指定对应的 actions 类型,而这个类型名在 reducers(effects)会一一对应,从而知道该去调用哪一个 reducers(effects),除了 type 以外,其它对象中的参数随意定义,都可以在对应的 reducers(effects)中获取,从而实现消息传递,将最新的数据传递过去更新 model 的数据(state)

注意:Action在model自身模型以外定义时需要加model的namespace前缀, 在model中定义不需要加。

  • 添加样式:
    CSS Modules会给组件的className加上hash字符串,来保证className仅对引用了样式的组件有效,如styles.normal可能会输出为normal___39QwY。
    className的输出格式可以通过webpack.config进行修改。

更新state

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

reducer 是唯一可以更新 state 的地方,这个唯一性让我们的 App 更具可预测性,所有的数据修改都有据可查。reducer 是 pure function,他接收参数 state 和 action,返回新的 state,通过语句表达即 (state, action) => newState。该函数把一个集合归并成一个单值。

Reducer 的概念来自于是函数式编程,在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。

注意:Reducer函数必须是纯函数。

Effects异步处理

+   //异步请求
    //request 是我们封装的一个网络请求库
+   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是关键字
+       * 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
+           }
+       }       
    }

当数据需要从服务器获取时,需要发起异步请求,请求到数据之后,通过调用 Reducers更新数据到全局state。dva 通过对 model 增加 effects 属性来处理 side effect(异步任务),这是基于 redux-saga 实现的,语法为 generator。Generator 返回的是迭代器,通过 yield 关键字实现暂停功能。Redux-saga 中文文档

* query(action, {call, put, select}){}表示一个worker Saga,监听所有的query action,并触发一个Api调用以获取服务器数据。当每个query action被发起时调用 call 和 put 都是 redux-saga 的 effects,call 表示调用异步函数,put 表示 dispatch action,其他的还有 select, take, fork, cancel 等,select 则可以用来访问其它 model。格式:*(action, effects) => void

在Effects里,Generator函数通过yield命令将异步操作同步化,无论是yield 亦或是 async 目的只有一个: 让异步编写跟同步一样 ,从而能够很好的控制执行流程。

订阅数据源

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

Subscriptions 表示订阅,用于订阅一个数据源,然后按需 dispatch action。格式为 ({ dispatch, history }) => unsubscribe 。比如:当用户进入 /y/monthCard/list 页面时,触发 action query 加载数据。

如果 url 规则比较复杂,比如 /users/:userId/search,那么匹配和 userId 的获取都会比较麻烦。这是推荐用 path-to-regexp 简化这部分逻辑。

定义路由

路由决定进入url渲染哪些Component。history 默认是 hashHistory 并且带有 _k 参数,可以换成 browserHistory,也可以通过配置去掉 _k 参数。

工具

dva使用总结

dva将所有与数据操作相关的逻辑集中放在一个地方处理和维护,在数据跟业务状态交互比较紧密的场景下,会使我们的代码更加清晰可控。尤其适用于数据跟业务状态关联性极强的企业级后台信息管理系统。

对于一个企业级后台管理系统,由于要进行大量的数据操作,在设计model时将不同类型的业务需求数据操作分开处理,便于维护。

项目的开发流程一般是从设计model state开始进行抽象数据,完成component后,将组件和model建立关联,通过dispatch一个action,在reducer中更新数据完成数据同步处理;当需要从服务器获取数据时,通过Effects数据异步处理,然后调用Reducer更新全局state。是一个单向的数据流动过程。解决了redux中代码分散和重写问题,总之,Dva:Build redux application easier and better。

学习资料

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容