Skip to content

Commit e829b1e

Browse files
committed
feat: App.use
1 parent 0453659 commit e829b1e

File tree

7 files changed

+201
-115
lines changed

7 files changed

+201
-115
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.2.1
2+
3+
- feat: App.use
4+
15
## 1.2.0
26

37
- fix: Set default props error on Vue2

README.md

+46
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,52 @@ const app = new App();
303303

304304
<!-- file:./tests/watch.vue end -->
305305

306+
## Get the injection object in setup
307+
308+
Useful when `defineExpose`
309+
310+
<!-- file:./tests/use.vue start -->
311+
312+
```vue
313+
<script lang="ts">
314+
import { defineComponent } from 'vue';
315+
import { Setup, Context } from 'vue-class-setup';
316+
317+
@Setup
318+
class App extends Context {
319+
private _value = 0;
320+
public get text() {
321+
return String(this._value);
322+
}
323+
public set text(text: string) {
324+
this._value = Number(text);
325+
}
326+
public addValue() {
327+
this._value++;
328+
}
329+
}
330+
export default defineComponent({
331+
...App.inject(),
332+
});
333+
</script>
334+
<script lang="ts" setup>
335+
const app = App.use();
336+
337+
defineExpose({
338+
addValue: app.addValue,
339+
});
340+
</script>
341+
<template>
342+
<div>
343+
<p class="text">{{ text }}</p>
344+
<p class="text-eq">{{ app.text === text }}</p>
345+
<button @click="addValue"></button>
346+
</div>
347+
</template>
348+
```
349+
350+
<!-- file:./tests/use.vue end -->
351+
306352
## Vue compatible
307353

308354
- `getCurrentInstance` returns the proxy object by default

src/config.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const SETUP_OPTIONS_NAME = 'setupOptions';
22
export const SETUP_NAME = 'setup';
33
export const SETUP_PROPERTY_DESCRIPTOR = 'setupPropertyDescriptor';
4-
export const SETUP_SETUP_DEFINE = '__setupDefine__';
4+
export const SETUP_SETUP_DEFINE = '__setupDefine';
5+
export const SETUP_USE = '__setupUse__';

src/context.ts

