DvaJS入门课
不管是Vue还是React,他们都没解决组件间的通信和数据流问题。当然,这个说法不是很准确,准确的说法是他们都没很好的处理这些问题。我们是可以用一些烂手段去解决这个问题,但是当应用比较大、数据多的时候,这将变的非常难以维护。因此,我们还需要引入别的库去优雅的解决这个问题。比如:Vue中的Vuex,React中的dva。如果已经掌握Vuex,dva也能很快上手。
React没有解决的问题(Dva的由来)
React 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。
如果开发大应用,还需要解决一个问题。
- 通信:组件之间如何通信?
- 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等
介绍
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
dva = React-Router + Redux + Redux-saga
特性
- 易学易用,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用后更是降低为 0 API
- elm 概念,通过 reducers, effects 和 subscriptions 组织 model
- 插件机制,比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
- 支持 HMR,基于 babel-plugin-dva-hmr 实现 components、routes 和 models 的 HMR
构建项目
安装dva-cli
npm install dva-cli -g
创建新应用
dva new dva-quickstart
启动项目
然后我们cd
进入dva-quickstart
目录,并启动开发服务器:$ cd dva-quickstart
$ npm start
使用 antd通过 npm 安装
antd
和babel-plugin-import
。babel-plugin-import
是用来按需加载 antd 的脚本和样式的,详见 repo 。$ npm install antd babel-plugin-import --save
编辑
.webpackrc
,使babel-plugin-import
插件生效。{
+ "extraBabelPlugins": [
+ ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
+ ]
}
定义路由我们要写个应用来先显示产品列表。首先第一步是创建路由,路由可以想象成是组成应用的不同页面。
新建 route component
routes/Products.js
,内容如下:import React from 'react'; const Products = (props) => (
<h2>List of Products</h2>
); export default Products;
添加路由信息到路由表,编辑
router.js
:+ import Products from './routes/Products';
...
+ <Route path="/products" exact component={Products} />
然后在浏览器里打开 http://localhost:8000/#/products ,你应该能看到前面定义的
h2
标签。编写UI Component随着应用的发展,你会需要在多个页面分享 UI 元素 (或在一个页面使用多次),在 dva 里你可以把这部分抽成 component 。
我们来编写一个
ProductList
component,这样就能在不同的地方显示产品列表了。新建
components/ProductList.js
文件:import React from 'react';
import PropTypes from 'prop-types';
import { Table, Popconfirm, Button } from 'antd'; const ProductList = ({ onDelete, products }) => {
const columns = [{
title: 'Name',
dataIndex: 'name',
}, {
title: 'Actions',
render: (text, record) => {
return (
<Popconfirm title="Delete?" onConfirm={() => onDelete(record.id)}>
<Button>Delete</Button>
</Popconfirm>
);
},
}];
return (
<Table
dataSource={products}
columns={columns}
/>
);
}; ProductList.propTypes = {
onDelete: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
}; export default ProductList;
定义 Model完成 UI 后,现在开始处理数据和逻辑。
dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
新建 model
models/products.js
:export default {
namespace: 'products',
state: [],
reducers: {
'delete'(state, { payload: id }) {
return state.filter(item => item.id !== id);
},
},
};
这个 model 里:
namespace
表示在全局 state 上的 keystate
是初始值,在这里是空数组reducers
等同于 redux 里的 reducer,接收 action,同步更新 state
然后别忘记在
index.js
里载入他:// 3. Model
+ app.model(require('./models/products').default);
connect 起来到这里,我们已经单独完成了 model 和 component,那么他们如何串联起来呢?
dva 提供了 connect 方法。如果你熟悉 redux,这个 connect 就是 react-redux 的 connect 。
编辑
routes/Products.js
,替换为以下内容:import React from 'react';
import { connect } from 'dva';
import ProductList from '../components/ProductList'; const Products = ({ dispatch, products }) => {
function handleDelete(id) {
dispatch({
type: 'products/delete',
payload: id,
});
}
return (
<div>
<h2>List of Products</h2>
<ProductList onDelete={handleDelete} products={products} />
</div>
);
}; // export default Products;
export default connect(({ products }) => ({
products,
}))(Products);
最后,我们还需要一些初始数据让这个应用 run 起来。编辑
index.js
:- const app = dva();
+ const app = dva({
+ initialState: {
+ products: [
+ { name: 'dva', id: 1 },
+ { name: 'antd', id: 2 },
+ ],
+ },
+ });
刷新浏览器,应该能看到以下效果:
Dva 概念
数据流向
数据的改变发生通常是通过:
- 用户交互行为(用户点击按钮等)
- 浏览器行为(如路由跳转等)触发的
当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State。所以,在 dva 中,数据流向非常清晰简明。
Models
核心概念
- State:一个对象,保存整个应用状态
- View:React 组件构成的视图层
- Action:一个对象,描述事件
- connect 方法:一个函数,绑定 State 到 View
- dispatch 方法:一个函数,发送 Action 到 State
State
type State = any
State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
在 dva 中你可以通过 dva 的实例属性 _store
看到顶部的 state 数据,但是通常你很少会用到:
const app = dva();
console.log(app._store); // 顶部的 state 数据
Action
type AsyncAction = any
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type
属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch
函数;需要注意的是 dispatch
是在组件 connect Models以后,通过 props 传入的。
dispatch({
type: 'add',
});
dispatch 函数
type dispatch = (a: Action) => Action
dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,常见的形式如:
dispatch({
type: 'user/add', // 如果在 model 外调用,需要添加 namespace
payload: {}, // 需要传递的信息
});
Reducer
type Reducer = (state: S, action: A) => S
Reducer(也称为 reducing function)函数接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。
Reducer 的概念来自于是函数式编程,很多语言中都有 reduce API。如在 javascript 中:
[{x:1},{y:2},{z:3}].reduce(function(prev, next){
return Object.assign(prev, next);
})
//return {x:1, y:2, z:3}
在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。需要注意的是 Reducer 必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。
Effect
Effect 被称为副作用,在我们的应用中,最常见的就是异步操作。它来自于函数编程的概念,之所以叫副作用是因为它使得我们的函数变得不纯,同样的输入不一定获得同样的输出。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。
dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。至于为什么我们这么纠结于 纯函数,如果你想了解更多可以阅读Mostly adequate guide to FP,或者它的中文译本JS函数式编程指南。
effects: {
*addAfter1Second (action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
},
}
effects和reducer的调用重名问题:
一般来说,修改state的时候使用reducer,其他时候都用effects。如果名称一样,在dva@2后,只有effect会调用,所以两个不能重名。
Subscription
Subscriptions 是一种从源获取数据的方法,它来自于 elm。
Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。 格式为 ({ dispatch, history }) => unsubscribe
。
subscriptions 只是定义了一个地方,用来订阅数据。
然后会传递 dispatch 和 history(dispatch 是 redux 的 store 中的 dispatch ,history 是 react-router 中的history。),具体要怎么订阅或是监听都需要第三方的类库来实现。
在这个 subsription 中的方法名是随意定的,每次变化都会一次去调用里面的所有方法。
import key from 'keymaster'; // 实现键盘监听的库
...
app.model({
namespace: 'count',
subscriptions: {
keyEvent({dispatch}) {
key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
},
setup({ dispatch, history }) {
history.listen(({ pathname }) => {
if (pathname === '/users') {
dispatch({
type: 'users/fetch',
});
}
});
},
}
});
参考:Subscriptions能否补充一些例子或者文档呢?
Router
这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作。
dva 实例提供了 router 方法来控制路由,使用的是react-router。
import { Router, Route } from 'dva/router';
app.router(({history}) =>
<Router history={history}>
<Route path="/" component={HomePage} />
</Router>
);
Route Components
在组件设计方法中,我们提到过 Container Components,在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。
所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/
目录下,而/components/
目录下则是纯组件(Presentational Components)。
dva 应用的最简结构
const app = dva();
// 注册 Model
app.model({
namespace: 'count',
state: 0,
reducers: {
add(state) { return state + 1 },
},
effects: {
*addAfter1Second(action, { call, put }) {
yield call(delay, 1000); // 和 Server 层交互
yield put({ type: 'add' });
},
},
subscriptions: {
keyEvent({dispatch}) {
key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
},
// setup在组件渲染之前会触发,setup一般是直接出发effects里面的方法
setup({ dispatch, history }) {
dispatch({ type: 'fetch'}); //如果是当前model可以直接写方法名
},
}
});
// 注册视图
app.router(() => <ConnectedApp />);
// 启动应用
app.start('#root');
总结
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,主要解决了组件通信和数据流问题。然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch。同时,dva 插件机制可以更好的扩展功能,综上,dva 可以理解为一个轻量级的应用框架。
参考:
DvaJS入门课的更多相关文章
- 重新理解CEO的学习能力----HHR计划----以太入门课--第一课
一共5个小节. 第一节:开始学习 1,投资人最看重的一点:CEO的学习能力. (因为CEO需要:找优秀的合伙人,需要市场调研,机会判断,组建团队,验证方向,去融资,冷启动,做增长,解决法务,财务,税务 ...
- X86逆向1:软件破解入门课【课件下载】
从本节课开始,我将带领小白入门学习软件破解的相关内容,大佬绕过,以后将会定期更新从最基本的破解知识点开始学习,由简单到复杂循序渐进,难度会逐步提高. 为了防止版权方面的争议,我将自行编写一些破解案例来 ...
- IC入门课第五课作业:完善 Microblog 前端(1、显示发布者的名字;2、增加新UI、3、关注其他学员的 canister)
上周完成了 IC 入门课程第五课的作业 现将答案贴出,欢迎同学们参考,禁止抄袭作业哦 课程作业 (完善 microblog 前端) 1. 显示消息的发布者名字 a. 给 Message 增加 auth ...
- flash 入门课知识小结
一. 几种类型帧的小结:(关键帧.空白关键帧.普通帧)1. 特点 帧——是进行flash动画制作的最基本的单位,每一个精彩的flash动画都是由很多个精心雕琢的帧构成的,在时间轴上的每一帧都可以包含 ...
- 风趣的JavaScript面向对象入门课程一
在我们程序猿界一直流传这这么一个joke,没女票我们可以new一个.没房子没票子没车子我们同样new一个!当然这听着更像是一种自嘲,毕竟我们程序猿都爱自嘲,哈哈,废话不多说,今天就由我带着你们来入Ja ...
- 复盘实战营一期毕业典礼----HHR计划----以太入门课--第一课
你要永远记住,实事求是. 我好像没能力给大家分享什么.分享点我的专业知识吧.我是做推荐+增长的,在一家D轮 DAU快千万的创业公司做增长优化负责人.一路优化,我把人均时长提高了30多分钟(现在人均时长 ...
- 【转】iOS 开发怎么入门?
原文网址:http://www.zhihu.com/question/20264108 iOS 开发怎么入门? 请问有设计模式.内存管理方面的资料吗?最好有除了官方文档之外的其它内容,10 条评论 分 ...
- Unity Shader入门教程(二)最基本的Diffuse和Normal样例
本教程参考了<猫都能学会的Unity3dShaderLab教程.CHM>, 1.请上网搜索并下载此文件. 2.随后再下载里面提到的素材: http://vdisk.weibo.com/s/ ...
- Python爬虫入门教程 2-100 妹子图网站爬取
妹子图网站爬取---前言 从今天开始就要撸起袖子,直接写Python爬虫了,学习语言最好的办法就是有目的的进行,所以,接下来我将用10+篇的博客,写爬图片这一件事情.希望可以做好. 为了写好爬虫,我们 ...
随机推荐
- Java面试题2-附答案
JVM的内存结构 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.Java虚拟机栈: 线程私有:每个方法在执行的时候会创建一个栈帧,存储了局部变量表, ...
- java 的CAS
CAS:什么是 CAS 机制?cas目的是实现原子操作解释一下:"原子操作(atomic operation)是不需要synchronized",这是多线程编程的老生常谈了.所谓原 ...
- xml字符串转json字符串
XML字符串转JSON字符串网上的方法很多,这里主要推荐两种: 1.使用org.json包 jar地址:http://mvnrepository.com/artifact/org.json/json ...
- TCP与UDP 笔记
本文整理自:<图解TCP/IP 第5版>作者:[日] 竹下隆史,[日] 村山公保,[日] 荒井透,[日] 苅田幸雄 著译者:乌尼日其其格出版时间:2013-07 TCP提供可靠的通信传输, ...
- [LC] 426. Convert Binary Search Tree to Sorted Doubly Linked List
Convert a BST to a sorted circular doubly-linked list in-place. Think of the left and right pointers ...
- 爬虫之使用requests爬取某条标签并生成词云
一.爬虫前准备 1.工具:pychram(python3.7) 2.库:random,requests,fake-useragent,json,re,bs4,matplotlib,worldcloud ...
- 3DSMAX卸载/完美解决安装失败/如何彻底卸载清除干净3DSMAX各种残留注册表和文件的方法
在卸载3dsmax重装3dsmax时发现安装失败,提示是已安装3dsmax或安装失败.这是因为上一次卸载3dsmax没有清理干净,系统会误认为已经安装3dsmax了.有的同学是新装的系统也会出现3ds ...
- Web服务器的配置与管理
Web服务器的配置与管理(2) 虚拟主机技术 在上篇博文中,我们已经利用IIS搭建好了一台Web服务器,并可以成功访问IIS中自带的默认站点,那么我们是否可以在这台服务器中再创建另外一个Web站点?也 ...
- Mac使用sublime text3的快捷键
符号说明⌘:command⌃:control⌥:option⇧:shift↩:enter⌫:delete 打开/关闭/前往⌘⇧N 打开一个新的sublime窗口⌘N 新建文件⌘⇧W 关闭sublime ...
- Hadoop的存储架构介绍
http://lxw1234.com/archives/2016/04/638.htm 该文章介绍了Hadoop的架构原理,简单易懂. 目前公司提供Hadoop的运算集群BMR,可以直接申请集群资源.