Skip to content

Commit 72a82c4

Browse files
committed
增加Computed装饰器
1 parent 87187a8 commit 72a82c4

12 files changed

+379
-17
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,19 @@ npm install @kaokei/use-vue-service @kaokei/di
4343

4444
## todo
4545

46-
支持ssr功能 https://pinia.vuejs.org/zh/ssr/
46+
1. 支持ssr功能 https://pinia.vuejs.org/zh/ssr/
47+
48+
1. getter支持缓存,使用computed支持缓存
49+
50+
应该在use-vue-service中实现
51+
52+
https://yuanbao.tencent.com/bot/app/share/chat/tUbGmhHdY1Ta
53+
54+
2. 优化 https://github.com/kaokei/utils/blob/main/src/index.ts 的文档
55+
56+
57+
```ts
58+
function useAppService<T>(token: interfaces.ServiceIdentifier<T>, app: any) {
59+
return app.runWithContext(() => useService(token));
60+
}
61+
```

docs/notes/DeepSeek支持SSR的方案.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,9 @@ if (import.meta.server) {
185185
9. 需要研究container.snapshort是否容易实现,以及是否包含子容器的snapshort
186186

187187
10. 需要研究angular是怎么支持ssr的
188+
189+
11. container需要有一个唯一ID,binding需要一个唯一ID。
190+
需要提供一个serializeState方法,可以序列化所有的container-binding-cache对象。
191+
需要在useService方法中根据容器唯一ID,binding唯一ID,从水合数据中找到对应的初始化数据。
192+
可能需要在useService/useRootService中向ssr context中注册cache对象,ssr context --> container id --> binding id --> cache
193+
通过这样的结构,方便序列化和反序列化

src/component-container.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { ComponentInternalInstance } from 'vue';
2-
import { Container } from '@kaokei/di';
1+
import type { ComponentInternalInstance } from 'vue';
2+
import type { Container } from '@kaokei/di';
33

4-
const key = Symbol('container');
4+
const map = new WeakMap<ComponentInternalInstance, Container>();
55

66
export function setContainer(
77
component: ComponentInternalInstance,
88
container: Container
99
) {
10-
(component as any)[key] = container;
10+
map.set(component, container);
1111
}
1212

1313
export function getContainer(component: ComponentInternalInstance) {
14-
return (component as any)[key];
14+
return map.get(component);
1515
}

src/computed.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { computed, reactive } from 'vue';
2+
3+
const map = new WeakMap<object, Map<string, any>>();
4+
5+
export function Computed(
6+
_target: any,
7+
key: string,
8+
descriptor: PropertyDescriptor
9+
) {
10+
const originalGet = descriptor.get!;
11+
const originalSet = descriptor.set;
12+
13+
return {
14+
configurable: true,
15+
enumerable: true,
16+
get<T, S = T>() {
17+
const that = reactive(this);
18+
19+
if (!map.has(that)) {
20+
map.set(that, new Map());
21+
}
22+
23+
const keyRefMap = map.get(that)!;
24+
let computedRef = keyRefMap.get(key);
25+
26+
if (!computedRef) {
27+
if (originalSet) {
28+
computedRef = computed({
29+
get: () => originalGet.call(that) as T,
30+
set: (v: S) => originalSet.call(that, v),
31+
});
32+
} else {
33+
computedRef = computed<T>(() => originalGet.call(that));
34+
}
35+
keyRefMap.set(key, computedRef);
36+
}
37+
38+
return computedRef.value;
39+
},
40+
set<T>(value: T) {
41+
const that = reactive(this);
42+
const computedRef = map.get(that)?.get(key);
43+
44+
if (computedRef && originalSet) {
45+
computedRef.value = value;
46+
}
47+
},
48+
} as PropertyDescriptor;
49+
}

src/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Container, Token } from '@kaokei/di';
2-
import { ComponentInternalInstance } from 'vue';
1+
import { type Container, Token } from '@kaokei/di';
2+
import type { ComponentInternalInstance } from 'vue';
33
import { createContainer } from './utils';
44

55
// 给依赖注入库使用的token