+77-100
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { watch } from 'vue';
2-
import { getCurrentInstance, type VueInstance, isVue2, isVue3 } from './vue';
1+
import { getCurrentInstance, type VueInstance, isVue2 } from './vue';
32
import { setupReference } from './setup-reference';
43
import { TargetName, Target } from './types';
54
import {
@@ -9,7 +8,7 @@ import {
98
} from './config';
109
import { createDefineProperty } from './property-descriptors';
1110
import { DefineConstructor } from './define';
12-
import { SETUP_SETUP_DEFINE } from './config';
11+
import { SETUP_SETUP_DEFINE, SETUP_USE } from './config';
1312

1413
let currentTarget: Target | null = null;
1514
let currentName: TargetName | null = null;
@@ -28,43 +27,50 @@ export function setCurrentHookName(name: TargetName | null) {
2827
currentName = name;
2928
}
3029

31-
function initProps(target: VueInstance, app: InstanceType<DefineConstructor>) {
32-
const keys = Object.keys(app.$defaultProps || {});
33-
if (keys.length) {
34-
watch(
35-
() => {
36-
const props = target['$props'];
37-
if (!props) return {};
38-
const data: Record<string, any> = {};
39-
keys.forEach((key) => {
40-
if (props[key] !== app[key]) {
41-
data[key] = app[key];
42-
}
43-
});
44-
return data;
30+
function use(target: VueInstance, _This: any) {
31+
let use: Map<any, InstanceType<DefineConstructor>>;
32+
if (target[SETUP_USE]) {
33+
use = target[SETUP_USE];
34+
} else {
35+
use = new Map();
36+
target[SETUP_USE] = use;
37+
}
38+
let app = use.get(_This)!;
39+
if (app) {
40+
return app;
41+
}
42+
app = new _This() as InstanceType<DefineConstructor>;
43+
44+
use.set(_This, app);
45+
46+
const names = Object.getOwnPropertyNames(app);
47+
const defineProperty = createDefineProperty(target);
48+
const propertyDescriptor = _This[SETUP_PROPERTY_DESCRIPTOR] as Map<
49+
string,
50+
PropertyDescriptor
51+
>;
52+
names.forEach((name) => {
53+
if (propertyDescriptor.has(name) || name === SETUP_SETUP_DEFINE) return;
54+
defineProperty(name, {
55+
get() {
56+
return app[name];
4557
},
46-
(defaultProps) => {
47-
const props = target['$props'];
48-
if (!props) {
49-
return;
50-
}
51-
const definePropertyProps = createDefineProperty(props);
52-
Object.keys(defaultProps).forEach((key) => {
53-
const descriptor = Object.getOwnPropertyDescriptor(
54-
props,
55-
key
56-
);
57-
definePropertyProps(key, {
58-
...descriptor,
59-
value: defaultProps[key],
60-
});
61-
});
58+
set(val) {
59+
app[name] = val;
6260
},
63-
{
64-
immediate: true,
65-
}
66-
);
67-
}
61+
});
62+
});
63+
propertyDescriptor.forEach((value, name) => {
64+
defineProperty(name, {
65+
get() {
66+
return app[name];
67+
},
68+
set(val) {
69+
app[name] = val;
70+
},
71+
});
72+
});
73+
return app;
6874
}
6975

7076
export class Context {
@@ -74,75 +80,46 @@ export class Context {
7480
string,
7581
PropertyDescriptor
7682
>();
83+
public static use<T extends new (...args: any) => any>(this: T) {
84+
const vm = getCurrentInstance();
85+
if (!vm) {
86+
throw Error('Please run in the setup function');
87+
}
88+
return use(vm, this) as InstanceType<T>;
89+
}
7790
public static inject<T extends new (...args: any) => any>(this: T) {
7891
const _This = this;
79-
const map = _This[SETUP_PROPERTY_DESCRIPTOR] as Map<
80-
string,
81-
PropertyDescriptor
82-
>;
83-
function use(target: VueInstance) {
84-
const app = new _This() as InstanceType<DefineConstructor>;
85-
const names = Object.getOwnPropertyNames(app);
86-
const defineProperty = createDefineProperty(target);
87-
// Watch default props
88-
if (isVue3) {
89-
initProps(target, app);
90-
}
9192

92-
names.forEach((name) => {
93-
if (map.has(name)) return;
94-
defineProperty(name, {
95-
get() {
96-
return app[name];
97-
},
98-
set(val) {
99-
app[name] = val;
100-
},
101-
});
102-
});
103-
map.forEach((value, name) => {
104-
defineProperty(name, {
105-
get() {
106-
return app[name];
107-
},
108-
set(val) {
109-
app[name] = val;
110-
},
111-
});
112-
});
113-
return app as InstanceType<T>;
114-
}
115-
const data: {
116-
beforeCreate?: () => void;
117-
created?: () => void;
118-
setup: () => InstanceType<T>;
119-
} = {
93+
return {
12094
setup() {
12195
return {} as InstanceType<T>;
12296
},
123-
};
124-
if (isVue2) {
125-
data.beforeCreate = function beforeCreate() {
126-
const vm = this as any;
127-
if (!vm.$options) return;
128-
const setup = vm.$options.setup;
129-
vm.$options.setup = (props: any, ctx: any) => {
130-
const app = use(vm) as any;
131-
if (app[SETUP_SETUP_DEFINE]) {
132-
return setup(app, ctx);
133-
}
134-
if (setup) {
135-
return setup(props, ctx);
97+
created() {
98+
const vm = this as any as VueInstance;
99+
const app = use(vm, _This);
100+
if (!vm.$options || isVue2) {
101+
return;
102+
}
103+
const render = vm.$options.render as Function | undefined;
104+
if (app[SETUP_SETUP_DEFINE] && render) {
105+
const proxyRender = (...args: any[]) => {
106+
const props = vm.$.props;
107+
for (let i = 0; i < args.length; i++) {
108+
if (args[i] === props) {
109+
args[i] = app;
110+
break;
111+
}
112+
}
113+
return render.apply(this, args);
114+
};
115+
vm.$options.render = proxyRender;
116+
117+
if (vm.$) {
118+
(vm as any).$.render = proxyRender;
136119
}
137-
return {};
138-
};
139-
};
140-
} else {
141-
data.created = function created() {
142-
use(this as any);
143-
};
144-
}
145-
return data;
120+
}
121+
},
122+
};
146123
}
147124
public $vm: VueInstance;
148125
public $emit: VueInstance['$emit'];

src/define.ts

+21-14
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,29 @@ export function initDefine(target: InstanceType<DefineConstructor>) {
6969
if (k in target) {
7070
// @ts-ignore
7171
target.$defaultProps[k] = target[k];
72-
}
73-
definePropertyTarget(k, {
74-
get() {
75-
let value = props[k];
76-
77-
if (typeof value === 'boolean') {
78-
if (!hasDefaultValue(target.$vm, k)) {
79-
value = target.$defaultProps[k];
72+
const defaultProps = target.$defaultProps;
73+
definePropertyTarget(k, {
74+
get() {
75+
let value = props[k];
76+
if (typeof value === 'boolean') {
77+
if (!hasDefaultValue(target.$vm, k)) {
78+
value = defaultProps[k];
79+
}
80+
} else if (isNull(value)) {
81+
value = defaultProps[k];
8082
}
81-
} else if (isNull(value)) {
82-
value = target.$defaultProps[k];
83-
}
84-
return value;
85-
},
86-
});
83+
return value;
84+
},
85+
});
86+
} else {
87+
definePropertyTarget(k, {
88+
get() {
89+
return props[k];
90+
},
91+
});
92+
}
8793
});
94+
// console.log('>>>>>>1111', target['ok2'], props);
8895
}
8996

9097
function hasDefaultValue(vm: VueInstance, key: string): boolean {

tests/use.spec.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { assert, test } from 'vitest';
2+
import { mount } from '@vue/test-utils';
3+
4+
import Use from './use.vue';
5+
6+
test('Use', async () => {
7+
const wrapper = mount(Use);
8+
console.log('>>>>>.', wrapper.html());
9+
assert.equal(wrapper.find('.text').text(), '0');
10+
assert.equal(wrapper.find('.text-eq').text(), 'true');
11+
12+
await wrapper.find('button').trigger('click');
13+
14+
assert.equal(wrapper.find('.text').text(), '1');
15+
assert.equal(wrapper.find('.text-eq').text(), 'true');
16+
});

tests/use.vue

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script lang="ts">
2+
import { defineComponent } from 'vue';
3+
import { Setup, Context } from 'vue-class-setup';
4+
5+
@Setup
6+
class App extends Context {
7+
private _value = 0;
8+
public get text() {
9+
return String(this._value);
10+
}
11+
public set text(text: string) {
12+
this._value = Number(text);
13+
}
14+
public addValue() {
15+
this._value++;
16+
}
17+
}
18+
export default defineComponent({
19+
...App.inject(),
20+
});
21+
</script>
22+
<script lang="ts" setup>
23+
const app = App.use();
24+
25+
defineExpose({
26+
addValue: app.addValue,
27+
});
28+
</script>
29+
<template>
30+
<div>
31+
<p class="text">{{ text }}</p>
32+
<p class="text-eq">{{ app.text === text }}</p>
33+
<button @click="addValue"></button>
34+
</div>
35+
</template>

0 commit comments

Comments
 (0)