From 22d2c31e5a1e3fc87c8a8d8627933dff3512e50d Mon Sep 17 00:00:00 2001 From: agileago Date: Sat, 4 Dec 2021 18:01:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0app=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/index.md | 2 +- example/api/http.ts | 13 +------ example/app.tsx | 41 +++++++++++++++++++++ example/count.service.ts | 15 ++++++++ example/layout/default.layout.tsx | 2 +- example/main.tsx | 26 +------------ example/module/auth/login.view.tsx | 29 +++++++++++++-- example/module/auth/user.service.ts | 30 +++++++++++---- example/module/home/home.view.tsx | 41 ++++++++++++++++++++- example/router/index.ts | 11 ------ example/router/router.service.ts | 21 +++++++++-- src/decorators/computed.ts | 2 +- src/decorators/hook.ts | 2 +- src/decorators/ref.ts | 1 - src/di/index.ts | 6 +-- src/extends/component.ts | 57 +++++++++++++++++------------ src/extends/service.ts | 45 +++-------------------- src/helper.ts | 6 +-- src/index.ts | 4 +- src/type.ts | 12 +++++- 20 files changed, 226 insertions(+), 140 deletions(-) create mode 100644 example/app.tsx create mode 100644 example/count.service.ts delete mode 100644 example/router/index.ts diff --git a/docs/index.md b/docs/index.md index 40e2947..3f8b85d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1 +1 @@ -# Hello VitePress \ No newline at end of file +# vue3 oop \ No newline at end of file diff --git a/example/api/http.ts b/example/api/http.ts index b72d4c0..50a9f8d 100644 --- a/example/api/http.ts +++ b/example/api/http.ts @@ -1,18 +1,7 @@ import axios from 'axios' -import type { App } from 'vue' -import { UserService } from '../module/auth/user.service' export const http = axios.create() -let app: App | undefined = undefined -export function setupHttp(obj: App) { - app = obj -} - -http.interceptors.request.use((config) => { - const userService = app?.getService(UserService) - console.log(userService) - return config -}) +export const HTTP_CLIENT = Symbol() export function login() { return http.get('/login') diff --git a/example/app.tsx b/example/app.tsx new file mode 100644 index 0000000..468e86a --- /dev/null +++ b/example/app.tsx @@ -0,0 +1,41 @@ +import { Component } from '@/di' +import { VueComponent } from '@/index' +import { UserService } from './module/auth/user.service' +import { Button, Col, ConfigProvider, Row } from 'ant-design-vue' +import zhCN from 'ant-design-vue/es/locale/zh_CN' +import { RouterView } from 'vue-router' +import { RouterService } from './router/router.service' +import { http, HTTP_CLIENT } from './api/http' +import { CountService } from './count.service' + +@Component({ + providers: [UserService, RouterService, { provide: HTTP_CLIENT, useValue: http }, CountService], +}) +export class App extends VueComponent { + constructor( + private userService: UserService, + private routerService: RouterService, + private countService: CountService, + ) { + super() + } + render() { + return ( + +

全局服务