src/core.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
hasInjectionContext,
77
App,
88
} from 'vue';
9-
import { Container } from '@kaokei/di';
9+
import type { Container } from '@kaokei/di';
1010
import type { Newable, CommonToken } from '@kaokei/di';
1111
import { createContainer } from './utils';
1212
import { CONTAINER_TOKEN, DEFAULT_CONTAINER } from './constants';
@@ -23,8 +23,7 @@ function bindProviders(container: Container, providers: Provider) {
2323
providers(container);
2424
} else {
2525
for (let i = 0; i < providers.length; i++) {
26-
const s = providers[i];
27-
container.bind(s).toSelf();
26+
container.bind(providers[i]).toSelf();
2827
}
2928
}
3029
}
@@ -56,9 +55,7 @@ function getCurrentContainer(): Container | undefined {
5655
// 直接借助vue的inject方法
5756
function getProvideContainer(): Container {
5857
if (hasInjectionContext()) {
59-
const token = CONTAINER_TOKEN;
60-
const defaultValue = DEFAULT_CONTAINER;
61-
return inject(token, defaultValue);
58+
return inject(CONTAINER_TOKEN, DEFAULT_CONTAINER);
6259
} else {
6360
throw new Error('getProvideContainer 只能在 setup 中使用');
6461
}
@@ -74,7 +71,7 @@ export function useRootService<T>(token: CommonToken<T>) {
7471
}
7572

7673
export function useAppService<T>(token: CommonToken<T>, app: App) {
77-
return app.runWithContext(() => useService(token));
74+
return app.runWithContext(() => getProvideContainer().get(token));
7875
}
7976

8077
export function declareProviders(providers: FunctionProvider): void;

src/find-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { CommonToken } from '@kaokei/di';
2-
import {
2+
import type {
33
VNode,
44
VNodeChild,
55
VNodeArrayChildren,

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export {
1111
useAppService,
1212
useRootService,
1313
} from './core';
14+
15+
export { Computed } from './computed';

src/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Container } from '@kaokei/di';
2-
import { reactive, ComponentInternalInstance, markRaw } from 'vue';
2+
import { reactive, type ComponentInternalInstance, markRaw } from 'vue';
33
import { setContainer } from './component-container';
44
import { CURRENT_COMPONENT, CURRENT_CONTAINER } from './constants';
55

@@ -20,6 +20,7 @@ export function createContainer(
2020
}
2121
if (instance) {
2222
// 组件实例绑定容器-方便后续通过组件实例获取容器对象
23+
// 目前只是提供给findService使用
2324
setContainer(instance, container);
2425
// 容器绑定组件实例-方便后续通过依赖注入获取当前组件实例
2526
container.bind(CURRENT_COMPONENT).toConstantValue(markRaw(instance));

tests/test20/demo.test.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { Computed } from '@/index';
2+
import { reactive } from 'vue';
3+
4+
describe('test20', () => {
5+
it('get DemoService instance', async () => {
6+
class DemoService {
7+
public id = 1;
8+
public name = 'demo';
9+
10+
public get age() {
11+
return this.getAge() + 100;
12+
}
13+
14+
public getAge() {
15+
return this.id + 10;
16+
}
17+
}
18+
19+
const spyOnGetAge = vi.spyOn(DemoService.prototype, 'getAge');
20+
21+
const demo = new DemoService();
22+
const reactiveDemo = demo;
23+
24+
expect(reactiveDemo).toBeInstanceOf(DemoService);
25+
26+
expect(reactiveDemo.id).toBe(1);
27+
expect(reactiveDemo.name).toBe('demo');
28+
29+
expect(spyOnGetAge).not.toHaveBeenCalled();
30+
31+
expect(reactiveDemo.age).toBe(111);
32+
33+
expect(spyOnGetAge).toHaveBeenCalledOnce();
34+
35+
reactiveDemo.id = 2;
36+
37+
expect(spyOnGetAge).toHaveBeenCalledOnce();
38+
39+
expect(reactiveDemo.age).toBe(112);
40+
41+
expect(spyOnGetAge).toHaveBeenCalledTimes(2);
42+
43+
expect(reactiveDemo.age).toBe(112);
44+
45+
expect(spyOnGetAge).toHaveBeenCalledTimes(3);
46+
});
47+
48+
it('get DemoService instance', async () => {
49+
class DemoService {
50+
public id = 1;
51+
public name = 'demo';
52+
53+
@Computed
54+
public get age() {
55+
return this.getAge() + 100;
56+
}
57+
58+
public getAge() {
59+
return this.id + 10;
60+
}
61+
}
62+
63+
const spyOnGetAge = vi.spyOn(DemoService.prototype, 'getAge');
64+
65+
const demo = new DemoService();
66+
const reactiveDemo = demo;
67+
68+
expect(reactiveDemo).toBeInstanceOf(DemoService);
69+
70+
expect(reactiveDemo.id).toBe(1);
71+
expect(reactiveDemo.name).toBe('demo');
72+
73+
expect(spyOnGetAge).not.toHaveBeenCalled();
74+
75+
expect(reactiveDemo.age).toBe(111);
76+
77+
expect(spyOnGetAge).toHaveBeenCalledOnce();
78+
79+
reactiveDemo.id = 2;
80+
81+
expect(spyOnGetAge).toHaveBeenCalledOnce();
82+
83+
expect(reactiveDemo.age).toBe(111);
84+
85+
expect(spyOnGetAge).toHaveBeenCalledTimes(1);
86+
87+
expect(reactiveDemo.age).toBe(111);
88+
89+
expect(spyOnGetAge).toHaveBeenCalledTimes(1);
90+
});
91+
92+
it('get DemoService instance', async () => {
93+
class DemoService {
94+
public id = 1;
95+
public name = 'demo';
96+
97+
@Computed
98+
public get age() {
99+
return this.getAge() + 100;
100+
}
101+
102+
public getAge() {
103+
return this.id + 10;
104+
}
105+
}
106+
107+
const spyOnGetAge = vi.spyOn(DemoService.prototype, 'getAge');
108+
109+
const demo = new DemoService();
110+
const reactiveDemo = reactive(demo);
111+
112+
expect(reactiveDemo).toBeInstanceOf(DemoService);
113+
114+
expect(reactiveDemo.id).toBe(1);
115+
expect(reactiveDemo.name).toBe('demo');
116+
117+
expect(spyOnGetAge).not.toHaveBeenCalled();
118+
119+
expect(reactiveDemo.age).toBe(111);
120+
121+
expect(spyOnGetAge).toHaveBeenCalledOnce();
122+
123+
reactiveDemo.id = 2;
124+
125+
expect(spyOnGetAge).toHaveBeenCalledOnce();
126+
127+
expect(reactiveDemo.age).toBe(112);
128+
129+
expect(spyOnGetAge).toHaveBeenCalledTimes(2);
130+
131+
expect(reactiveDemo.age).toBe(112);
132+
133+
expect(spyOnGetAge).toHaveBeenCalledTimes(2);
134+
});
135+
136+
it('get DemoService instance', async () => {
137+
class DemoService {
138+
public id = 1;
139+
public name = 'demo';
140+
141+
@Computed
142+
public get age() {
143+
return this.getAge() + 100;
144+
}
145+
146+
@Computed
147+
public get age2() {
148+
return this.getAge() + 200;
149+
}
150+
151+
public getAge() {
152+
return this.id + 10;
153+
}
154+
}
155+
156+
const spyOnGetAge = vi.spyOn(DemoService.prototype, 'getAge');
157+
158+
const demo = new DemoService();
159+
const reactiveDemo = reactive(demo);
160+
161+
expect(reactiveDemo).toBeInstanceOf(DemoService);
162+
163+
expect(reactiveDemo.id).toBe(1);
164+
expect(reactiveDemo.name).toBe('demo');
165+
166+
expect(spyOnGetAge).not.toHaveBeenCalled();
167+
168+
expect(reactiveDemo.age).toBe(111);
169+
170+
expect(spyOnGetAge).toHaveBeenCalledOnce();
171+
172+
reactiveDemo.id = 2;
173+
174+
expect(spyOnGetAge).toHaveBeenCalledOnce();
175+
176+
expect(reactiveDemo.age).toBe(112);
177+
178+
expect(spyOnGetAge).toHaveBeenCalledTimes(2);
179+
180+
expect(reactiveDemo.age).toBe(112);
181+
182+
expect(spyOnGetAge).toHaveBeenCalledTimes(2);
183+
184+
expect(reactiveDemo.age2).toBe(212);
185+
186+
expect(spyOnGetAge).toHaveBeenCalledTimes(3);
187+
188+
expect(reactiveDemo.age2).toBe(212);
189+
190+
expect(spyOnGetAge).toHaveBeenCalledTimes(3);
191+
192+
expect(reactiveDemo.age).toBe(112);
193+
194+
expect(spyOnGetAge).toHaveBeenCalledTimes(3);
195+
196+
expect(reactiveDemo.age).toBe(112);
197+
198+
expect(spyOnGetAge).toHaveBeenCalledTimes(3);
199+
});
200+
});

0 commit comments

Comments
 (0)