💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # dva [dva](https://github.com/dvajs/dva) 首先是一个基于现有应用架构 [redux](https://github.com/reduxjs/redux) 和 [redux-saga](https://github.com/redux-saga/redux-saga) 的数据流方案,然后为了简化开发体验,dva 还额外内置了 [react-router](https://github.com/ReactTraining/react-router) 和 [fetch](https://github.com/github/fetch) ,所以也可以理解为一个轻量级的应用框架,没有引入任何新概念。 1. 脚手架工具:[dva-cli](https://github.com/dvajs/dva-cli) 2. [dva-知识地图](https://github.com/dvajs/dva-knowledgemap/blob/29549f253ad130802146b6d066b3ad1af4c7d0be/README.md) ## 整体架构 分析一下这个图: ![](https://img.kancloud.cn/2a/90/2a909b4e546464d7bef2a296873fdf7e_1614x508.png) 首先我们根据 `url` 访问相关的 `Route-Component`,在组件中我们通过 `dispatch` 发送 `action` 到 `model` 里面的 `effect` 或者直接 `Reducer`; 当我们将`action`发送给`effect`,基本上是取服务器上面请求数据的,服务器返回数据之后,`effect` 会发送相应的 `action` 给 `reducer`,由**唯一能操作** `state` 的 `reducer` 产生新的 `state` ,然后通过`connect` 重新渲染组件。 当我们将`action`发送给`reducer`,那直接由 `reducer` 产生新的 `state`,然后通过 `connect` 重新渲染组件。 这样我们就能走完一个流程了。 ## Model `model` 是 `dva` 中最重要的概念,`Model` 非 `MVC` 中的 `M`,而是领域模型,用于把数据相关的逻辑聚合到一起,几乎所有的数据,逻辑都在这里进行处理分发 ### state State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。 在 dva 中你可以通过 dva 的实例属性 `_store` 看到顶部的 state 数据,但是通常你很少会用到: ```js const app = dva(); console.log(app._store); // 顶部的 state 数据 ``` ### namespace `model` 的命名空间,同时也是它在 全局 `state` 上的属性(作为一个大对象的键值),只能用字符串,我们发送在发送 `action` 到相应的 `reducer` 时,就会需要用到 `namespace` 。 ### dispatch 函数 `type dispatch = (a: Action) => Action` dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。 在 dva 中,`connect`( react-redux 的 connect) Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,常见的形式如: ```js // 返回 Promise dispatch({ type: 'user/add', // 如果在 model 外调用,需要添加 namespace payload: {}, // 需要传递的信息 }); ``` [Dva异步的同步处理](https://www.jianshu.com/p/c11fd2e10f0b) ### reducers 以`key/value` 格式定义 `reducer`,用于处理同步操作,唯一可以修改 `state` 的地方。由 `action` 触发。其实一个纯函数。 在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。 需要注意的是 Reducer 必须是[纯函数](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md),所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。 并且,每一次的计算都应该使用[immutable data](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md#reasonable),这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。 **如果 reducers中的同步方法名和 effects 中的异步方法名 相同,那么都会被调用,且 优先执行 reducers,应该避免同名!** ### effects 用于处理异步操作和业务逻辑,不直接修改 `state`,简单的来说,就是获取从服务端获取数据,并且发起一个 `action` 交给 `reducer` 的地方。 其中它用到了 redux-saga,里面有几个常用的函数。 ``` *add(action, { call, put }) { yield call(delay, 1000); // 执行异步函数 yield put({ type: 'minus' }); // 发出一个 Action,类似于 dispatch }, ``` ### subscription Subscriptions 是一种从 源 获取数据的方法,它来自于 elm。 Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 `dispatch` 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。 ```js import key from 'keymaster'; ... app.model({ namespace: 'count', subscriptions: { keyEvent(dispatch) { key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); }, } }); ``` ## Router 这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 [History API](http://mdn.beonex.com/en/DOM/window.history.html) 可以监听浏览器url的变化,从而控制路由相关操作。 dva 实例提供了 router 方法来控制路由,使用的是 [react-router](https://github.com/reactjs/react-router)。 ```js import { Router, Route } from 'dva/router'; app.router(({history}) => <Router history={history}> <Route path="/" component={HomePage} /> </Router> ); ``` ## Route Components 在[组件设计方法](https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/tutorial/04-%E7%BB%84%E4%BB%B6%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%B3%95.md)中,我们提到过 Container Components,在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。 所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在`/routes/`目录下,而`/components/`目录下则是纯组件(Presentational Components)。 ## dva-loading 每个页面中将loading状态作为属性传入组件,在进行样式处理,比如转圈圈或者显示正在加载什么的,但是重点是,我们的app有多个页面,每个页面都这么做,很繁琐。 dva 有一个管理 effects 执行的 hook,并基于此封装了 [dva-loading 插件](https://github.com/dvajs/dva/tree/master/packages/dva-loading)。通过这个插件,我们可以不必一遍遍地写`showLoading`和`hideLoading`,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。 `umi-plugin-dva` 默认内置了 dva-loading 插件。 dva-loading的使用非常简单,在index.js中加入: ``` import createLoading from 'dva-loading'; const app = dva(); app.use(createLoading()); ``` loading 在异步请求发出那一刻会持续监听该异步请求方法的状态,在异步请求结束之前 isLoading 的值一直是 true,当此次异步请求结束时 isLoading 的值变成 false,同时 loading 对象停止监听。 如何只做一次状态处理,每次请求期间都会触发loading状态呢,其实也很简单啦,因为dva-loading提供了一个global属性。 ``` loading: { global: true, effects: { 'user/query': true }, models: { 'user/query', true }, } /* 'user/query' 是单一异步请求后台的方法,在请求过程中,global 和 effects 中值都为 true, 当请求结束,已经有数据返回,userList 中获取到用户列表时该值为 false。 可能有个疑问:既然 global 可以展示异步加载是否完成,为什么还要 effects 属性,这是因为一个页面中可能同时有多个异步加载,只要有一个异步加载没有完成,global 都是 true。 */ ``` 如果同时发出若干个异步请求,需求是当所有异步请求都响应才做下一步操作,可以使用`loading.global()`方法,该方法监听所有异步请求的状态。 具体参考这个[Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/f81d36c639d35e67db8b2d0a65b561e4af203eb1)。 ## 参考 [dva理论到实践——帮你扫清dva的知识盲点](https://www.jianshu.com/p/e184cd6d253c) ===好用真舒服,感觉起飞了===