利用 Dawn 工程化工具实践 MobX 数据流管理方案

简介: 项目在最初应用 MobX 时,对较为复杂的多人协作项目的数据流管理方案没有一个优雅的解决方案,通过对[MobX官方文档](https://mobx.js.org/best/store.html)中针对大型可维护项目最佳实践的学习和应用,把自己的理解抽象出一个简单的todoMVC应用,供大家交流和讨论。

搭建开发环境

安装Dawn

要求 Node.js v7.6.0 及以上版本。
$ [sudo] npm install dawn -g

初始化工程

$ dawn init -t front

这里我选择使用无依赖的 front 模板,便于自定义我的前端工程。

目录结构分析

由 dawn 工具生成的项目目录如下:

.
├── .dawn # dawn 配置文件
├── node_modules
├── src
│   ├── assets
│   └── index.js
├── test # 单元测试
├── .eslintrc.json
├── .eslintrc.yml
├── .gitignore
├── .npmignore
├── README.md
├── package.json
├── server.yml
└── tsconfig.json

其中我们重点需要关注的是 src 目录,其中的 index.js 就是我们项目的入口文件。

安装依赖

"devDependencies": {
  "react": "^15.6.1",
  "react-dom": "^15.6.1"
},
"dependencies": {
  "mobx": "^3.2.2",
  "mobx-react": "^4.2.2",
  // 以下是todoMVC样式模块
  "todomvc-app-css": "^2.1.0",
  "todomvc-common": "^1.0.4"
}

安装好依赖,环境就配置完成了,整个环境搭建过程只需要3步,开箱即用,不需要关注 Webpack 和 ESLint 等开发环境的繁琐配置。当然,Dawn 也完全支持自定义这些工具的配置。

todoMVC with MobX

新的项目目录设计如下:

...
├── src
│   ├── assets # 放置静态文件
│   │   ├── common.less
│   │   ├── favicon.ico
│   │   └── index.html
│   ├── components # 业务组件
│   │   ├── todoApp.js
│   │   ├── todoEntry.js
│   │   ├── todoItem.js
│   │   └── todoList.js
│   ├── index.js # 入口文件
│   ├── models # 数据模型定义
│   │   └── TodoModel.js
│   ├── stores # 数据store定义
│   │   ├── TodoStore.js
│   │   ├── ViewStore.js
│   │   └── index.js
│   └── utils # 工具函数
│       └── index.js
...

其中 MobX 数据流实践的核心概念就是数据模型(Model)和数据储存(Store)。

定义数据模型

数据模型即为 MVVM(Model/View/ViewModel) 中的 Model。早期的前端开发,需求比较简单,大多是基于后端传输的数据去直接填充页面中的“坑位”,没有定义数据模型的意识。但随着前端业务复杂度和数据传输量的不断上升,如果没有数据模型的定义,在多人协作时会让前端系统维护的复杂性和不可控性急剧上升,直观体现就是其它人对数据做改动时,很难覆盖到改动的某个字段会产生的全部影响,直接导致维护的周期和难度不断增加。

定义数据模型有以下好处:

  • 让数据源变的可控,可以清晰的了解到定义字段的含义、类型等信息,是数据的天然文档,对多人协作大有裨益。通过应用面向对象的思想,也可以在模型中定义一些属性和方法供创建出的实例使用。
  • 实现前端数据持久化,单页应用经常会遇到多页面数据共享和实时更新的问题,通过定义数据模型并创建实例,可以避免异步拉取来的数据进行 View 层渲染后就被销毁。

下面是待办事项的数据模型定义:
todoitem.png

import { observable } from 'mobx';
class TodoModel {
  store;
  id;
  @observable title;
  @observable completed;
  /**
   * 创建一个TodoModel实例
   * 用于单个todo列表项的操作
   * @param {object} store 传入TodoStore,获取领域模型状态和方法
   * @param {string} id 用于前端操作的实例id
   * @param {string} title todo项的内容
   * @param {boolean} completed 是否完成的状态
   * @memberof TodoModel
   */
  constructor(store, id, title, completed) {
    this.store = store;
    this.id = id;
    this.title = title;
    this.completed = completed;
  }
  // 切换列表项的完成状态
  toggle = () => {
    this.completed = !this.completed;
  }
  // 根据id删除列表项
  delete = () => {
    this.store.todos = this.store.todos
      .filter(todo => todo.id !== this.id);
  }
  // 设置实例title
  setTitle = (title) => {
    this.title = title;
  }
}
export default TodoModel;

从 TodoModel 的定义中可以清楚的看到一个待办事项拥有的属性和方法,通过这些,就可以对创建出的实例进行相应的操作。但是在实例中只能修改实例自身的属性,怎样才能把待办事项的状态变化通过 viewModel 来渲染到 view 层呢?

定义数据储存

官方文档对数据储存的定义是这样的:

Stores can be found in any Flux architecture and can be compared a bit with controllers in the MVC pattern. The main responsibility of stores is to move logic and state out of your components into a standalone testable unit.

翻译过来是:数据储存(Store)可以在任何 Flux 系架构中找到,可以与 MVC 模式中的控制器(Controller)进行类比。它的主要职责是将逻辑和状态从组件中移至一个独立的,可测试的单元。

也就是说,Store 就是连接我们的 View 层和 Model 层之间的桥梁,即 ViewModel,所有的状态和逻辑变化都应该在 Store 中完成。同一个 Store 不应该在内存中有多个实例,要确保每个 Store 只有一个实例,并允许我们安全地对其进行引用。

下面通过项目示例来更清晰的理解这个过程。

首先是 todoMVC 的数据 Store 定义:

import { observable } from 'mobx';
import { uuid } from '../utils';
import TodoModel from '../models/TodoModel';
class TodoStore {
  // 保存todo列表项
  @observable todos = [];
  // 添加todo,参数为todo内容
  // 注意:此处传入的 this 即为 todoStore 实例的引用
  // 通过引用使得 TodoModel 有了调用 todoStore 的能力
  addTodo(title) {
    this.todos.push(
      new TodoModel(this, uuid(), title, false)
    );
  }
}
export default TodoStore;

需要注意的是,在创建 TodoModel 传入的 this 即为 todoStore 实例的引用,通过这里的引用使得 TodoModel 的实例拥有了调用 todoStore 的能力,这也就是我们要保证数据储存的 Store 只有一个实例的原因。

然后是视图层对数据进行渲染的方式:

import React, { Component } from 'react';
import { computed } from 'mobx';
import { inject, observer } from 'mobx-react';
import TodoItem from './todoItem';
@inject('todoStore')
@observer
class TodoList extends Component {
  @computed get todoStore() {
    return this.props.todoStore;
  }
  render() {
    const { todos } = this.todoStore;
    return (
      <section className="main">
        <ul className="todo-list">
          {todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
        </ul>
      </section>
    );
  }
}
export default TodoList;

我们把这个过程分步来理解:

  • 首先,拿到待办事项的内容(title)和完成状态,通过 TodoModel 创建一个新的待办事项的实例。
  • 其次,在 todoStore 中把每个创建出的 TodoModel 实例填入 todos 数组,用于待办事项列表的渲染。
  • 最后,在视图层中通过 inject 装饰器注入todoStore,从而引用其中的 todos 数组,MobX 会响应数组的变化完成渲染。

如果待办事项的内容和完成状态需要改动,就要修改 Model 中对应的类型属性,然后在 todoStore 中进行相应的加工,最后产出新的视图展示。而在这个过程中,我们只需要把可能会变化的属性定义为可观察的变量,在需要变更的时候进行修改,剩余的工作 MobX 会帮我们完成。

定义用户界面状态

刚才定义的 todoStore 是针对数据储存的,但是对于前端来讲,还有很大一部分工作是 UI 的状态管理。
UI 的状态通常没有太多的逻辑,但会包含大量松散耦合的状态信息,同样可以通过定义 UI Store 来管理这部分状态。

以下是一个 UI Store 的简单定义:

import { observable } from 'mobx';
export default class ViewStore {
  @observable todoBeingEdited = null;
}

这个 Store 只包含一个可观察的属性,用于保存正在编辑的 TodoModal 实例,通过这个属性来控制视图层待办事项的修改:

...
class TodoItem extends Component {

  ...

  edit = () => {
    // 设置 todoBeingEdited 为当前待办事项todo的实例
    this.viewStore.todoBeingEdited = this.todo;
    this.editText = this.todo.title;
  };

  ...

  handleSubmit = () => {
    const val = this.editText.trim();
    if (val) {
      this.todo.setTitle(val);
      this.editText = val;
    } else {
      this.todo.delete();
    }
    // 提交修改后初始化 todoBeingEdited 变量
    this.viewStore.todoBeingEdited = null;
  }

  render() {
    // 根据 todoBeingEdited 和当前 todo 比较的结果判断是否处于编辑状态
    const isEdit = expr(() => 
      this.viewStore.todoBeingEdited === this.todo);
    const cls = [
      this.todo.completed ? 'completed' : '',
      isEdit ? 'editing' : ''
    ].join(' ');
    return (
      <li className={cls}>
        ...
      </li>
    );
  }

}

export default TodoItem;

在视图中对 UI Store 的可观察的属性进行修改,MobX 会收集相应的变化经过处理后响应在视图上。

源码

完整的 todoMVC 代码可以通过以下方式获取:

$ dawn init -t react-mobx

或者在 Github 上查看源码:https://github.com/xdlrt/dn-template-react-mobx

总结

基于 MobX 的数据流管理方案,分为以下几步:

  • 定义数据 Model,使数据源可控并可持久化
  • 定义数据 Store 和 UI Store,创建并管理数据 Model 实例及实例属性的变更
  • 将 Store 注入到视图层,使用其中的数据进行视图渲染,MobX 自动响应数据的变化更新视图

process.jpg

以上是我对 MVVM 框架中使用 MobX 管理数据流的一些理解,同时这种方案也在团队内一个较为复杂的项目中进行实践,目前项目的健壮性和可维护性比较健康,欢迎提出不同的见解,共同交流。

最后再吃我一发安利

Dawn 是「阿里云-业务运营事业部」前端团队开源的前端构建和工程化工具。

它通过封装中间件(middleware) ,如 webpack 和本地 server ,并在项目 pipeline 中按需使用,可以将开发过程抽象为相对固定的阶段和有限的操作,简化并统一开发环境,能够极大地提高团队的开发效率。

项目的模板即工程 boilerplate 也可以根据团队的需要进行定制复用,实现「configure once run everywhere」。
欢迎体验并提出意见和建议,帮助我们改进。Github地址:https://github.com/alibaba/dawn

目录
相关文章
|
5月前
|
移动开发 前端开发 Android开发
mPaaS 常见问题之移动开发平台 mpaas的H5 前端 Kylin 框架引入vant后包特别大如何解决
mPaaS(移动平台即服务,Mobile Platform as a Service)是阿里巴巴集团提供的一套移动开发解决方案,它包含了一系列移动开发、测试、监控和运营的工具和服务。以下是mPaaS常见问题的汇总,旨在帮助开发者和企业用户解决在使用mPaaS产品过程中遇到的各种挑战
203 0
|
5月前
|
缓存 前端开发 JavaScript
Vite 构建流程大揭秘:快速构建前端项目的秘密武器
Vite 构建流程大揭秘:快速构建前端项目的秘密武器
|
2月前
|
前端开发 API 开发者
【前端数据革命】React与GraphQL协同工作:从理论到实践全面解析现代前端数据获取的新范式,开启高效开发之旅!
【8月更文挑战第31天】本文通过具体代码示例,介绍了如何利用 GraphQL 和 React 搭建高效的前端数据获取系统。GraphQL 作为一种新型数据查询语言,能精准获取所需数据、提供强大的类型系统、统一的 API 入口及实时数据订阅功能,有效解决了 RESTful API 在复杂前端应用中遇到的问题。通过集成 Apollo Client,React 应用能轻松实现数据查询与实时更新,大幅提升性能与用户体验。文章详细讲解了从安装配置到查询订阅的全过程,并分享了实践心得,适合各层次前端开发者学习参考。
30 0
|
2月前
|
前端开发 API 开发者
【React状态管理新思路】Context API入门:从零开始摆脱props钻孔的优雅之道,全面解析与实战案例分享!
【8月更文挑战第31天】React 的 Context API 有效解决了多级组件间状态传递的 &quot;props 钻孔&quot; 问题,使代码更简洁、易维护。本文通过电子商务网站登录状态管理案例,详细介绍了 Context API 的使用方法,包括创建、提供及消费 Context,以及处理多个 Context 的场景,适合各水平开发者学习与应用,提高开发效率和代码质量。
25 0
|
2月前
|
缓存 前端开发 JavaScript
"React与GraphQL Apollo Client的神奇之处:如何用高效数据驱动应用让你的项目一鸣惊人?"
【8月更文挑战第31天】在当今的Web开发领域,数据驱动应用已成主流。本文章深入探讨了React——一个用于构建用户界面的流行JavaScript库,与GraphQL及Apollo Client结合使用时如何助力开发者高效创建数据驱动应用。通过示例代码,文章展示了React与GraphQL Apollo Client在实际项目中的应用方法,并总结了其优势及最佳实践,为读者提供了全面的技术指南。
25 0
|
2月前
|
缓存 NoSQL 数据库
【超实用秘籍】FastAPI高手教你如何通过最佳实践构建高效Web应用:从代码组织到异步编程与缓存优化的全方位指南!
【8月更文挑战第31天】FastAPI凭借出色性能和易用性成为现代Web应用的首选框架。本文通过示例代码介绍构建高效FastAPI应用的最佳实践,包括开发环境搭建、代码模块化组织、异步编程及性能优化等。通过模块化设计和异步数据库操作,结合缓存技术,大幅提升应用性能与可维护性,助您轻松应对高并发场景。
43 0
|
2月前
|
前端开发 JavaScript 中间件
【前端状态管理之道】React Context与Redux大对决:从原理到实践全面解析状态管理框架的选择与比较,帮你找到最适合的解决方案!
【8月更文挑战第31天】本文通过电子商务网站的具体案例,详细比较了React Context与Redux两种状态管理方案的优缺点。React Context作为轻量级API,适合小规模应用和少量状态共享,实现简单快捷。Redux则适用于大型复杂应用,具备严格的状态管理规则和丰富的社区支持,但配置较为繁琐。文章提供了两种方案的具体实现代码,并从适用场景、维护成本及社区支持三方面进行对比分析,帮助开发者根据项目需求选择最佳方案。
22 0
|
2月前
|
存储 API 数据库
深入探索DDD与事件溯源:使用Entity Framework Core构建高效且可维护的领域驱动设计应用——从理论到实践的全方位指南,附带代码示例与最佳实践分享
【8月更文挑战第31天】本文通过实例介绍如何结合领域驱动设计(DDD)与事件溯源(Event Sourcing)及 Entity Framework Core(EF Core),构建高效且可维护的应用程序。DDD 强调业务逻辑与软件设计的紧密融合,而事件溯源则通过记录所有变更事件来重建状态。文章详细展示了创建基于 EF Core 的项目、配置数据库上下文、定义领域模型与事件,并存储和提交事件的具体步骤。通过这些技术,实现了复杂业务逻辑的持久化与重构,提高了应用程序的灵活性和扩展性。
41 0
|
5月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的商业辅助决策系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的商业辅助决策系统的详细设计和实现(源码+lw+部署文档+讲解等)
|
5月前
|
存储 JavaScript 前端开发
下一篇
无影云桌面