利用 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

目录
相关文章
|
3月前
|
存储 前端开发 JavaScript
高效组件的设计与封装之道
本文结合了作者自身碰到的场景来说明如何做好组件设计和封装。
|
测试技术 数据库 安全
带你读《C++代码整洁之道:C++17 可持续软件开发模式实践》之二:构建安全体系
如果想用C++语言编写出易维护的、扩展性良好的以及生命力强的软件,那么,对于所有的软件开发人员、软件设计人员、对现代C++代码感兴趣或想降低开发成本的项目领导者来说,本书都是必需品。如果你想自学编写整洁的C++代码,那么本书也是你需要的。本书旨在通过一些示例帮助各个技术层次的开发人员编写出易懂的、灵活的、可维护的和高效的C++代码。即使你是一名资深的开发工程师,在本书中也可以找到有价值的知识点。
|
4月前
|
缓存 前端开发 JavaScript
"React与GraphQL Apollo Client的神奇之处:如何用高效数据驱动应用让你的项目一鸣惊人?"
【8月更文挑战第31天】在当今的Web开发领域,数据驱动应用已成主流。本文章深入探讨了React——一个用于构建用户界面的流行JavaScript库,与GraphQL及Apollo Client结合使用时如何助力开发者高效创建数据驱动应用。通过示例代码,文章展示了React与GraphQL Apollo Client在实际项目中的应用方法,并总结了其优势及最佳实践,为读者提供了全面的技术指南。
44 0
|
4月前
|
前端开发 API 开发者
【前端数据革命】React与GraphQL协同工作:从理论到实践全面解析现代前端数据获取的新范式,开启高效开发之旅!
【8月更文挑战第31天】本文通过具体代码示例,介绍了如何利用 GraphQL 和 React 搭建高效的前端数据获取系统。GraphQL 作为一种新型数据查询语言,能精准获取所需数据、提供强大的类型系统、统一的 API 入口及实时数据订阅功能,有效解决了 RESTful API 在复杂前端应用中遇到的问题。通过集成 Apollo Client,React 应用能轻松实现数据查询与实时更新,大幅提升性能与用户体验。文章详细讲解了从安装配置到查询订阅的全过程,并分享了实践心得,适合各层次前端开发者学习参考。
44 0
|
4月前
|
前端开发 开发者 C#
深度解析 Uno Platform 中的 MVVM 模式:从理论到实践的全方位指南,助你轻松掌握通过 C# 与 XAML 构建高效可维护的跨平台应用秘籍
【8月更文挑战第31天】本文详细介绍如何在优秀的跨平台 UI 框架 Uno Platform 中实施 MVVM(Model-View-ViewModel)模式,通过一个简单的待办事项列表应用演示其实现过程。MVVM 模式有助于分离视图层与业务逻辑层,提升代码组织性、易测性和可维护性。Uno Platform 的数据绑定机制使视图与模型间的同步变得高效简便。文章通过构造 `TodoListViewModel` 类及其相关视图,展示了如何解耦视图与模型,实现动态数据绑定及命令处理,从而提高代码质量和开发效率。通过这一模式,开发者能更轻松地构建复杂的跨平台应用。
63 0
|
7月前
|
算法 测试技术 数据处理
【C++ 设计思路】优化C++项目:高效解耦库接口的实战指南
【C++ 设计思路】优化C++项目:高效解耦库接口的实战指南
198 5
|
7月前
|
设计模式 移动开发 安全
构建高效Android应用:探究LiveData的优势与实践
【4月更文挑战第7天】随着移动开发技术的不断演进,Android开发者面临着构建更加高效、响应式和可维护应用的挑战。在众多解决方案中,LiveData作为一种观察者模式的实现,已经成为Google推荐的数据持有架构组件。本文将深入探讨LiveData的核心优势,通过具体实例展现其在Android应用中的实现方式,并分析其如何简化UI组件与数据源之间的通信,提升应用的健壮性和可测试性。
|
7月前
|
存储 JavaScript 前端开发
|
存储 前端开发 数据可视化
构建可靠的前端项目,少不了这些必备工具
构建可靠的前端项目,少不了这些必备工具
|
JavaScript 前端开发 中间件
Redux原理及工作流程
Redux原理及工作流程
136 0