Skip to content

Commit c036b3f

Browse files
committed
feat: bind this
1 parent 150cb74 commit c036b3f

File tree

9 files changed

+110
-92
lines changed

9 files changed

+110
-92
lines changed

.github/workflows/node.js.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ jobs:
2525
# with:
2626
# node-version: ${{ matrix.node-version }}
2727
# cache: 'npm'
28-
- run: ./test-all.sh
28+
- run: yarn test:all

examples/vite-vue2/src/components/HelloWorld.vue

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ interface Props {
99
@Setup
1010
class App extends Define<Props> {
1111
public value = 0;
12-
public onClick() {}
12+
public constructor () {
13+
super()
14+
this.onClick = this.onClick.bind(this)
15+
}
16+
public onClick() {
17+
this.value++;
18+
}
1319
}
1420
1521
export default defineComponent({

src/on-computed.ts renamed to src/computed.ts

+19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { computed } from 'vue';
22
import { Target, TargetName } from './types';
33
import { getCurrentHookContext } from './context';
4+
import { createDefineProperty } from './property-descriptors';
5+
46

57
function compute(
68
target: Target,
@@ -35,3 +37,20 @@ export function onComputed() {
3537
const descriptor = getDescriptor(target, name)!;
3638
compute(target, name, descriptor, 'get');
3739
}
40+
41+
export function initComputed(target: object, descriptor: Map<string, PropertyDescriptor>) {
42+
const defineProperty = createDefineProperty(target);
43+
descriptor.forEach((value, key) => {
44+
let get = value.get;
45+
if (get) {
46+
get = get.bind(target);
47+
const compute = computed(get);
48+
defineProperty(key, {
49+
...value,
50+
get() {
51+
return compute.value;
52+
}
53+
});
54+
}
55+
});
56+
}

src/config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export const SETUP_OPTIONS_NAME = 'setupOptions';
22
export const SETUP_NAME = 'setup';
3+
export const SETUP_PROPERTY_DESCRIPTOR = 'setupPropertyDescriptor';

src/context.ts

+12-38
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { getCurrentInstance, type VueInstance } from './vue';
22
import { setupReference } from './setup-reference';
33
import { TargetName, Target } from './types';
4-
import { SETUP_NAME, SETUP_OPTIONS_NAME } from './config';
4+
import { SETUP_NAME, SETUP_OPTIONS_NAME, SETUP_PROPERTY_DESCRIPTOR } from './config';
5+
import {createDefineProperty } from './property-descriptors';
56

67
let currentTarget: Target | null = null;
78
let currentName: TargetName | null = null;
@@ -26,16 +27,18 @@ export function setCurrentHookName(name: TargetName | null) {
2627
export class Context {
2728
public static [SETUP_NAME] = false;
2829
public static [SETUP_OPTIONS_NAME] = new Map();
30+
public static [SETUP_PROPERTY_DESCRIPTOR] = new Map<string, PropertyDescriptor>()
2931
public static inject<T extends new (...args: any) => any>(this: T) {
3032
const _This = this;
31-
const set = getPropertyDescriptors(_This);
32-
const list = Array.from(set);
33+
const map = _This[SETUP_PROPERTY_DESCRIPTOR] as Map<string, PropertyDescriptor>;
3334
function use(target: object) {
3435
const app = new _This();
3536
const names = Object.getOwnPropertyNames(app);
37+
const defineProperty = createDefineProperty(target);
38+
3639
names.forEach(name => {
37-
if (set.has(name)) return;
38-
Object.defineProperty(target, name, {
40+
if (map.has(name)) return;
41+
defineProperty(name, {
3942
get() {
4043
return app[name];
4144
},
@@ -44,8 +47,9 @@ export class Context {
4447
}
4548
})
4649
});
47-
list.forEach(name => {
48-
Object.defineProperty(target, name, {
50+
map.forEach((value, name) => {
51+
52+
defineProperty(name, {
4953
get() {
5054
return app[name];
5155
},
@@ -56,19 +60,12 @@ export class Context {
5660
})
5761
return target as InstanceType<T>;
5862
}
59-
let ok = false;
6063
return {
6164
created() {
62-
if (ok) {
63-
ok = false;
64-
return
65-
}
6665
use(this);
6766
},
6867
setup() {
69-
const data = use({});
70-
ok = true;
71-
return data as InstanceType<T>;;
68+
return {} as InstanceType<T>;;
7269
}
7370
}
7471
}
@@ -86,26 +83,3 @@ export class Context {
8683
}
8784
}
8885
function emit() { }
89-
90-
type Descriptor = {
91-
[x: string]: TypedPropertyDescriptor<any>;
92-
} & {
93-
[x: string]: PropertyDescriptor;
94-
};
95-
96-
function getPropertyDescriptors(Target: new (...args: any) => any) {
97-
const list: Descriptor[] = [];
98-
const set = new Set<string>();
99-
while (!Target || Target !== Context) {
100-
list.unshift(Object.getOwnPropertyDescriptors(Target.prototype));
101-
Target = Object.getPrototypeOf(Target);
102-
}
103-
list.forEach(item => {
104-
Object.keys(item).forEach(key => {
105-
if (key[0] === '_' || key === 'constructor') return;
106-
set.add(key);
107-
})
108-
})
109-
110-
return set;
111-
}

src/define.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type VueInstance } from './vue';
22
import { Context } from './context';
3+
import { createDefineProperty } from './property-descriptors';
34

45
type DeepReadonly<T> = T extends object
56
? T extends Array<any>
@@ -24,10 +25,11 @@ export interface DefineConstructor {
2425
setup: typeof Context['setup']
2526
setupOptions: typeof Context['setupOptions']
2627
setupDefine: boolean;
28+
setupPropertyDescriptor: Map<string, PropertyDescriptor>;
2729
new <T extends {} = {}, E extends DefaultEmit = DefaultEmit>(...args: unknown[]): DeepReadonly<T> & DefineInstance<T, E>;
2830
}
2931

30-
export const Define: DefineConstructor = class Define extends Context {
32+
export const Define: DefineConstructor = class Define extends Context {
3133
public static setupDefine = true;
3234
} as any;
3335

@@ -36,21 +38,26 @@ export function initDefine(target: object) {
3638
if (!props) {
3739
return;
3840
}
41+
const definePropertyProps = createDefineProperty(props);
42+
const definePropertyTarget = createDefineProperty(target);
43+
3944
Object.keys(props).forEach(k => {
4045
if (typeof props[k] === 'undefined' && k in target) {
41-
Object.defineProperty(props, k, {
46+
definePropertyProps(k, {
4247
configurable: true,
4348
writable: true,
4449
value: target[k]
4550
});
4651
}
47-
defineProperty(target, k, () => {
48-
return props[k];
49-
});
52+
definePropertyTarget(k, {
53+
get() {
54+
return props[k];
55+
}
56+
})
5057
});
5158
}
5259

53-
function defineProperty<T>(o: T, p: PropertyKey, get: () => any) {
60+
function defineProperty2<T>(o: T, p: PropertyKey, get: () => any) {
5461
Object.defineProperty(o, p, {
5562
get
5663
});

src/property-descriptors.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
3+
const whitelist: string[] = ['constructor', '$props', '$emit']
4+
5+
export function getPropertyDescriptors(Target: new (...args: any) => any) {
6+
const list: PropertyDescriptor[] = [];
7+
const map = new Map<string, PropertyDescriptor>()
8+
while (Target && Target.prototype) {
9+
list.unshift(Object.getOwnPropertyDescriptors(Target.prototype));
10+
Target = Object.getPrototypeOf(Target);
11+
}
12+
13+
list.forEach(item => {
14+
Object.keys(item).forEach(key => {
15+
if (key[0] === '_' || whitelist.includes(key)) {
16+
delete item[key];
17+
return
18+
}
19+
map.set(key, item[key]);
20+
})
21+
});
22+
23+
return map;
24+
}
25+
26+
export function createDefineProperty(target: object) {
27+
return (key: PropertyKey, value: PropertyDescriptor) => {
28+
Object.defineProperty(target, key, value);
29+
}
30+
}

src/setup.ts

+23-45
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,53 @@
11
import { reactive } from 'vue';
22
import { TargetName, PassOnToCallback } from './types';
33
import { setCurrentHookName, setCurrentHookTarget, Context } from './context';
4-
import { SETUP_OPTIONS_NAME, SETUP_NAME } from './config';
5-
import { onComputed } from './on-computed';
4+
import { SETUP_OPTIONS_NAME, SETUP_NAME, SETUP_PROPERTY_DESCRIPTOR } from './config';
5+
import { onComputed, initComputed } from './computed';
66
import { getOptions, getSetupOptions, setOptions } from './options';
77
import { initDefine } from './define';
88
import { setupReference } from './setup-reference';
9+
import { getPropertyDescriptors } from './property-descriptors';
910

1011
export type TargetConstructor = {
1112
inject: typeof Context['inject']
1213
setup: typeof Context['setup']
1314
setupOptions: typeof Context['setupOptions']
15+
setupPropertyDescriptor: Map<string, PropertyDescriptor>;
1416
new(...args: any[]): any;
1517
};
1618

17-
interface Item {
18-
name: TargetName;
19-
hook: PassOnToCallback;
20-
}
19+
2120

2221
function initHook<T extends object>(target: T) {
2322
setCurrentHookTarget(target);
24-
const set = new Set<TargetName>();
25-
const options = getOptions(target.constructor);
26-
const special: Item[] = [];
27-
const plainArr: Item[] = [];
28-
options.forEach((names, hook) => {
29-
if (hook !== onComputed) {
30-
return names.forEach((name) => {
31-
plainArr.push({
32-
hook,
33-
name,
34-
});
35-
});
23+
const Target: TargetConstructor = target.constructor as any;
24+
const options = getOptions(Target);
25+
const propertyDescriptor = Target.setupPropertyDescriptor;
26+
27+
// bind this
28+
propertyDescriptor.forEach(({ value, writable }, key) => {
29+
if (typeof value === 'function' && writable) {
30+
target[key] = value.bind(target);
3631
}
37-
names.forEach((name) => {
38-
special.push({
39-
hook,
40-
name,
41-
});
42-
});
43-
});
32+
})
4433

4534
// init props
4635
if (target.constructor['setupDefine']) {
4736
initDefine(target);
4837
}
4938

5039
// init computed
51-
special.forEach((item) => {
52-
initName(item);
53-
});
40+
initComputed(target, propertyDescriptor);
5441

55-
// init plain hooks
56-
plainArr.forEach((item) => {
57-
initName(item);
58-
});
42+
// init PassOnTo
43+
options.forEach((names, hook) => {
44+
return names.forEach((name) => {
45+
initName(name, hook);
46+
});
47+
})
5948
setCurrentHookTarget(null);
6049

61-
function initName({ name, hook }: Item) {
62-
if (!set.has(name) && typeof target[name] === 'function') {
63-
target[name] = target[name].bind(target);
64-
set.add(name);
65-
}
50+
function initName(name: TargetName, hook: PassOnToCallback) {
6651
setCurrentHookName(name);
6752
hook(target[name]);
6853
setCurrentHookName(null);
@@ -71,17 +56,10 @@ function initHook<T extends object>(target: T) {
7156
}
7257

7358
function Setup<T extends TargetConstructor>(Target: T) {
74-
const descriptors = Object.getOwnPropertyDescriptors(Target.prototype);
75-
76-
Object.keys(descriptors).filter((k) => {
77-
const descriptor = descriptors[k];
78-
if (descriptor.get) {
79-
setOptions(onComputed, k);
80-
}
81-
});
8259
class Setup extends Target {
8360
public static [SETUP_OPTIONS_NAME] = getSetupOptions(Target);
8461
public static [SETUP_NAME] = true;
62+
public static [SETUP_PROPERTY_DESCRIPTOR] = getPropertyDescriptors(Target);
8563
public constructor(...args: any[]) {
8664
setupReference.count();
8765
super(...args);

tests/context.spec.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11

22
import { test, assert } from 'vitest';
3-
import { Context } from 'vue-class-setup';
3+
import { Setup, Context } from 'vue-class-setup';
44

55

66
test('Context', () => {
7+
8+
@Setup
79
class Base extends Context {
810
public age = 100;
911
public get ageText() {
@@ -14,6 +16,7 @@ test('Context', () => {
1416
}
1517
};
1618

19+
@Setup
1720
class Base2 extends Base {
1821
public ok = true;
1922
public num = 100;

0 commit comments

Comments
 (0)