Skip to content

Commit d679542

Browse files
committed
Code setup API, document, add basic test
1 parent d1ada43 commit d679542

File tree

9 files changed

+148
-0
lines changed

9 files changed

+148
-0
lines changed

docs/en/_sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- [Quick start](/en/quick-start/quick-start.md)
33
- Class component
44
- [Component](/en/class-component/component/component.md)
5+
- [Setup](/en/class-component/setup/setup.md)
56
- [Property](/en/class-component/property/property.md)
67
- [Method](/en/class-component/method/method.md)
78
- [Hooks](/en/class-component/hooks/hooks.md)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import ref from 'vue'
2+
import { Component, Vue, setup } from 'vue-facing-decorator'
3+
4+
@Component({
5+
setup(props, ctx) {
6+
const message = ref('hello world')
7+
return { message }
8+
}
9+
})
10+
class MyComponent extends Vue {
11+
private data = setup(() => 'hello world') // This setup() function will no longer work!
12+
13+
mounted() {
14+
console.log(this.message) // Undefined!
15+
}
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Component, Vue, setup } from 'vue-facing-decorator'
2+
import { useRouter } from 'vue-router'
3+
4+
@Component
5+
class MyComponent extends Vue {
6+
private router = setup(() => useRouter())
7+
8+
mounted() {
9+
console.log(this.router.getRoutes())
10+
}
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Usage
2+
3+
Use the `setup` function exported from `'vue-facing-decorator'` to inject [composables](https://vuejs.org/guide/reusability/composables.html) into your component's class as data.
4+
5+
[](./code-usage-base.ts ':include :type=code typescript')
6+
7+
## Options
8+
9+
You can also provide your own custom setup function as part of the component options, but be aware that if you do this, the `setup()` function from `'vue-facing-decorator'` will no longer work to inject data, and any properties you return from that setup will only be accessible in the component's template or render function.
10+
11+
[](./code-option-setup.ts ':include :type=code typescript')
12+

src/component.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defineComponent, type ComponentCustomOptions } from 'vue';
22
import { obtainSlot, getSuperSlot } from './utils'
3+
import { build as optionSetup } from './option/setup'
34
import { build as optionComputed } from './option/computed'
45
import { build as optionData } from './option/data'
56
import { build as optionMethodsAndHooks } from './option/methodsAndHooks'
@@ -10,6 +11,7 @@ import { build as optionInject } from './option/inject'
1011
import { build as optionEmit } from './option/emit'
1112
import { build as optionVModel } from './option/vmodel'
1213
import { build as optionAccessor } from './option/accessor'
14+
import type { SetupContext, RenderFunction } from 'vue';
1315
import type { OptionBuilder } from './optionBuilder'
1416
import type { VueCons } from './index'
1517
export type Cons = VueCons
@@ -18,6 +20,7 @@ export type Cons = VueCons
1820
function ComponentOption(cons: Cons, extend?: any) {
1921

2022
const optionBuilder: OptionBuilder = {}
23+
const setupData = optionSetup(cons, optionBuilder)
2124
optionVModel(cons, optionBuilder)
2225
optionComputed(cons, optionBuilder)//after VModel
2326
optionWatch(cons, optionBuilder)
@@ -28,9 +31,13 @@ function ComponentOption(cons: Cons, extend?: any) {
2831
optionMethodsAndHooks(cons, optionBuilder)//after Ref Computed
2932
optionAccessor(cons, optionBuilder)
3033
const raw = {
34+
setup: optionBuilder.setup,
3135
data() {
3236
delete optionBuilder.data
3337
optionData(cons, optionBuilder, this)
38+
if (optionBuilder.data && Object.keys(setupData).length > 0) {
39+
Object.assign(optionBuilder.data, setupData)
40+
}
3441
return optionBuilder.data ?? {}
3542
},
3643
methods: optionBuilder.methods,
@@ -46,6 +53,7 @@ function ComponentOption(cons: Cons, extend?: any) {
4653

4754
type ComponentOption = {
4855
name?: string
56+
setup?: (this: void, props: Readonly<any>, ctx: SetupContext<any>) => Promise<any> | any | RenderFunction | void,
4957
emits?: string[]
5058
provide?: Record<string, any> | Function
5159
components?: Record<string, any>

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { Component, ComponentBase } from './component'
2+
export { setup } from './option/setup'
23
export { decorator as Ref } from './option/ref'
34
export { decorator as Watch } from './option/watch'
45
export { decorator as Prop } from './option/props'

src/option/setup.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { proxyRefs } from 'vue';
2+
import type { Ref, SetupContext, ShallowUnwrapRef } from 'vue';
3+
import type { Cons } from '../component'
4+
import type { OptionBuilder } from '../optionBuilder'
5+
import { getValidNames } from '../utils'
6+
7+
const isPromise = (v: any) => typeof v === 'object' && typeof v.then === 'function'
8+
9+
export function build(cons: Cons, optionBuilder: OptionBuilder): Record<string, any> {
10+
const setupData: Record<string, any> = {};
11+
12+
optionBuilder.setup = (props, ctx) => {
13+
const sample = new cons(optionBuilder, ctx)
14+
15+
let promise: Promise<any> | null = null;
16+
17+
const names = getValidNames(sample, (des) => {
18+
return !!des.enumerable
19+
})
20+
for (const name of names) {
21+
const value = (sample as any)[name]
22+
const keys = Object.keys(value ?? {})
23+
if (keys.length === 1 && keys[0] === '__vueFacingDecoratorDeferredSetup') {
24+
const setupState = value.__vueFacingDecoratorDeferredSetup(props, ctx)
25+
if (isPromise(setupState)) {
26+
if (!promise) {
27+
promise = Promise.resolve(setupState)
28+
}
29+
promise = promise.then(() => {
30+
return setupState.then((value: any) => {
31+
setupData[name] = proxyRefs(value)
32+
return {}
33+
})
34+
})
35+
} else {
36+
setupData[name] = setupState;
37+
}
38+
}
39+
}
40+
41+
return promise ?? {};
42+
}
43+
return setupData;
44+
}
45+
46+
export type UnwrapSetupValue<T> = T extends Ref<infer R>
47+
? R
48+
: ShallowUnwrapRef<T>
49+
50+
export type UnwrapPromise<T> = T extends Promise<infer R> ? R : T
51+
52+
export function setup<R = any>(setupFn: (this: void, props: Readonly<any>, ctx: SetupContext<any>) => R): UnwrapSetupValue<UnwrapPromise<R>> {
53+
return {
54+
__vueFacingDecoratorDeferredSetup: setupFn
55+
} as UnwrapSetupValue<UnwrapPromise<R>>
56+
}

src/optionBuilder.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import type { RenderFunction, SetupContext } from 'vue';
12
import type { WatchConfig } from './option/watch'
23
import type { PropsConfig } from './option/props'
34
import type { InjectConfig } from './option/inject'
45
export interface OptionBuilder {
56
name?: string
7+
setup?: (this: void, props: Readonly<any>, ctx: SetupContext<any>) => Promise<any> | any | RenderFunction | void
68
data?: Record<string, any>
79
methods?: Record<string, Function>
810
hooks?: Record<string, Function>

test/option/setup.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
import { inject } from 'vue'
3+
import { mount } from '@vue/test-utils'
4+
import { expect } from 'chai';
5+
import 'mocha';
6+
import { Component, Base, setup } from '../../dist'
7+
8+
const AXIOM = 'setup is working to allow composition API usage'
9+
const injectionKey = Symbol('injection test key')
10+
11+
function useInjectedValue() {
12+
inject(injectionKey)
13+
}
14+
15+
@Component
16+
export class Comp extends Base {
17+
18+
injectedValue = setup(() => useInjectedValue())
19+
20+
}
21+
22+
const CompContext = Comp as any
23+
24+
25+
describe('setup function',
26+
() => {
27+
const wrapper = mount(CompContext,{
28+
global: {
29+
provide: {
30+
[injectionKey]: AXIOM
31+
}
32+
}
33+
})
34+
const vm = wrapper.vm
35+
it('injects the value provided to the component via composition API', () => {
36+
expect(vm.injectedValue).to.equal(AXIOM)
37+
})
38+
}
39+
)
40+
41+
export default {}

0 commit comments

Comments
 (0)