资讯专栏INFORMATION COLUMN

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

0x584a / 642人阅读

摘要:新的项目目录设计如下放置静态文件业务组件入口文件数据模型定义数据定义工具函数其中数据流实践的核心概念就是数据模型和数据储存。最后再吃我一发安利是阿里云业务运营事业部前端团队开源的前端构建和工程化工具。

本文首发于阿里云前端dawn团队专栏。

项目在最初应用 MobX 时,对较为复杂的多人协作项目的数据流管理方案没有一个优雅的解决方案,通过对MobX官方文档中针对大型可维护项目最佳实践的学习和应用,把自己的理解抽象出一个简单的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 层渲染后就被销毁。

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

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 (
      
    {todos.map(todo => )}
); } } 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 (
      
  • ...
  • ); } } export default TodoItem;

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

    源码

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

    $ dawn init -t react-mobx

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

    总结

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

    定义数据 Model,使数据源可控并可持久化

    定义数据 Store 和 UI Store,创建并管理数据 Model 实例及实例属性的变更

    将 Store 注入到视图层,使用其中的数据进行视图渲染,MobX 自动响应数据的变化更新视图

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

    最后再吃我一发安利

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

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

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

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/89390.html

    相关文章

    • 3秒钟快速搭建一个react多页应用

      摘要:本文主要阐述了如何使用快速搭建一个多页面应用。而我司当前的情况比较适合于使用多页面应用,既提高了开发效率,保证了用户体验,又极大的兼容了原有的体系。当然也可以不复制不修改,此时就是一个单页面应用。 what 本文主要阐述了如何使用dawn快速搭建一个多页面应用。 why 单页有许多优缺点。而我司当前的情况比较适合于使用多页面应用,既提高了开发效率,保证了用户体验,又极大的兼容了原有的P...

      h9911 评论0 收藏0
    • 阿里云前端周刊 - 第 31 期

      摘要:发布按照官方发布计划,的发布意味着进入阶段,彻底退出舞台,的还有半年结束。为了应对这个挑战,美团点评境外度假前端研发团队自年月起启动了面向端用户的赫尔墨斯项目。前端技术越来越复杂,有不低的技术门槛。 推荐 1. 利用 Dawn 工程化工具实践 MobX 数据流管理方案 https://zhuanlan.zhihu.com/p/... 项目在最初应用 MobX 时,对较为复杂的多人协作项...

      madthumb 评论0 收藏0
    • 阿里云前端周刊 - 第 24 期

      摘要:版本发布近日发布的版本中引入了许多新的特性,并且能够更好地与协同开发。阿里云前端工程化工具正式开源取黎明破晓之意,原为阿里云业务运营团队内部的前端构建和工程化工具,现已完全开源。 推荐 1. Firefox 引入 Headless 模式 https://developer.mozilla.org... 类似于 Chrome 的 Headless 模式,现在 Firefox 也引入了 H...

      lncwwn 评论0 收藏0
    • ? 阿里云前端程化工具 Dawn 正式开源!

      摘要:取黎明破晓之意,原为阿里云业务运营团队内部的前端构建和工程化工具,现已完全开源。它通过和将开发过程抽象为相对固定的阶段和有限的操作,简化并统一了开发人员的日常构建与开发相关的工作。 showImg(https://segmentfault.com/img/remote/1460000011006491); Dawn Dawn 取「黎明、破晓」之意,原为「阿里云·业务运营团队」内部的前端...

      陆斌 评论0 收藏0
    • 前端每周清单半年盘点之 React 与 ReactNative 篇

      摘要:前端每周清单半年盘点之与篇前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。与求同存异近日,宣布将的构建工具由迁移到,引发了很多开发者的讨论。 前端每周清单半年盘点之 React 与 ReactNative 篇 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为...

      Barry_Ng 评论0 收藏0

    发表评论

    0条评论

    最新活动
    阅读需要支付1元查看
    <