Skip to content

Commit d9b4856

Browse files
committed
feat: 属性支持数组形式,并给与类型提示
1 parent f25f7ff commit d9b4856

File tree

6 files changed

+79
-41
lines changed

6 files changed

+79
-41
lines changed

README.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pnpm add vue3-oop
4141
### 定义组件
4242

4343
```typescript jsx
44-
import { Autobind, ComponentProps, Computed, Hook, Link, Ref, VueComponent } from 'vue3-oop'
44+
import { Autobind, ComponentPropsArray, Computed, Hook, Link, Ref, VueComponent } from 'vue3-oop'
4545
import { Directive, VNodeChild, watch } from 'vue'
4646

4747
const focusDirective: Directive = {
@@ -60,13 +60,12 @@ interface Foo_Props {
6060

6161
class Foo extends VueComponent<Foo_Props> {
6262
// vue需要的运行时属性检查
63-
static defaultProps: ComponentProps<Foo_Props> = {
64-
size: String,
65-
}
63+
static defaultProps: ComponentPropsArray<Foo_Props> = ['size']
6664
// 组件需要的局部指令
6765
static directives: Record<string, Directive> = {
6866
focus: focusDirective,
6967
}
68+
7069
constructor() {
7170
super()
7271
// watch在构造函数中初始化
@@ -80,14 +79,17 @@ class Foo extends VueComponent<Foo_Props> {
8079

8180
// 组件自身状态
8281
@Ref() count = 1
82+
8383
// 计算属性
8484
@Computed()
8585
get doubleCount() {
8686
return this.count * 2
8787
}
88+
8889
add() {
8990
this.count++
9091
}
92+
9193
// 自动绑定this
9294
@Autobind()
9395
remove() {
@@ -111,7 +113,7 @@ class Foo extends VueComponent<Foo_Props> {
111113
<span>{this.count}</span>
112114
<button onClick={this.remove}>-</button>
113115
<div>{this.context.slots.item?.('aaa')}</div>
114-
<input type="text" v-focus />
116+
<input type="text" v-focus/>
115117
</div>
116118
)
117119
}

example/main.tsx

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import '@abraham/reflection'
2-
import type { ClassType } from '@/index'
3-
import { Autobind, Component, ComponentProps, Computed, Hook, Link, Ref, VueComponent, VueService } from '@/index'
2+
import type { ClassType, ComponentProps } from '@/index'
3+
import { Autobind, Component, Computed, Hook, Link, Ref, VueComponent, VueService } from '@/index'
44
import { forwardRef, Inject, Injectable, SkipSelf } from 'injection-js'
55
import { createApp, VNodeChild, watch } from 'vue'
66

@@ -23,6 +23,10 @@ class CountService extends VueService {
2323
// 组件属性声明
2424
interface HomeChild_Props {
2525
size: 'small' | 'large'
26+
value?: string
27+
'onUpdate:value'?: (name: string) => void
28+
check?: boolean
29+
'onUpdate:check'?: (check: boolean) => void
2630
// 组件个性化定义属性
2731
slots: {
2832
item(name: string): VNodeChild
@@ -32,9 +36,7 @@ interface HomeChild_Props {
3236
// 带属性组件
3337
@Component({ providers: [CountService] })
3438
class HomeChild extends VueComponent<HomeChild_Props> {
35-
static defaultProps: ComponentProps<HomeChild_Props> = {
36-
size: String,
37-
}
39+
static defaultProps: ComponentProps<HomeChild_Props> = ['size', 'value', 'onUpdate:value', 'check', 'onUpdate:check']
3840

3941
constructor(
4042
private countService: CountService,

src/extends/component.ts

+3-28
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
1-
import { getCurrentInstance, Prop, provide, SetupContext, VNodeChild, VNodeProps } from 'vue'
1+
import { getCurrentInstance, provide, VNodeChild, VNodeProps } from 'vue'
22
import { getEmitsFromProps, useCtx, useProps } from '@/helper'
3-
import { Hanlder, VueComponentStaticContructor } from '@/type'
3+
import { Hanlder, VueComponentStaticContructor, WithSlotTypes, WithVModel, WithVSlots } from '@/type'
44
import { RefHandler } from '@/decorators/ref'
55
import { ComputedHandler } from '@/decorators/computed'
66
import { HookHandler } from '@/decorators/hook'
77
import { LinkHandler } from '@/decorators/link'
88

99
export const GlobalStoreKey = 'GlobalStoreKey'
1010

11-
type VueComponentProps<T extends Record<string, any>> = Omit<T, 'slots'> & WithVSlots<T> & VNodeProps
12-
13-
// 处理tsx slots 类型问题
14-
type WithVSlots<T extends Record<string, any>> = 'slots' extends keyof T
15-
? {
16-
'v-slots'?: Partial<T['slots'] & { $stable: boolean; default(): VNodeChild }>
17-
[name: string]: any
18-
}
19-
: Record<string, any>
20-
21-
type WithSlotTypes<T extends Record<string, any>> = Omit<SetupContext, 'slots'> & {
22-
slots: Partial<T['slots'] & { default(): VNodeChild }>
23-
}
11+
type VueComponentProps<T extends Record<string, any>> = Omit<T, 'slots'> & WithVModel<T> & WithVSlots<T> & VNodeProps
2412

2513
export abstract class VueComponent<T = Record<string, any>> {
2614
/** 装饰器处理 */
@@ -93,16 +81,3 @@ export abstract class VueComponent<T = Record<string, any>> {
9381
abstract render(ctx?: any): VNodeChild
9482
abstract render(ctx?: any, cache?: any[]): VNodeChild
9583
}
96-
97-
export type ComponentProps<T extends Record<string, any>> = {
98-
[U in keyof Omit<T, 'slots'>]-?: Prop<any>
99-
}
100-
101-
export type ComponentSlots<T extends { props: any }> = T extends { props: infer U }
102-
? 'v-slots' extends keyof U
103-
? U['v-slots']
104-
: Record<string, unknown>
105-
: never
106-
107-
/** 为了阻止ts把不相关的类也解析到metadata数据中,用这个工具类型包装一下类 */
108-
export type ClassType<T> = T

src/helper.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ export function getDeepOwnDescriptor(proto: any, key: string): PropertyDescripto
5252
if (desc) return desc
5353
return getDeepOwnDescriptor(Reflect.getPrototypeOf(proto), key)
5454
}
55-
export function getEmitsFromProps(defaultProps: Record<string, any>) {
56-
const keys = Object.keys(defaultProps)
55+
export function getEmitsFromProps(defaultProps: Record<string, any> | string[]) {
56+
const keys = Array.isArray(defaultProps) ? defaultProps : Object.keys(defaultProps)
5757
const emits: string[] = []
5858

5959
for (let key of keys) {

src/index.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,11 @@ export { Hook } from './decorators/hook'
77
export * from './helper'
88
export { Component, InjectorKey } from './di'
99
export type { ComponentOptions } from './di'
10-
export type { ComponentProps, ComponentSlots, ClassType } from './extends/component'
10+
export type {
11+
ComponentProps,
12+
ComponentSlots,
13+
ClassType,
14+
WithVModel,
15+
ComponentPropsArray,
16+
ComponentPropsObject,
17+
} from './type'

src/type.ts

+52
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Prop, SetupContext, VNodeChild } from 'vue'
2+
13
export interface VueComponentStaticContructor {
24
new (...args: any[]): any
35
/** 组件显示名称 */
@@ -24,3 +26,53 @@ export interface Hanlder {
2426
key: string
2527
handler: (targetThis: any) => void
2628
}
29+
30+
// 处理tsx slots 类型问题
31+
export type WithVSlots<T extends Record<string, any>> = 'slots' extends keyof T
32+
? {
33+
'v-slots'?: Partial<T['slots'] & { $stable: boolean; default(): VNodeChild }>
34+
[name: string]: any
35+
}
36+
: Record<string, any>
37+
38+
export type WithSlotTypes<T extends Record<string, any>> = Omit<SetupContext, 'slots'> & {
39+
slots: Partial<T['slots'] & { default(): VNodeChild }>
40+
}
41+
42+
type ModelProps<T extends Record<string, any>> = Exclude<
43+
{
44+
[Prop in keyof T]: T extends { [k in Prop as `onUpdate:${k & string}`]?: any } ? Prop : never
45+
}[keyof T],
46+
undefined
47+
>
48+
49+
export type WithVModel<T extends Record<string, any>, U extends keyof T = ModelProps<T>> = {
50+
[k in U as `v-model:${k & string}`]?: T[k] | [T[k], string[]]
51+
}
52+
53+
export type ComponentProps<T extends Record<string, any>> = ComponentPropsArray<T> | ComponentPropsObject<T>
54+
55+
export type ComponentPropsObject<T extends Record<string, any>> = {
56+
[U in keyof Omit<T, 'slots'>]-?: Prop<any>
57+
}
58+
export type ComponentPropsArray<T extends Record<string, any>> = UnionToTuple<keyof Omit<T, 'slots'>>
59+
60+
export type ComponentSlots<T extends { props: any }> = T extends { props: infer U }
61+
? 'v-slots' extends keyof U
62+
? U['v-slots']
63+
: Record<string, unknown>
64+
: never
65+
66+
/** 为了阻止ts把不相关的类也解析到metadata数据中,用这个工具类型包装一下类 */
67+
export type ClassType<T> = T
68+
69+
// 联合类型转换成数组 'aaa' | 'bbb' -> ['aaa', 'bbb']
70+
export type UnionToTuple<T> = (
71+
(T extends any ? (t: T) => T : never) extends infer U
72+
? (U extends any ? (u: U) => any : never) extends (v: infer V) => any
73+
? V
74+
: never
75+
: never
76+
) extends (_: any) => infer W
77+
? [...UnionToTuple<Exclude<T, W>>, W]
78+
: []

0 commit comments

Comments
 (0)