小工具      在线工具  汉语词典  css  js  c++  java

快速开始使用 Dva

额外说明

收录于:42天前

Dva概念

#数据流向

数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。

好榜样

#State

type State = any

State表示Model的状态数据,通常表示为JavaScript对象(当然可以是任意值);操作时,每次都要当作不可变数据(immutable data)对待,保证每次都是一个全新的对象,没有Reference关系,这样才能保证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

调度函数是用于触发操作的函数。 Action是改变State的唯一方法,但它只是描述一种行为。 Dipatch可以看作是触发这种行为的一种方式,而Reducer则描述了如何改变数据。

在dva中,connect Model的组件可以通过props访问dispatch,并且可以调用Model中的Reducer或者Effects。常见形式如下:

dispatch({
  type: 'user/add', // 如果在 model 外调用,需要添加 namespace
  payload: {}, // 需要传递的信息
});

#Reducer

type Reducer<S, A> = (state: S, action: A) => S

Reducer(也称归约函数)函数接受两个参数:上次累加操作的结果和当前要累加的值,并返回一个新的累加结果。该函数将一组值组合成一个值。

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 必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用不可变数据,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。

#Effect

这些效应称为副作用。在我们的应用中,最常见的就是异步操作。它来自函数式编程的概念。它被称为副作用,因为它使我们的函数变得不纯粹。相同的输入不一定会产生相同的输出。

dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了生成器的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。至于为什么我们这么纠结于 纯函数,如果你想了解更多可以阅读FP 基本充分指南,或者它的中文译本JS 函数式编程指南

#Subscription

Subscriptions 是一种从 来源 获取数据的方法,它来自于 elm。

订阅语义就是订阅,用于订阅数据源,然后根据条件调度所需的操作。数据源可以是当前时间、服务器的websocket连接、键盘输入、地理位置变化、历史路由变化等。

import key from 'keymaster';
...
app.model({
  namespace: 'count',
  subscriptions: {
    keyEvent({dispatch}) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  }
});

#Router

这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 历史API 可以监听浏览器url的变化,从而控制路由相关操作。

dva 实例提供了 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)。

#

以上是原文链接

快速上手

查看英文版

本章节会引导开发者快速搭建 德瓦 项目,并熟悉他的所有概念。

最终效果:

这是一款测试鼠标点击速度的应用程序,并记录用户1秒内点击最多的次数。顶部最高记录记录最大速度;中间一个是当前速度,提供即时反馈,让用户更加投入;最下面的是点击按钮。

看到这个需求,我们可能会想:

  1. 如何创建应用程序?
  2. 创建完成后,如何一步步组织代码呢?
  3. 开发完成后,如何构建、部署、发布?

在代码组织部分,您可能想要:

  1. 组件怎么写?
  2. 风格怎么写?
  3. 模型怎么写?
  4. 如何连接模型和组件?
  5. 用户操作后如何将数据更新到State?
  6. 如何处理异步逻辑? (点击后+1,延迟一秒后-1)
  7. 如何处理路由?

还:

  1. 我不想每次刷新的时候都将top记录清为0。我想通过localStorage进行记录,这样刷新后可以保留最高的记录。怎么处理呢?
  2. 我想同时支持键盘点击速度测量,怎么办?

我们可以带着这些疑问来到这篇文章,但不用担心它有多复杂,因为整个 JavaScript 代码只有 70 多行。

安装 dva-cli

您更喜欢专注于逻辑本身,而不是手动键入代码行来构建初始项目结构并配置开发环境。

所以你需要安装的第一件事是 dva-cli。 dva-cli是dva的命令行工具,包括init、new、generate等功能。目前最重要的功能就是能够快速生成项目以及你需要的代码片段。

$ npm install -g dva-cli

安装完成后,可以通过 dva -v 查看版本,以及 dva -h 查看帮助信息。

创建新应用程序

安装完 dva-cli 后,我们用他来创建一个新应用,取名 myApp

$ dva new myApp --demo

注意:--demo 用于创建简单的 demo 级项目,正常项目初始化不加要这个参数。

然后进入项目目录并启动。

$ cd myApp
$ npm start

