Skip to content

快速开始

简介

@kaokei/use-vue-service 是一个轻量级的适用于 Vue 3 项目的状态管理库。当前版本为 4.0.0,依赖 @kaokei/di 版本 ^5.0.4

本库基于 @kaokei/di 开发,支持依赖注入能力。

本库的灵感来源于 Angular Service,优势是管理数据的方式更加灵活。

安装

sh
npm install @kaokei/di @kaokei/use-vue-service

本库 不依赖 reflect-metadata,所以 不需要 安装这个 npm 包。

本库使用 TC39 Stage 3 标准装饰器语法,TypeScript 5.0+ 默认支持,无需tsconfig.json 中配置 experimentalDecorators

关于装饰器

旧版本需要配置 "experimentalDecorators": true,4.0.0 版本已迁移到 Stage 3 装饰器,不再需要任何额外配置。

基本使用

ts
// service.ts —— 定义了 2 个服务,LoggerService 和 CountService
// 其中 CountService 依赖 LoggerService
// @Inject 装饰器来自 @kaokei/di,通过 @kaokei/use-vue-service 重新导出
import { Inject } from '@kaokei/use-vue-service';

export class LoggerService {
  public log(...msg: any[]) {
    console.log('from logger service ==>', ...msg);
  }
}

export class CountService {
  public count = 0;

  @Inject(LoggerService)
  public accessor logger: LoggerService;

  public addOne() {
    this.count++;
    this.logger.log('addOne ==> ', this.count);
  }
}
vue
<script lang="ts" setup>
// 这个组件使用了 service.ts 中定义的服务
import { declareProviders, useService } from '@kaokei/use-vue-service';
import { CountService, LoggerService } from './service.ts';
// 将 CountService、LoggerService 和当前组件进行绑定
declareProviders([CountService, LoggerService]);
// 实例化 CountService 得到一个 countService 对象
// countService 对象自动注入了 logger 属性,是一个 LoggerService 实例
const countService = useService(CountService);
</script>

<template>
  <div>{{ countService.count }}</div>
  <button @click="countService.addOne()">点击按钮+1</button>
</template>

关于 @Inject

@Inject 装饰器来自 @kaokei/di,本库通过 export * from '@kaokei/di' 重新导出,因此可以直接从 @kaokei/use-vue-service 导入使用。

三组核心 API

本库提供三组 API,分别对应三种不同的服务作用域。每组包含一个"声明"函数和一个"获取"函数。

组件级:useService / declareProviders

组件级作用域是最常用的方式。容器通过 Vue 的 provide/inject 机制在组件树中传递。

  • declareProviders(providers) —— 在当前组件创建一个子容器(继承父级容器),组件卸载时自动销毁。
  • useService(token) —— 从当前组件或最近的祖先组件的容器中获取服务实例。
ts
import { declareProviders, useService } from '@kaokei/use-vue-service';
import { CountService } from './service.ts';

// 在组件的 setup 中调用
declareProviders([CountService]);
const countService = useService(CountService);

全局根级:useRootService / declareRootProviders

全局根级作用域直接操作全局的根容器,不依赖 Vue 组件树,可以在任何地方调用。

  • declareRootProviders(providers) —— 在全局根容器上声明服务提供者,绑定的服务在整个应用中全局共享。
  • useRootService(token) —— 从全局根容器中获取服务实例。
ts
import { declareRootProviders, useRootService } from '@kaokei/use-vue-service';
import { GlobalConfigService } from './service.ts';

declareRootProviders([GlobalConfigService]);
const config = useRootService(GlobalConfigService);

App 级:useAppService / declareAppProviders / declareAppProvidersPlugin

App 级作用域通过 app.runWithContext 在指定 Vue App 实例的上下文中操作容器,适用于多 App 实例场景。

  • declareAppProviders(providers, app) —— 在指定 App 实例的上下文中声明服务提供者。
  • useAppService(token, app) —— 在指定 App 实例的上下文中获取服务实例。
  • declareAppProvidersPlugin(providers) —— 返回一个 Vue 插件,可直接用于 app.use()
ts
import { createApp } from 'vue';
import { declareAppProvidersPlugin } from '@kaokei/use-vue-service';
import { AppService } from './service.ts';
import App from './App.vue';

const app = createApp(App);
// 以 Vue 插件形式声明 App 级服务
app.use(declareAppProvidersPlugin([AppService]));
app.mount('#app');

装饰器

除了从 @kaokei/di 重新导出的 @Inject@PostConstruct 等装饰器外,本库还提供以下三个与 Vue 响应式系统集成的装饰器。

@Computed

将 getter 属性转换为 Vue computed 响应式计算属性。支持 @Computed@Computed() 两种用法。采用懒创建策略——首次访问时才创建 ComputedRef。如果原型链上存在同名 setter,则自动创建 writable computed。

ts
import { Computed } from '@kaokei/use-vue-service';

class UserService {
  public firstName = '张';
  public lastName = '三';

  @Computed
  public get fullName() {
    return this.firstName + this.lastName;
  }
}

@Raw

标记属性不参与 Vue 响应式追踪。当服务实例被 reactive() 包裹后,被 @Raw 装饰的属性值会自动调用 markRaw,确保该属性永远不会被 Vue 的响应式系统代理。支持 @Raw@Raw() 两种用法,支持普通 field 和 auto-accessor 两种装饰目标。

适用于复杂的第三方 SDK 对象(如 ECharts 实例、Monaco Editor 实例等),避免转为响应式导致的性能问题或功能异常。

ts
import { Raw } from '@kaokei/use-vue-service';

class ChartService {
  // 普通 field 用法
  @Raw
  public chartInstance: any = null;

  // auto-accessor 用法
  @Raw
  accessor editorInstance: any = null;
}

@RunInScope

EffectScope 中运行方法,自动管理副作用生命周期。每次调用被装饰方法时,会在实例的根 Scope 内创建一个新的子 Scope,并在子 Scope 中执行方法体,返回该子 Scope 供用户管理。支持 @RunInScope@RunInScope() 两种用法。

ts
import { RunInScope } from '@kaokei/use-vue-service';
import { watchEffect } from 'vue';
import type { EffectScope } from 'vue';

class TimerService {
  public count = 0;

  @RunInScope
  public startWatch(): EffectScope {
    watchEffect(() => {
      console.log('count 变化了:', this.count);
    });
    return null as any; // 占位,实际返回值由装饰器接管
  }
}

// 使用时
const scope = timerService.startWatch() as unknown as EffectScope;
// 需要清理时
scope.stop();

Token 常量

本库导出两个预定义的 Token 常量,用于在父子组件之间查找服务实例。

FIND_CHILD_SERVICE

FIND_CHILD_SERVICE 是一个 Token<FindChildService> 实例。通过该 Token 获取的函数可以根据指定的 token 查找子组件中声明的单个服务实例。如果未找到则返回 undefined

ts
import { useService, FIND_CHILD_SERVICE } from '@kaokei/use-vue-service';

const findChild = useService(FIND_CHILD_SERVICE);
const childService = findChild(SomeService);

FIND_CHILDREN_SERVICES

FIND_CHILDREN_SERVICES 是一个 Token<FindChildrenServices> 实例。通过该 Token 获取的函数可以根据指定的 token 查找子组件中声明的所有匹配服务实例,返回一个数组。

ts
import { useService, FIND_CHILDREN_SERVICES } from '@kaokei/use-vue-service';

const findChildren = useService(FIND_CHILDREN_SERVICES);
const allChildServices = findChildren(SomeService);