MobX + PureMVVM + YuriJS

1,332 阅读5分钟

来源:github.com/meidengtech…

为什么要用MobX

参考: Introduce MobxMobX介绍(中文版)

  • 核心概念很简单
  • 强大的 MVVM 模式
  • 可复用的 MVVM 方法/类(译注:这是 MobX 和许多常见 MVVM 框架的核心区别)
  • 非常快:叶端组件级别的重绘

可复用的 MVVM 方法/类

复用一些公共的 MVVM 模式 非常重要。

mobx-utils里有许多可被复用的 MVVM 模式。

你还可以自己实现更多的通用模式并复用它们,例如:

  • 实现一个通用的分页请求数据的模式。 参见 例子
  • 实现一个通用的使特定目标可以被鼠标/触摸事件移动(基于 onMouseDown/onTouchDown 事件)的模式。参加 例子
  • 实现一个通用的模式来完成“撤销/重做”功能。
  • 实现一个通用的模式,可以基于 json-ot 来完成对任意JSON对象的协同编辑。

PureMVVM (by @tdzl2003)

  • Pure Model 表示模型层是 朴素的可监听对象(Plain Observable Object)
    • 意思是在mobx中 observable(jsonObject)
  • Pure View 表示视图层:
    • 没有任何状态
    • 没有任何动作(Action)
  • Pure ViewModel 表示视图模型层:
    • 和视图层一一对应

不过为啥要这么做?

因为:

  • Pure Model 使得模型层的数据非常容易序列化/反序列化,还可以更容易的:
    • 存储历史状态
    • 使用 IDL 来和后端/微服务共享模型设计,不需要直接用 JavaScript/TypeScript 手写模型定义。
    • 很容易配合 json-ot 实现协同编辑
  • Pure View 使得视图层可以使用模板来定义,从而使代码:
    • 更简单,更干净
    • 更少bug
  • Pure ViewModel 使得你的逻辑代码:
    • 更容易被组织

不过这会不会导致视图模型层变得巨大?

有可能,因为按这个定义,视图模型层包含“所有的逻辑代码”,不过你可以:

  • 对于共享状态使用全局的Store定义。
  • 使用可复用的MVVM模式来大量减少代码。
  • 把大的“页面”拆分成多个“挂件”,每个“挂件”拥有自己的视图层和视图模型层。
  • 使用#region/子对象来拆解代码,不过对一个视图只暴露一个根的视图模型类。

YuriJS

纪念尤里·加加林:太空第一人

YuriJS = Vue模板 + Mobx + React

YuriJS 提供了一个把 Vue 模板编译成 React 组件的编译器,使用基于 Mobx 的视图模型层来完成数据绑定。

index.template

<div class="root">
  Hello, @yurijs!
  <div>
    <button @click="++counter">+</button>
    {{counter}}
    <button @click="--counter">-</button>
  </div>
</div>

index.vm.ts

import { makeObservable, observable } from 'mobx';

export default class CounterViewModel {
  constructor() {
    makeObservable(this);
  }

  @observable
  counter = 0;
}

index.css

.root {
  display: flex;
  flex-direction: column;
}

为啥要使用 YuriJS?

  • 兼容 Vue 的模板
  • 与 React 兼容,对 React 组件和生态友好
    • 兼容 AntD 或者任何其他 React 组件库
  • 可监听的属性,元素级别的更新
    • 所以你可以写一个非常的模板,还保证很好的更新效率。
  • 更少的代码: 不需要再写 useCallback/useMemo 了。
  • 开发时支持模板热加载,同时保持状态不变。
  • 小,只有很薄一层,简单,可替换
    • 这就是 React 下实现 Vue 模板,随时都可以替换成原始的 React 实现。

我们未来甚至还会提供一个 Vue 模板到小程序/React Native的编译器,来在不同平台下运行 YuriJS 应用。(译注:类似 TaroJS 所完成的事情,不过因为基于 MobX 和模板,会比 TaroJS 要薄很多)

在 webpack 下使用:

安装依赖:

npm install --save-dev \
    @yuri/template-loader \
    @yuri/hmr-template-loader
npm install --save \
    @yuri/runtime \
    @yuri/html \
    classnames mobx mobx-react-lite
# or with yarn:
yarn add --dev \
    @yuri/template-loader \
    @yuri/hmr-template-loader
yarn add \
    @yuri/runtime \
    @yuri/html \
    classnames mobx mobx-react-lite
# 如果你使用自己的组件库,可以不必安装 @yuri/html 。

在 Webpack 的 module/rules 下增加如下的规则: (译注:DEV 表示是否开发模式,你可能需要自己考虑如何判断。使用 argv.mode === 'development' 是一种办法。)

  module.exports = {
    module: {
      rules: [
        // Add following lines:
        {
          test: /\.template$/,
          use: (__DEV__
            ? [
                {
                  loader: '@yurijs/hmr-template-loader',
                },
              ]
            : []
          ).concat([
            {
              loader: '@yurijs/template-loader',
              options: {
                cssModules: true,
              },
            },
          ]),
        },
        ...
      ]
    }
  }

选项:

  • cssModules(boolean, default=false): 是否启用 CSS Modules。
  • styleExtension(string, default=.css): 默认配套样式文件的扩展名(.less/.scss)。
  • defaultNS(string, default=@yurijs/html): 配置默认加载的组件库(默认是全部html/svg标签)。

额外引入组件

可以在模板文件中使用声明标签额外引入组件:

<!-- 作为命名空间引用 -->
<import module="antd" />
<antd:Button>Click Me</antd:Button>

<!-- 作为命名空间引用,并重命名 -->
<import module="antd" as="extra" />
<extra:Button>Click Me</extra:Button>

<!-- 直接引用组件 -->
<import module="antd" name="Button,Input" />
<button>Click Me</button>

<import module="../components" name="Button" />
<button>Click Me</button>

结合 TypeScript使用

你可以另外声明一个 '.d.ts' 文件来支持在TypeScript中使用 YuriJS 导出的组件

import { ComponentType } from 'react';

export interface HomeProps {}

declare const Home: ComponentType<HomeProps>;

export default Home;

VSCode 语法高亮

  • 安装 Vue 官方扩展: vetur
  • 打开一个 .template 文件
  • CtrlOrCmd+Shift+P -> 更改语言模式 -> '.template'的配置文件关联 -> Vue-html

怎么运行例子

# 安装依赖
yarn

# 构建runtime和html两个项目
yarn build

# 用webpack构建例子
cd examples/simple
yarn start

目前的限制

  • 组件兼容
    • v-model 目前不支持各种不统一的 onXXXChange ( input/checkbox 等都需要进行包装)
    • 基于 Children 进行逻辑计算 (例如某些组件库的 Tab/Select 等)
    • Renderer 属性 (例如某些组件库的 Table/VirtualizedList 等)
    • 暂时不支持导入default导出
    • 暂时不支持导出命名
  • Vue-HTML 特性支持
    • 事件修饰符
    • 非值处理策略(译注:Vue 展示 false 时会显示 false 文本,而 React 则会过滤掉false/null/undefined 等值)
    • 不一样的导入语法
  • 开发者体验:
    • 组件生命周期错误会导致热加载失效
    • 缺少格式化工具
    • 缺少自动完成支持