009 Umi 中的 elm 概念的数据流管理方案 Dva

简介: 009 Umi 中的 elm 概念的数据流管理方案 Dva

image.png


虽然 Umi 中的 dva 已经不是官方推荐的最好的数据流管理方案了,但是学习 dva 的时候,其实更有利于我们后面熟悉纯 hooks 的数据流管理方案。


在 Umi 中我们对请求方法做了高效的封装,对开发中遇到的请求相关的服务都做了内置功能。

比如 mock 数据、请求代理、统一请求地址配置、接口文件组织等都有鲜明的 Umi 风格。


在接下来的几个课程中,我们会详细的说明,Umi 在数据获取方面提供的能力和服务。

Umi 将如何帮助你高效的完成数据获取和绑定的工作。

学完这节课您将会掌握 dva 的基本入门。


dva

什么是 dva?

dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。

通常在 dva 的项目中,你需要掌握 (6 个 API](dvajs.com/guide/conce… redux 的概念已经很少了,但是在 Umi 项目中,你需要掌握的 API 数是 0。 即一个也不需要掌握。因为在 Umi 中通过约定的方式组织代码,框架自动完成了相应 API 的执行。

比如,在 src/models 中新建文件,就会被自动使用 app.model 绑定到 dva 中。



为什么要用 dva

经过一段时间的自学或培训,大家应该都能理解 redux 的概念,并认可这种数据流的控制可以让应用更可控,以及让逻辑更清晰。

但随之而来通常会有这样的疑问:概念太多,并且 reducer, saga, action 都是分离的(分文件)。


这带来的问题是:

  • 编辑成本高,需要在 reducer, saga, action 之间来回切换
  • 不便于组织业务模型 (或者叫 domain model) 。比如我们写了一个 userlist 之后,要写一个 productlist,需要复制很多文件。

还有一些其他的:

  • saga 书写太复杂,每监听一个 action 都需要走 fork -> watcher -> worker 的流程
  • entry 书写麻烦
  • ...

而 dva 正是用于解决这些问题。


什么时候需要使用 dva

在 react hooks 上线之后。在 Umi 项目中,我们建议轻量的使用 dva。仅仅在以下几种场景下推荐使用 dva:

  1. 父子组件之间的数据互通
  2. 多页面之间的数据传递(即,公共数据)
  3. 非 react 组件的场景



dva 入门课

::: tip 内容来自之前为阿里内部同学准备的入门课。根据 Umi 中的使用情况,对无需关注的概念做了删减。 :::


React 没有解决的问题

React 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。

如果开发大应用,还需要解决一个问题。

  • 通信:组件之间如何通信?
  • 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等



通信问题

组件会发生三种通信。

  • 向子组件发消息
  • 向父组件发消息
  • 向其他组件发消息

React 只提供了一种通信手段:传参。对于大应用,很不方便。



组件通信的例子

步骤 1

class Son extends React.Component {
  render() {
    return <input />;
  }
}
class Father extends React.Component {
  render() {
    return (
      <div>
        <Son />
        <p>这里显示 Son 组件的内容</p>
      </div>
    );
  }
}
ReactDOM.render(<Father />, mountNode);
复制代码

看这个例子,思考一下父组件如何拿到子组件的值。


步骤 2

class Son extends React.Component {
  render() {
    return <input onChange={this.props.onChange} />;
  }
}
class Father extends React.Component {
  constructor() {
    super();
    this.state = {
      son: '',
    };
  }
  changeHandler(e) {
    this.setState({
      son: e.target.value,
    });
  }
  render() {
    return (
      <div>
        <Son onChange={this.changeHandler.bind(this)} />
        <p>这里显示 Son 组件的内容:{this.state.son}</p>
      </div>
    );
  }
}
ReactDOM.render(<Father />, mountNode);
复制代码

看下这个例子,看懂源码,理解子组件如何通过父组件传入的函数,将自己的值再传回父组件。


数据流图

image.png


核心概念

  • State:一个对象,保存整个应用状态
  • View:React 组件构成的视图层
  • Action:一个对象,描述事件
  • connect 方法:一个函数,绑定 State 到 View
  • dispatch 方法:一个函数,发送 Action 到 State


State 和 View

State 是储存数据的地方,收到 Action 以后,会更新数据。

View 就是 React 组件构成的 UI 层,从 State 取数据后,渲染成 HTML 代码。只要 State 有变化,View 就会自动更新。


Action

Action 是用来描述 UI 层事件的一个对象。

{
  type: 'click-submit-button',
  payload: this.form.data
}
复制代码


connect 方法

connect 是一个函数,绑定 State 到 View。也支持高阶函数的用法。

import { connect } from 'dva';
function mapStateToProps(state) {
  return { todos: state.todos };
}
connect(mapStateToProps)(App);
复制代码


connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。

connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。


dispatch 方法

dispatch 是一个函数方法,用来将 Action 发送给 State。

dispatch({
  type: 'click-submit-button',
  payload: {},
});
复制代码


dispatch 方法从哪里来?被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。

connect 的数据从哪里来? connect 方法传入的第一个参数是 mapStateToProps 函数,该函数默认传入一个参数 state 对应了整个应用的 state,你可以通过设置映射关系,将 state 中的某个值,绑定到页面组件的 props 里面。



数据流图

image.png


model 最简结构

export default {
  namespace: 'count',
  state: 0,
  reducers: {
    add(state) {
      return state + 1;
    },
  },
  effects: {
    *addAfter1Second(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'add' });
    },
  },
};
复制代码