几秒钟后,您将看到如下输出:

          proxy: listened on 8989
     livereload: listening on 35729
?  173/173 build modules
webpack: bundle build is now finished.

(要关闭服务器,请按 Ctrl-C。)

在浏览器里打开 http://本地主机:8989/ ,正常情况下,你会看到一个 "Hello Dva" 页面。

定义模型

收到需求后建议的做法不是立即编码,而是先以上帝模式进行整体设计。

  1. 首先设计模型
  2. 重新设计组件
  3. 最后连接模型和组件

在这个需求中,我们定义模型如下:

app.model({
  namespace: 'count',
  state: {
    record : 0,
    current: 0,
  },
});

namespace 是 model state 在全局 state 所用的 key,state 是默认数据。然后 state 里的 record 表示 highest recordcurrent 表示当前速度。

组件齐全

完成 Model 之后,我们来编写 Component 。推荐尽量通过 无状态函数 的方式组织 Component,在 dva 的架构里我们基本上不需要用到 state 。

import styles from './index.less';
const CountApp = ({count, dispatch}) => {
  return (
    <div className={styles.normal}>
      <div className={styles.record}>Highest Record: {count.record}</div>
      <div className={styles.current}>{count.current}</div>
      <div className={styles.button}>
        <button onClick={() => { dispatch({type: 'count/add'}); }}>+</button>
      </div>
    </div>
  );
};

注意:

  1. 这里先 import styles from './index.less';,再通过 styles.xxx 的方式声明 css classname 是基于 css-modules 的方式,后面的样式部分会用上
  2. 通过 props 传入两个值,count 和 dispatchcount 对应 model 上的 state,在后面 connect 的时候绑定,dispatch用于分发 action
  3. dispatch({type: 'count/add'}) 表示分发了一个 {type: 'count/add'} 的 action,至于什么是 action,详见:[email protected]

更新状态

更新 state 是通过 reducers 处理的,详见 减速器@redux.js.org

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

这个需求里,我们需要定义两个 reducer,add 和 minus,分别用于计数的增和减。值得注意的是 add 时 record 的逻辑,他只在有更高的记录时才会被记录。

请注意,这里的 add 和 minus 两个action,在 count model 的定义中是不需要加 namespace 前缀的,但是在自身模型以外是需要加 model 的 namespace

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
+ reducers: {
+   add(state) {
+     const newCurrent = state.current + 1;
+     return { ...state,
+       record: newCurrent > state.record ? newCurrent : state.record,
+       current: newCurrent,
+     };
+   },
+   minus(state) {
+     return { ...state, current: state.current - 1};
+   },
+ },
});

注意:

  1. { ...state } 里的 ... 是对象扩展运算符,类似 Object.extend,详见:对象扩展运算符
  2. add(state) {} 等同于 add: function(state) {}

绑定数据

还记得前面组件中使用的计数和调度吗?您对它们的起源有疑问吗?

定义模型和组件后,我们需要连接它们。这样,Component就可以使用Model中定义的数据,并且Model也可以接收Component中调度的动作。

这个需求里只要用到 count

function mapStateToProps(state) {
  return { count: state.count };
}
const HomePage = connect(mapStateToProps)(CountApp);

这里的 connect 来自 反应还原

定义路线

收到url后,就决定渲染哪些组件,这是由路由决定的。

这个需求只需要一页,路由部分不需要修改。

app.router(({history}) =>
  <Router history={history}>
    <Route path="/" component={HomePage} />
  </Router>
);

注意:

  1. history 默认是 hashHistory 并且带有 _k 参数,可以换成 browserHistory,也可以通过配置去掉 _k 参数。

现在刷新浏览器。如果一切顺利,您应该会看到以下效果:

添加样式

默认是通过 css modules 的方式来定义样式,这和普通的样式写法并没有太大区别,由于之前已经在 Component 里 hook 了 className,这里只需要在 index.less 里填入以下内容:

.normal {
  width: 200px;
  margin: 100px auto;
  padding: 20px;
  border: 1px solid #ccc;
  box-shadow: 0 0 20px #ccc;
}

