惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cybersecurity and Infrastructure Security Agency CISA
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Latest news
Latest news
L
LINUX DO - 热门话题
Cisco Talos Blog
Cisco Talos Blog
S
Securelist
T
Threatpost
AWS News Blog
AWS News Blog
P
Privacy & Cybersecurity Law Blog
C
CERT Recently Published Vulnerability Notes
B
Blog RSS Feed
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
T
Tor Project blog
P
Palo Alto Networks Blog
博客园 - 三生石上(FineUI控件)
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
H
Help Net Security
小众软件
小众软件
C
Cisco Blogs
有赞技术团队
有赞技术团队
Cyberwarzone
Cyberwarzone
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
S
Schneier on Security
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
The Register - Security
The Register - Security
Project Zero
Project Zero
Hugging Face - Blog
Hugging Face - Blog
The Cloudflare Blog
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
爱范儿
爱范儿
A
About on SuperTechFans
T
The Exploit Database - CXSecurity.com
P
Privacy International News Feed
A
Arctic Wolf
大猫的无限游戏
大猫的无限游戏
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
K
Kaspersky official blog
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
H
Hackread – Cybersecurity News, Data Breaches, AI and More

一颗小树

周更的第 100 篇 改变阅读方式 宝贵的人生建议 小树的工具库 2023 读《重构》有感 仅需 10 分钟,用 GPTs 实现文章总结助手 高质量的需求交付 产品始于问题,而不是解决方案 如何更好地休息 最优解人生 与体重斗,其乐无穷 结构化 prompt = 数字员工? 持续创作的法门 如何降低知识焦虑 7 月思维碎片 N 倍生产力提升:我的 AI 助理 Indie Hacker,互联网打工人的下一个出路? 如何成就伟大事业 夏日碎片 小报编辑的自我修养 一颗小树 #49 投入真实生活 一颗小树 #48 过年杂记 一颗小树 #47 我的人生信念(2023) 一颗小树 #46 回本就卖 一颗小树 #45 规划和落地 小树的 2022 年终总结 一颗小树 #44 构建高质量信源 一颗小树 #43 我的 2022 书单 一颗小树 #42 阳了怎么办 一颗小树 #41 一人企业 一颗小树 #40 非必要别正常啦 一颗小树 #39 阅读和创造 一颗小树 #38 疫情生活碎片 一颗小树 #37 基于多维表格的可转债投资系统 一颗小树 #36 飞盘初体验 一颗小树 #35 雁栖湖半日游 一颗小树 #34 初探多维表格 一颗小树 #33 回不去的名字叫家乡 一颗小树 #32 生活需要留白 一颗小树 #31 Figma 的成功之道 一颗小树 #30 拓宽自己 拥抱善意 一颗小树 #29 有关下雨的记忆 一颗小树 #28 隐入尘烟 一颗小树 #27 找到并坚持自己的热爱 一颗小树 #26 好产品的“差”改版 一颗小树 #25 吹吹南澳岛的海风 一颗小树 #24 代码重构的一点心得 一颗小树 #23 和团队新同学的对话 一颗小树 #22 从《干法》中学到的 一颗小树 #21 阿里四年我的收获和感想 一颗小树 #20 周更半年我学到的事 一颗小树 #19 慢慢来比较快 一颗小树 #18 只规划每天最重要的三件事 一颗小树 #17 裁员潮下的反思 一颗小树 #16 纪录片的力量在于真实 一颗小树 #15 世界很小但很好 一颗小树 #14 让自己成为问题的终结者 一颗小树 #13 别忘记你的热爱 一颗小树 #12 聊聊写作这件事 一颗小树 #11 开始投资前更重要的事 一颗小树 #10 疫情囤货指南 一颗小树 #9 疫情之下 重建内心的秩序 一颗小树 #8 不要倒在黎明前 一颗小树 #7 从小孩子身上学到的事 一颗小树 #6 和自己的产品一起成长 一颗小树 #5 技术对生产力的的变革是不可阻挡的 一颗小树 #4 暴风雨的意义 一颗小树 #3 投资成功,是我们变成一个更好的人之后自然的结果 一颗小树 #2 持续学习来应对这个复杂多变的世界 一颗小树 #1 任天堂和上岭村 一颗小树 #0 这是写给你们的第一封信 游戏改变世界 把自己给开源了 构建自己的不为清单 有爱和被爱 做了一点微小的工作 小树的 2021 年终总结 小树的 2020 年终总结 与其感慨路难行,不如马上出发 阿里前端也切图?不,人工智能帮你做 如何写好简历,拿到大厂 offer 的敲门砖 任时光匆匆流去 | 2019 年终总结 聊聊在阿里远程办公那点事儿 在 D2 做主持人是怎样一种体验? 提高工作效率的几个方法 有限状态机在 CSS 动画中的应用 「译」React 项目结构和组件命名之道 编写小而美函数的艺术 使用纯粹的JS构建 Web Component 翻译计划-状态管理请三思 高性能 MobX 模式(part 3)- 用例教程 高性能 MobX 模式(part 2)- 响应变化 技术文章写法浅谈 翻译计划-用node.js开发一个可交互的命令行应用 无他,唯手熟尔-前端实习面试题篇 工欲善其事必先利其器-前端实习简历篇 手把手带你用原生js实现css属性的set和get 我们来翻翻元素样式的族谱-getComputedStyle 翻译计划-从sass到PostCSS 把简单做好也不简单-css水平垂直居中
利用Dawn工程化工具实践MobX数据流管理方案
也树 · 2017-10-29 · via 一颗小树

项目在最初应用 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 层渲染后就被销毁。

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

todoitem

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

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

最后再吃我一发安利

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

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

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