Observable模式的MVVM让Vue在中小型Web应用中有天然的优势,但随着Vue的流行度日益增长,Vue在大型项目中的运用略显捉襟见肘。显然在高复杂度项目中,类型检查已成必需要素,而Vue2在TypeScript的类型检查支持又不够彻底,更重要的是Vuex的状态逻辑的模块化设计欠缺。
所以这里提出以下解决方案:
- 业务逻辑模块化 - 将解决模块化这个重要问题
- TypeScript - vue-cli3
- TSX - 更好的模版类型推导
- 依赖注入 - 目前最好的DI库inversify
- 分包 - 使用lerna构建Monorepo
lerna初始化后,进行领域驱动设计,得到大的领域模块。在必要情况下,将可以进行分包,同时启用动态import懒加载或者是RequireJS等模块加载器,以提高构建时性能和运行性能。
在核心应用子项目的初始化使用vue-cli3建构,选择TypeScript作为主要语言,它将自动引入Webpack的ts-loder。
这是核心目录结构:
|-- App.vue|-- main.ts+-- modules/ |-- Todos/ |-- Navigation/ |-- Portal/ |-- Counter/ ...+-- lib/ |-- loader.ts |-- moduleConnect.ts ...+-- components/...复制代码
main.ts
是默认的entry。
// ...// omit some modulesimport { load } from './lib/loader';const { portal, app,} = load({ bootstrap: 'Portal', modules: { Counter, Todos, Portal, Navigation }, main: App, components: { home: { screen: TodosView, path: '/', module: 'todos', }, counter: { screen: CounterView, path: '/counter', module: 'counter', }, }});Vue.prototype.portal = portal;new Vue(app).$mount("#app");复制代码
App.vue
是主视图文件。
复制代码
modules
包含全部的业务逻辑,也包括视图层状态和导航模块等,它将由Vuex来启动。 例如以下是Counter模块:
import { injectable } from "inversify";import Module, { state, action, computed } from "../../lib/baseModule";@injectable()export default class Counter extends Module { @state count: number = 0; @action calculate(num: number, state?: any) { state.count += num; } getViewProps() { return { count: this.count, calculate: (num: number) => this.calculate(num) } }}复制代码
lib/loader.ts
是应用配置加载器。
import { Container } from 'inversify';export function load(parmas: any = {}) { const { bootstrap, modules, ...option } = parmas; const container = new Container({ skipBaseClassChecks: true }); Object.keys(modules).forEach(key => { container.bind(key).to(modules[key]); }); container.bind("AppOptions").toConstantValue(option); const portal: any = container.get(bootstrap); portal.bootstrap(); const app = portal.createApp(); return { portal, app, };}复制代码
lib/moduleConnect.ts
是ViewModule的View连接器。 这是一个高阶组件的连接器。
import { Component, Vue } from "vue-property-decorator";export default (ViewContainer: any, module: string) => { @Component({ components: { ViewContainer } }) class Container extends Vue { get module() { return this.portal[module]; } render(createElement: any) { const props = this.module.getViewProps(); return createElement(ViewContainer, { props }) } } return Container;}复制代码
components/Counter/index.tsx
是Counter的组件。
import { Component, Vue, Prop } from "vue-property-decorator";import './style.scss';type Calculate = (sum: number) => void;@Componentexport default class CounterView extends Vue { @Prop() count!: number; @Prop(Function) calculate!: Calculate; render(){ return ({this.count}) }}复制代码
配合TSX的View组件模块,同时基于此架构等整体设计将很大程度上契合TypeScript的类型检查和推导。
在该Vue架构中最核心的设计部分应该是usm-vuex
,它让Vuex进行业务模块化变得简单明了,配合View层的ViewModule,它能够让当前的架构设计变得高内聚低耦合,在复用性与维护性上大大提高,同时配合依赖注入(dependency injection),让模块间的依赖变得清晰易懂。对于现实中的大型应用还有很多其他细节部分待完善,这里就不敷述了。
usm-vuex
Repo:
本文的架构完整Demo: