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

数据流向

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

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

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

当此类行为会改变数据的时候可以通过 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

作者:黎贝卡beka
链接:https://www.jianshu.com/p/f7401adce447
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文出处:简书【黎贝卡beka】

原文链接:https://www.jianshu.com/p/f7401adce447

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论