+ + + + {this.countService.count} + + + + +
+ ) + } +} diff --git a/example/count.service.ts b/example/count.service.ts new file mode 100644 index 0000000..7a2885f --- /dev/null +++ b/example/count.service.ts @@ -0,0 +1,15 @@ +import { Autobind, Ref, VueService } from '@/index' + +export class CountService extends VueService { + @Ref() count = 0 + + @Autobind() + add() { + this.count++ + } + + @Autobind() + remove() { + this.count-- + } +} diff --git a/example/layout/default.layout.tsx b/example/layout/default.layout.tsx index bcf067b..1dc586e 100644 --- a/example/layout/default.layout.tsx +++ b/example/layout/default.layout.tsx @@ -1,4 +1,4 @@ -import { VueComponent } from '@/extends/component' +import { VueComponent } from '@/index' import { RouterView } from 'vue-router' export default class DefaultLayout extends VueComponent { diff --git a/example/main.tsx b/example/main.tsx index a5f9ac2..88deecd 100644 --- a/example/main.tsx +++ b/example/main.tsx @@ -1,32 +1,8 @@ import '@abraham/reflection' import { createApp } from 'vue' -import { VueComponent } from '@/extends/component' -import { RouterView } from 'vue-router' -import { setupRouter } from './router' -import { Component } from '@/di' -import { UserService } from './module/auth/user.service' import './theme/app.css' import 'ant-design-vue/dist/antd.css' -import { Button, ConfigProvider, Table } from 'ant-design-vue' -import zhCN from 'ant-design-vue/es/locale/zh_CN' -import { setupHttp } from './api/http' - -@Component({ autoResolveDeps: true, globalStore: true }) -class App extends VueComponent { - constructor(private userService: UserService) { - super() - } - render() { - return ( - -

全局服务

- -
- ) - } -} +import { App } from './app' const app = createApp(App) -setupRouter(app) -setupHttp(app) app.mount('#app') diff --git a/example/module/auth/login.view.tsx b/example/module/auth/login.view.tsx index c705f48..851e5c5 100644 --- a/example/module/auth/login.view.tsx +++ b/example/module/auth/login.view.tsx @@ -1,14 +1,21 @@ -import { VueComponent } from '@/extends/component' -import { Component } from '@/di' +import { Component, VueComponent } from '@/index' import { UserService } from './user.service' import { Button, Col, Form, Input, Row } from 'ant-design-vue' import { Ref } from '@/decorators/ref' import { CatchLoading } from '../../common/decorators/catch.decorator' import { Autobind } from '@/helper' +import { CountService } from '../../count.service' +import { SkipSelf } from 'injection-js' -@Component() +@Component({ + providers: [CountService], +}) export default class LoginView extends VueComponent { - constructor(private userService: UserService) { + constructor( + private userService: UserService, + @SkipSelf() private parentCountService: CountService, + private countService: CountService, + ) { super() } @Ref() loading = false @@ -31,6 +38,20 @@ export default class LoginView extends VueComponent { return ( +

全局的状态: {this.parentCountService.count}

+

局部的状态

+ + + + {this.countService.count} + + + +
diff --git a/example/module/auth/user.service.ts b/example/module/auth/user.service.ts index e73deba..d35daa8 100644 --- a/example/module/auth/user.service.ts +++ b/example/module/auth/user.service.ts @@ -1,15 +1,25 @@ -import { VueService } from '@/extends/service' -import { Injectable } from 'injection-js' +import { Hook, Ref, VueService } from '@/index' +import { Inject, Injectable } from 'injection-js' import { RouterService } from '../../router/router.service' -import { Ref } from '@/index' +import { AxiosInstance } from 'axios' +import { HTTP_CLIENT } from '../../api/http' @Injectable() export class UserService extends VueService { - constructor(private routerService: RouterService) { + constructor(private routerService: RouterService, @Inject(HTTP_CLIENT) private httpService: AxiosInstance) { super() + this.guardHttp() this.guardRouter() } - @Ref() token?: string + @Ref() token = '' + + private _requestGuard: number + + guardHttp() { + this._requestGuard = this.httpService.interceptors.request.use((config) => { + return config + }) + } guardRouter() { this.routerService.router.beforeEach(async (to, from) => { @@ -17,8 +27,14 @@ export class UserService extends VueService { if (to.path !== '/login' && !this.token) return { path: '/login' } }) } + + @Hook('BeforeUnmount') + unmount() { + this.httpService.interceptors.request.eject(this._requestGuard) + } + async login(model: any) { - this.token = await new Promise((resolve) => setTimeout(resolve, 3000, 'token')) - this.routerService.router.replace('/') + this.token = await new Promise((resolve) => setTimeout(resolve, 1000, 'token')) + await this.routerService.router.replace('/') } } diff --git a/example/module/home/home.view.tsx b/example/module/home/home.view.tsx index 8e9dc87..8dc222b 100644 --- a/example/module/home/home.view.tsx +++ b/example/module/home/home.view.tsx @@ -1,7 +1,44 @@ -import { VueComponent } from '@/extends/component' +import { Component, VueComponent } from '@/index' +import { CountService } from '../../count.service' +import { Optional, SkipSelf } from 'injection-js' +import { Button, Col, Row } from 'ant-design-vue' +import { watch } from 'vue' +@Component({ + providers: [CountService], +}) export default class HomeView extends VueComponent { + constructor( + @SkipSelf() private parentCountService: CountService, + private countService: CountService, + @Optional() private aaa: string, + ) { + super() + watch( + () => this.parentCountService.count, + () => (countService.count = parentCountService.count), + { + immediate: true, + }, + ) + } render() { - return
home
+ return ( + <> +

全局的状态: {this.parentCountService.count}

+

局部的状态

+ + + + {this.countService.count} + + + + + ) } } diff --git a/example/router/index.ts b/example/router/index.ts deleted file mode 100644 index b1a1903..0000000 --- a/example/router/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { App } from 'vue' -import { createRouter, createWebHistory } from 'vue-router' -import { routes } from './routes' - -export function setupRouter(app: App) { - const router = createRouter({ - history: createWebHistory(), - routes: routes, - }) - app.use(router) -} diff --git a/example/router/router.service.ts b/example/router/router.service.ts index d5c02ad..95a68ee 100644 --- a/example/router/router.service.ts +++ b/example/router/router.service.ts @@ -1,7 +1,22 @@ import { Injectable } from 'injection-js' -import { useRouter } from 'vue-router' +import { createRouter, createWebHistory } from 'vue-router' +import { routes } from './routes' +import { getCurrentApp, VueService } from '@/index' @Injectable() -export class RouterService { - router = useRouter() +export class RouterService extends VueService { + history = createWebHistory() + router = createRouter({ + history: this.history, + routes: routes, + }) + app = getCurrentApp()! + get currentRoute() { + return this.router.currentRoute.value + } + + constructor() { + super() + this.app.use(this.router) + } } diff --git a/src/decorators/computed.ts b/src/decorators/computed.ts index 2307c3c..972cb41 100644 --- a/src/decorators/computed.ts +++ b/src/decorators/computed.ts @@ -8,7 +8,7 @@ interface ComputedItem { } const MetadataKey = Symbol('Computed') -export function Computed() { +export function Computed(): MethodDecorator { return function (target: any, key: string | symbol) { let list: (string | symbol)[] = Reflect.getMetadata(MetadataKey, target) || [] list = list.slice() diff --git a/src/decorators/hook.ts b/src/decorators/hook.ts index fe498c8..7f84607 100644 --- a/src/decorators/hook.ts +++ b/src/decorators/hook.ts @@ -36,7 +36,7 @@ type Lifecycle = const MetadataKey = Symbol('Hook') -export function Hook(lifecycle: Lifecycle) { +export function Hook(lifecycle: Lifecycle): MethodDecorator { return function (target: any, key: string | symbol) { let list: HookItem[] = Reflect.getMetadata(MetadataKey, target) || [] list = list.slice() diff --git a/src/decorators/ref.ts b/src/decorators/ref.ts index 54341c8..814cc6f 100644 --- a/src/decorators/ref.ts +++ b/src/decorators/ref.ts @@ -6,7 +6,6 @@ const MetadataKey = Symbol('Ref') export function Ref(): PropertyDecorator { return function (target: any, key: string | symbol) { let list: (string | symbol)[] = Reflect.getMetadata(MetadataKey, target) || [] - // 处理原型链上的数据 list = list.slice() const hasItem = list.find((k) => k === key) if (!hasItem) list.push(key) diff --git a/src/di/index.ts b/src/di/index.ts index 5c4ee8e..cad097d 100644 --- a/src/di/index.ts +++ b/src/di/index.ts @@ -6,8 +6,8 @@ const MetadataKey = Symbol('Component') declare module 'vue' { interface App { - getStore(): ReflectiveInjector - getService: ReflectiveInjector['get'] + getStore(): any + getService(token: any): any } } @@ -30,7 +30,7 @@ export interface ComponentOptions { globalStore?: boolean } -export function Component(options?: ComponentOptions) { +export function Component(options?: ComponentOptions): ClassDecorator { return function (target: any) { if (!target.resolveComponent) target.resolveComponent = resolveComponent Reflect.defineMetadata(MetadataKey, options, target) diff --git a/src/extends/component.ts b/src/extends/component.ts index acbf092..ef09318 100644 --- a/src/extends/component.ts +++ b/src/extends/component.ts @@ -1,20 +1,23 @@ -import { Prop, provide, SetupContext, VNodeChild, VNodeProps } from 'vue' +import { getCurrentInstance, Prop, provide, SetupContext, VNodeChild, VNodeProps } from 'vue' import { getEmitsFromProps, useCtx, useProps } from '@/helper' -import { AnyContructor, Hanlder } from '@/type' +import { Hanlder, VueComponentStaticContructor } from '@/type' import { RefHandler } from '@/decorators/ref' import { ComputedHandler } from '@/decorators/computed' import { HookHandler } from '@/decorators/hook' -import { ProviderKey } from '@/extends/service' import { LinkHandler } from '@/decorators/link' +export const GlobalStoreKey = 'GlobalStoreKey' + export abstract class VueComponent> { + /** 装饰器处理 */ static handler: Hanlder[] = [RefHandler, ComputedHandler, LinkHandler, HookHandler] - static resolveComponent: any - static __vccOpts__value: any + /** 是否自定义解析组件 */ + static resolveComponent?: any + static __vccOpts__value?: any + /** 组件option定义,vue3遇到类组件会从此属性获取组件的option */ static get __vccOpts() { if (this.__vccOpts__value) return this.__vccOpts__value - // eslint-disable-next-line @typescript-eslint/no-this-alias - const CompConstructor = this as unknown as AnyContructor + const CompConstructor = this as unknown as VueComponentStaticContructor return (this.__vccOpts__value = { ...CompConstructor, @@ -35,35 +38,43 @@ export abstract class VueComponent> { }, }) } - - /** - * 主要给jsx提示用 - */ + /** 是否作为全局store提供外部入口,此时会在 当前app上注入2个方法,用于获取此组件的服务 */ + static globalStore?: boolean + /** 是否把自己当做服务provide出去,以便子组件可注入 */ + static ProviderKey?: string | symbol + /** 主要给jsx提示用 */ get $props() { return this.props } - - /** - * 组件属性 - */ + /** 组件属性 */ public props: T & VNodeProps & Record - /** - * 组件上下文 - */ + /** 组件上下文 */ public context: SetupContext constructor() { this.props = useProps() this.context = useCtx() this.context.expose(this) - // @ts-ignore - if (this.constructor[ProviderKey]) provide(this.constructor[ProviderKey], this) + const ThisConstructor = this.constructor as VueComponentStaticContructor + if (ThisConstructor.ProviderKey) provide(ThisConstructor.ProviderKey, this) + if (ThisConstructor.globalStore) { + // 如果作为全局的服务,则注入到根上面 + const current = getCurrentInstance()! + const app = current.appContext.app + app.provide(GlobalStoreKey, this) + app.getStore = () => this + app.getService = (token) => { + if ((typeof token === 'function' || typeof token === 'object') && 'ProviderKey' in token) { + token = token.ProviderKey + } + // @ts-ignore + return current?.provides[token] + } + } VueComponent.handler.forEach((handler) => handler.handler(this)) } - /** - * 渲染函数 - */ + /** 渲染函数 */ abstract render(): VNodeChild abstract render(ctx?: any): VNodeChild abstract render(ctx?: any, cache?: any[]): VNodeChild diff --git a/src/extends/service.ts b/src/extends/service.ts index 0e6a7a8..140a2ee 100644 --- a/src/extends/service.ts +++ b/src/extends/service.ts @@ -1,50 +1,17 @@ import { RefHandler } from '@/decorators/ref' import { ComputedHandler } from '@/decorators/computed' import { HookHandler } from '@/decorators/hook' -import { getCurrentInstance, InjectionKey, provide } from 'vue' -import { Hanlder } from '@/type' +import { provide } from 'vue' +import { Hanlder, VueComponentStaticContructor } from '@/type' import { LinkHandler } from '@/decorators/link' -export const ProviderKey = 'ProviderKey' +export const ProviderKey = 'ProviderKey' as const export abstract class VueService { static handler: Hanlder[] = [RefHandler, ComputedHandler, LinkHandler, HookHandler] - constructor() { - // 自动provide service - // @ts-ignore - if (this.constructor[ProviderKey]) provide(this.constructor[ProviderKey], this) - VueService.handler.forEach((handler) => handler.handler(this)) - } -} - -export abstract class VueGlobalService extends VueService { - static get(token: string | symbol): T - static get(token: InjectionKey): T - static get< - T extends { - new (...args: any[]): InstanceType - ProviderKey: InjectionKey - }, - >(token: T): InstanceType - static get(token: any) { - if ((typeof token === 'function' || typeof token === 'object') && 'ProviderKey' in token) { - token = token.ProviderKey - } - // @ts-ignore - const instance = this.__instance - // @ts-ignore - return instance?.provides[token] - } - static getSelf(this: T): InstanceType { - // @ts-ignore - return this.__self as unknown as InstanceType - } public constructor() { - super() - const Constructor = this.constructor - // @ts-ignore - Constructor.__self = this - // @ts-ignore - Constructor.__instance = getCurrentInstance() + const ThisConstructor = this.constructor as VueComponentStaticContructor + if (ThisConstructor.ProviderKey) provide(ThisConstructor.ProviderKey, this) + VueService.handler.forEach((handler) => handler.handler(this)) } } diff --git a/src/helper.ts b/src/helper.ts index 7c8e9be..20fc954 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -20,7 +20,9 @@ export function useCtx(): SetupContext { // @ts-ignore return instance.setupContext } - +export function getCurrentApp() { + return getCurrentInstance()?.appContext.app +} export function getProtoMetadata(target: any, key: symbol, returnDesc = false): any[] { if (!target) return [] const proto = Reflect.getPrototypeOf(target) @@ -44,14 +46,12 @@ export function getProtoMetadata(target: any, key: symbol, returnDesc = false): } return res } - export function getDeepOwnDescriptor(proto: any, key: string): PropertyDescriptor | null { if (!proto) return null const desc = Reflect.getOwnPropertyDescriptor(proto, key) if (desc) return desc return getDeepOwnDescriptor(Reflect.getPrototypeOf(proto), key) } - export function getEmitsFromProps(defaultProps: Record) { const keys = Object.keys(defaultProps) const emits: string[] = [] diff --git a/src/index.ts b/src/index.ts index f4e71e2..3b7ca3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -export { VueComponent } from './extends/component' -export { VueService, VueGlobalService } from './extends/service' +export { VueComponent, GlobalStoreKey } from './extends/component' +export { VueService, ProviderKey } from './extends/service' export { Ref } from './decorators/ref' export { Computed } from './decorators/computed' export { Link } from './decorators/link' diff --git a/src/type.ts b/src/type.ts index 29794dc..de420f6 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,9 +1,19 @@ -export interface AnyContructor { +export interface VueComponentStaticContructor { new (...args: any[]): any + /** 组件显示名称 */ displayName?: string + /** 组件属性vue描述 */ defaultProps?: any + /** 组件是否回退attrs */ inheritAttrs?: boolean + /** 组件使用的指令 */ directives?: any + /** 组件作为服务的key */ + ProviderKey?: symbol | string + /** 组件是否作为全局服务 */ + globalStore?: boolean + /** 自定义解析组件 */ + resolveComponent?: any [prop: string]: any }