.record {
  border-bottom: 1px solid #ccc;
  padding-bottom: 8px;
  color: #ccc;
}

.current {
  text-align: center;
  font-size: 40px;
  padding: 40px 0;
}

.button {
  text-align: center;
  button {
    width: 100px;
    height: 40px;
    background: #aaa;
    color: #fff;
  }
}

效果如下:

异步处理

在此之前,我们所有的操作处理都是同步的。当用户单击 + 按钮时,该值会增加 1。

现在我们要开始处理异步任务,dva 通过对 model 增加 effects 属性来处理 side effect(异步任务),这是基于 redux-saga 实现的,语法为 generator。(但是,这里不需要我们理解 generator,知道用法就可以了)

在这个需求中,当用户点击+按钮,值增加1时,会触发一个额外的副作用,即延迟1秒后,值会变成1。

app.model({
  namespace: 'count',
+ effects: {
+   *add(action, { call, put }) {
+     yield call(delay, 1000);
+     yield put({ type: 'minus' });
+   },
+ },
...
+function delay(timeout){
+  return new Promise(resolve => {
+    setTimeout(resolve, timeout);
+  });
+}

注意:

  1. *add() {} 等同于 add: function*(){}
  2. call 和 put 都是 redux-saga 的 effects,call 表示调用异步函数,put 表示 dispatch action,其他的还有 select, take, fork, cancel 等,详见 redux-saga 文档
  3. 默认的 effect 触发规则是每次都触发(takeEvery),还可以选择 takeLatest,或者完全自定义 take 规则

刷新浏览器。如果正常的话,初始需求图中的所有需求都应该已经实现了。

订阅键盘事件

实现了鼠标测速之后,如何实现键盘测速呢?

在 dva 里有个叫 subscriptions 的概念,他来自于 榆树

订阅语义就是订阅,用于订阅数据源,然后根据条件调度所需的操作。数据源可以是当前时间、服务器的websocket连接、键盘输入、地理位置变化、历史路由变化等。

dva 中的订阅和模型绑定。

+import key from 'keymaster';
...
app.model({
  namespace: 'count',
+ subscriptions: {
+   keyboardWatcher({ dispatch }) {
+     key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
+   },
+ },
});

这里我们不需要手动安装 keymaster 依赖,在我们敲入 import key from 'keymaster'; 并保存的时候,dva-cli 会为我们安装 keymaster 依赖并保存到 package.json 中。输出如下:

use npm: tnpm
Installing `keymaster`...
[keymaster@*] installed at node_modules/.npminstall/keymaster/1.6.2/keymaster (1 packages, use 745ms, speed 24.06kB/s, json 2.98kB, tarball 15.08kB)
All packages installed (1 packages installed from npm registry, use 755ms, speed 23.93kB/s, json 1(2.98kB), tarball 15.08kB)
?  2/2 build modules
webpack: bundle build is now finished.

所有代码

索引.js

import dva, { connect } from 'dva';
import { Router, Route } from 'dva/router';
import React from 'react';
import styles from './index.less';
import key from 'keymaster';

const app = dva();

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
  reducers: {
    add(state) {
      const newCurrent = state.current + 1;
      return { ...state,
        record: newCurrent > state.record ? newCurrent : state.record,
        current: newCurrent,
      };
    },
    minus(state) {
      return { ...state, current: state.current - 1};
    },
  },
  effects: {
    *add(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
  },
  subscriptions: {
    keyboardWatcher({ dispatch }) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  },
});

const CountApp = ({count, dispatch}) => {
  return (
    <div className={styles.normal}>
      <div className={styles.record}>Highest Record: {count.record}</div>
      <div className={styles.current}>{count.current}</div>
      <div className={styles.button}>
        <button onClick={() => { dispatch({type: 'count/add'}); }}>+</button>
      </div>
    </div>
  );
};

function mapStateToProps(state) {
  return { count: state.count };
}
const HomePage = connect(mapStateToProps)(CountApp);

app.router(({history}) =>
  <Router history={history}>
    <Route path="/" component={HomePage} />
  </Router>
);

app.start('#root');


// ---------
// Helpers

function delay(timeout){
  return new Promise(resolve => {
    setTimeout(resolve, timeout);
  });
}

构建一个应用程序

我们已经在开发环境中验证过了,现在需要部署给用户。键入以下命令:

$ npm run build

输出:

> @ build /private/tmp/dva-quickstart
> atool-build

Child
    Time: 6891ms
        Asset       Size  Chunks             Chunk Names
    common.js    1.18 kB       0  [emitted]  common
     index.js     281 kB    1, 0  [emitted]  index
    index.css  353 bytes    1, 0  [emitted]  index

命令执行成功后,编译后的产物会在dist目录下。

下一步

通过完成这个简单的例子,您是否已经有了上一个问题的答案?您熟悉 dva 中包含的概念:模型、路由器、减速器、效果、订阅吗?

下一步可以进入 教程 了解更多。

原文链接

. . .

相关推荐

额外说明

Adams软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载   一、软件简介 Adams是一款由Mechanical Dynamics Inc(MDI)开发的有限元分析软件,主要用于模拟机械系统的运动和动力学行为。它广泛应用于汽车、航空航天、机械、电子等多个领域。以下是对Adam

额外说明

LIS3DHTR加速度传感器6D方向识别

1. (LIS3DH_TOP)0x60->0x42(LIS3DH_UP_DX) X轴往上 2. (LIS3DH_TOP)0x60->0x48(LIS3DH_DW_DX) Y轴往上 3. (LIS3DH_TOP)0x60->0x50(LIS3DH_BOTT

额外说明

FlinkCDC菜鸟教程/演示 Mysql基于Flink CDC 导入 Kafka

演示: Mysql基于Flink CDC 导入 Kafka 使用下面的内容创建一个 docker-compose.yml 文件: version: '2.1' services: mysql:   image: debezium/example-

额外说明

[Hello World] 二分查找笔记

活动地址:CSDN21天学习挑战赛 目录 一、介绍 1.1 定义 1.2 前提条件 1.3 原理 1.4 局限性 1.5 复杂度 二、代码示例 二、实践 一、介绍 1.1 定义 二分查找(Binary Search)也称折半查找,它是一种效率较高的查找方

额外说明

计算机网络实验: 使用Wireshark抓包工具进行网络层和链路层网络协议分析(Ethernet & ARP部分)

在这一部分中,我们将抓取并分析 Ethernet 数据帧的内容,观察 ARP 协议在现实网络中如何工作。 抓取Ethernet 数据帧并进行分析: a) 启动web浏览器,清空浏览器的缓存,启动 Wireshark 程序 b) 键入: http://ga