Model 对象的属性

  • namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
  • state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
  • reducers: Action 处理器,处理同步动作,用来算出最新的 State
  • effects:Action 处理器,处理异步动作


Reducer

Reducer 是 Action 处理器,用来处理同步操作,可以看做是 state 的计算器。它的作用是根据 Action,从上一个 State 算出当前 State。

一些例子:

// count +1
function add(state) {
  return state + 1;
}
// 往 [] 里添加一个新 todo
function addTodo(state, action) {
  return [...state, action.payload];
}
// 往 { todos: [], loading: true } 里添加一个新 todo,并标记 loading 为 false
function addTodo(state, action) {
  return {
    ...state,
    todos: state.todos.concat(action.payload),
    loading: false,
  };
}
复制代码


Effect

Action 处理器,处理异步动作,基于 Redux-saga 实现。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是输入输出操作,全局 dom 变化,访问服务端数据等。

function* addAfter1Second(action, { put, call }) {
  yield call(delay, 1000);
  yield put({ type: 'add' });
}
复制代码


Generator 函数

Effect 是一个 Generator 函数,内部使用 yield 关键字,标识每一步的操作(不管是异步或同步)。


call 和 put

dva 提供多个 effect 函数内部的处理函数,比较常用的是 callput

  • call:执行异步函数
  • put:发出一个 Action,相当于 View 里面的 dispatch

到这里,我们就将 dva 的基本概念讲解清楚了,如果你不是很理解,建议你多看几遍。如果你稍微有了一点概念,那就可以在后续的课程中慢慢掌握以上所有概念。这样你会更加清楚的了解到如何在项目中使用 dva。

感谢阅读,今天不需要写任何的代码,只需要简单的搞懂 dva 的概念即可。我们会在后续手动创建 dva 插件来完成上述提到的,为什么 dva 中的 6 个概念,正在的业务开发中可以一个都不用掌握。

目录
相关文章
|
2天前
|
JavaScript 前端开发 算法
Vue.js的单向数据流:让你的应用更清晰、更可控
Vue.js的单向数据流:让你的应用更清晰、更可控
|
9月前
|
资源调度 前端开发 API
用 React 构建可复用的设计系统(一)
用 React 构建可复用的设计系统
83 0
|
8月前
|
JavaScript 数据安全/隐私保护 网络架构
Vue Router最佳实践,以确保你的Vue.js应用的路由管理清晰、可维护和高效
以下是使用Vue Router的最佳实践,以确保你的Vue.js应用的路由管理清晰、可维护和高效。
114 0
|
9月前
|
JavaScript 前端开发 中间件
Redux原理及工作流程
Redux原理及工作流程
85 0
|
9月前
|
前端开发 API
用 React 构建可复用的设计系统(二)
用 React 构建可复用的设计系统
69 0
|
9月前
|
资源调度 JavaScript 前端开发
Vue 3 状态管理进阶:使用 Pinia 构建可扩展的应用程序
Vue 3 状态管理进阶:使用 Pinia 构建可扩展的应用程序
154 0
|
11月前
|
前端开发
前端学习笔记202304学习笔记第十二天-vue3.0-总结组件之间数据共享方案
前端学习笔记202304学习笔记第十二天-vue3.0-总结组件之间数据共享方案
40 0
|
12月前
|
设计模式 前端开发 JavaScript
组件库设计 | React组件库Concis开源探索过程中的一些心路历程
本文可能无法从细节层面教会你如何做好一个开源组件库,作者也在不断探索和学习,但是也许会对你有所启发。这篇文章既是分享,也是记录,在写这篇文章的此刻,已经是作者一拍脑袋要做一个开源项目将近半年时间了。半年前作者对于如何开发一个组件库一无所知,对于开源项目也是了解甚少,抱着什么不会学什么的态度,独自一人踏上了开源之旅。
175 2
组件库设计 | React组件库Concis开源探索过程中的一些心路历程
|
前端开发
react实战笔记139:使用RTK构建store1
react实战笔记139:使用RTK构建store1
82 0
react实战笔记139:使用RTK构建store1
|
前端开发
react实战笔记138:引入RTK数据
react实战笔记138:引入RTK数据
75 0
react实战笔记138:引入RTK数据