额外说明

YOLOv5论文作图教程(2)— 软件界面布局和基础功能介绍

前言:Hello大家好,我是小哥谈。通过上一节课的学习,相信大家都已成功安装好软件了,本节课就给大家详细介绍一下Axure RP9软件的界面布局及相关基础功能,希望大家学习之后能够有所收获!-   前期回顾:              YOLOv5论文作

额外说明

SpringCloud H版 EureKa使用及集群讲解

一、SpringCloud SpringCloud也是一个我们现在基本开发必备的框架之一了,他提供了整个微服务的解决方案,早在很早以前我就写了详细的SpringCloud的博客,但一直没写系列的教程,现在就准备讲解下SpringCloud全家桶的系列教程

额外说明

使用jquery,实现根据浏览器窗口大小改变,使标签在规定时间内淡出淡入。

效果:  jquery代码: $(window).resize(function(){//Windows的resize函数,窗口发生变化时触发 //获取浏览器高度 var h = $(window).height(); if(h < 500) {/

额外说明

CV面试知识点总结

一.Batch Normalization(批归一化,简称BatchNorm) 1.缓解梯度消失 在神经网络中Batch Norm在线性层的后面,激活函数层的前面。 它把线性层的输出变成了以0为中心,标准差为1的分布,进入sigmoid激活函数的非饱和区

额外说明

MySql安装与使用

MySql安装与使用 mysql是目前最流行的关系型数据库管理系统,在WEB应用方面MySQL是最好的RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一。 MySQL是非常灵活的一款数

ads via 小工具