Skip to content

Commit c4222a0

Browse files
committed
初步完善注入组件的逻辑
1 parent 05f35b8 commit c4222a0

File tree

12 files changed

+232
-80
lines changed

12 files changed

+232
-80
lines changed

demo/App.vue

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,7 @@
11
<script setup lang="ts">
2-
import HelloWorld from './components/HelloWorld.vue';
2+
import DemoComp1 from './components/demo1/DemoComp.vue';
33
</script>
44

55
<template>
6-
<div>
7-
<a href="https://vite.dev" target="_blank">
8-
<img src="/vite.svg" class="logo" alt="Vite logo" />
9-
</a>
10-
<a href="https://vuejs.org/" target="_blank">
11-
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
12-
</a>
13-
</div>
14-
<HelloWorld msg="Vite + Vue" />
6+
<DemoComp1 msg="Vite + Vue" />
157
</template>
16-
17-
<style scoped>
18-
.logo {
19-
height: 6em;
20-
padding: 1.5em;
21-
will-change: filter;
22-
transition: filter 300ms;
23-
}
24-
.logo:hover {
25-
filter: drop-shadow(0 0 2em #646cffaa);
26-
}
27-
.logo.vue:hover {
28-
filter: drop-shadow(0 0 2em #42b883aa);
29-
}
30-
</style>

demo/assets/vue.svg

Lines changed: 0 additions & 1 deletion
This file was deleted.

demo/components/demo1/DemoComp.vue

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue';
3+
import { declareProviders, useService } from '../../../src/index';
4+
import { DemoService } from './DemoService';
5+
6+
defineProps<{ msg: string }>();
7+
8+
declareProviders([DemoService]);
9+
10+
const count = ref(0);
11+
12+
const service = useService(DemoService);
13+
</script>
14+
15+
<template>
16+
<div>
17+
<h1>{{ msg }}</h1>
18+
19+
<div>
20+
<button type="button" @click="count++">count is {{ count }}</button>
21+
</div>
22+
23+
<div>
24+
<button type="button" @click="service.changeName">
25+
service name is {{ service.name }}
26+
</button>
27+
</div>
28+
29+
<div>
30+
<button type="button" @click="service.increaseAge">
31+
service age is {{ service.age }}
32+
</button>
33+
</div>
34+
</div>
35+
</template>

demo/components/demo1/DemoService.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export class DemoService {
2+
public name = 'demo';
3+
public age = 100;
4+
5+
public increaseAge() {
6+
this.age++;
7+
}
8+
9+
public changeName() {
10+
this.name = `${this.name}-${this.age}`;
11+
}
12+
}

demo/main.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { createApp } from 'vue'
2-
import './style.css'
3-
import App from './App.vue'
1+
import 'reflect-metadata';
2+
import { createApp } from 'vue';
3+
import './style.css';
4+
import App from './App.vue';
45

5-
createApp(App).mount('#app')
6+
createApp(App).mount('#app');

index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
65
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
76
<title>Vite + Vue + TS</title>
87
</head>

public/vite.svg

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/index.ts

Lines changed: 127 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,165 @@
1-
import { provide, getCurrentInstance, onUnmounted, reactive, ref } from 'vue';
2-
3-
import { Container } from 'inversify';
4-
5-
const CONTAINER_KEY = 'USE_VUE_SERVICE_CONTAINER_KEY';
1+
import {
2+
provide,
3+
inject,
4+
getCurrentInstance,
5+
onUnmounted,
6+
reactive,
7+
ref,
8+
hasInjectionContext,
9+
type Component,
10+
} from 'vue';
11+
import { Container, ContainerModule, type interfaces } from 'inversify';
612

13+
const CONTAINER_TOKEN = 'USE_VUE_SERVICE_CONTAINER_TOKEN';
14+
export function createToken<T>(desc: string): interfaces.ServiceIdentifier<T> {
15+
return Symbol.for(desc);
16+
}
17+
export const COMPONENT_TOKEN = createToken<Component>(
18+
'USE_VUE_SERVICE_COMPONENT_TOKEN'
19+
);
720
const DEFAULT_CONTAINER_OPTIONS = {
21+
reactive: true,
822
autoBindInjectable: false,
923
defaultScope: 'Singleton',
1024
skipBaseClassChecks: false,
1125
};
12-
13-
function createContainer(parent?: Container, options?: any) {
26+
function getOptions(options: any) {
27+
return Object.assign({}, DEFAULT_CONTAINER_OPTIONS, options);
28+
}
29+
function makeReactiveObject(_: any, obj: any) {
30+
if (typeof obj === 'object') {
31+
return reactive(obj);
32+
} else {
33+
return ref(obj);
34+
}
35+
}
36+
function createContainer(parent?: Container, opts?: any) {
37+
let container: Container;
38+
const options = getOptions(opts);
1439
if (parent) {
15-
return parent.createChild(options);
40+
container = parent.createChild(options);
41+
} else {
42+
container = new Container(options);
1643
}
17-
return new Container(options);
44+
if (opts?.instance) {
45+
container.bind(COMPONENT_TOKEN).toConstantValue(opts.instance);
46+
}
47+
return options?.reactive ? reactiveContainer(container) : container;
48+
}
49+
function reactiveContainer(container: Container) {
50+
const originalBind = container.bind;
51+
const newBind = (serviceIdentifier: any) => {
52+
const bindingToSyntax = originalBind.call(container, serviceIdentifier);
53+
const protos = Object.getPrototypeOf(bindingToSyntax);
54+
const methods = Object.getOwnPropertyNames(protos).filter(
55+
p => typeof protos[p] === 'function' && p.indexOf('to') === 0
56+
);
57+
for (let i = 0; i < methods.length; i++) {
58+
const method = methods[i];
59+
const originalMethod = (protos as any)[method];
60+
(bindingToSyntax as any)[method] = (...args: any[]) => {
61+
const result = originalMethod.call(bindingToSyntax, ...args);
62+
if (result?.onActivation) {
63+
result.onActivation(makeReactiveObject);
64+
}
65+
return result;
66+
};
67+
}
68+
return bindingToSyntax;
69+
};
70+
container.bind = newBind as any;
71+
return container;
1872
}
1973
function bindContainer(container: Container, providers: any) {
20-
if (typeof providers === 'function') {
74+
if (providers instanceof ContainerModule) {
75+
container.load(providers);
76+
} else if (typeof providers === 'function') {
2177
providers(container);
2278
} else {
2379
for (let i = 0; i < providers.length; i++) {
2480
const s = providers[i];
2581
container.bind(s).toSelf();
2682
}
2783
}
28-
container.onActivation('_', (_: any, instance: any) => {
29-
return typeof instance === 'object' ? reactive(instance) : ref(instance);
30-
});
3184
}
32-
33-
const DEFAULT_CONTAINER = createContainer();
34-
35-
function getServiceFromContainer(container: Container, token: any) {
85+
function getServiceFromContainer<T>(
86+
container: Container,
87+
token: interfaces.ServiceIdentifier<T>
88+
) {
3689
return container.get(token);
3790
}
38-
39-
function injectFromSelf(key: any, defaultValue: any) {
91+
function getCurrentContainer() {
4092
const instance: any = getCurrentInstance();
4193
if (instance) {
94+
const token = CONTAINER_TOKEN;
4295
const provides = instance.provides;
43-
return provides[key] || defaultValue;
96+
if (provides.hasOwnProperty(token)) {
97+
return provides[token];
98+
}
4499
} else {
45100
console.warn(
46-
`inject() can only be used inside setup() or functional components.`
101+
`declareProviders can only be used inside setup() or functional components.`
102+
);
103+
}
104+
}
105+
function getContextContainer() {
106+
if (hasInjectionContext()) {
107+
const token = CONTAINER_TOKEN;
108+
const defaultValue = DEFAULT_CONTAINER;
109+
const instance: any = getCurrentInstance();
110+
if (instance) {
111+
const provides = instance.provides;
112+
return provides[token] || defaultValue;
113+
} else {
114+
return inject(token, defaultValue);
115+
}
116+
} else {
117+
console.warn(
118+
`declareProviders and useService can only be used inside setup() or functional components.`
47119
);
48120
}
49121
}
50122

51-
export function useService(token: any) {
52-
const currentContainer = injectFromSelf(CONTAINER_KEY, DEFAULT_CONTAINER);
53-
return getServiceFromContainer(currentContainer, token);
123+
const DEFAULT_CONTAINER = createContainer();
124+
export function useService<T>(token: interfaces.ServiceIdentifier<T>) {
125+
const container = getContextContainer();
126+
return getServiceFromContainer(container, token);
54127
}
55-
export function useRootService(token: any) {
128+
export function useRootService<T>(token: interfaces.ServiceIdentifier<T>) {
56129
return getServiceFromContainer(DEFAULT_CONTAINER, token);
57130
}
58-
59131
export function declareProviders(providers: any, options?: any) {
60-
const instance = getCurrentInstance();
61-
if (!instance) {
62-
throw new Error('declareProviders can only be used inside setup function.');
63-
}
64-
const parentContainer = injectFromSelf(CONTAINER_KEY, DEFAULT_CONTAINER);
65-
if (parentContainer.uid === instance.uid) {
66-
throw new Error('declareProviders can only be called once.');
132+
const currentContainer = getCurrentContainer();
133+
if (currentContainer) {
134+
bindContainer(currentContainer, providers);
135+
} else {
136+
const parent = getContextContainer();
137+
if (parent) {
138+
const instance = getCurrentInstance();
139+
const container = createContainer(parent, { instance, ...options });
140+
bindContainer(container, providers);
141+
onUnmounted(() => {
142+
container.unbindAll();
143+
});
144+
provide(CONTAINER_TOKEN, container);
145+
}
67146
}
68-
const finalOptions = Object.assign({}, DEFAULT_CONTAINER_OPTIONS, options);
69-
const currentContainer = createContainer(parentContainer, finalOptions);
70-
bindContainer(currentContainer, providers);
71-
72-
onUnmounted(() => {
73-
currentContainer.unbindAll();
74-
});
75-
76-
(currentContainer as any).uid = instance.uid;
77-
provide(CONTAINER_KEY, currentContainer);
78147
}
79-
80148
export function declareRootProviders(providers: any) {
81149
bindContainer(DEFAULT_CONTAINER, providers);
82150
}
151+
export function declareAppProviders(providers: any, options?: any) {
152+
return (app: any) => {
153+
const appContainer = getContextContainer();
154+
if (appContainer) {
155+
bindContainer(appContainer, providers);
156+
} else {
157+
const container = createContainer(DEFAULT_CONTAINER, options);
158+
bindContainer(container, providers);
159+
app.onUnmounted(() => {
160+
container.unbindAll();
161+
});
162+
app.provide(CONTAINER_TOKEN, container);
163+
}
164+
};
165+
}

tests/DemoComp.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { DemoService } from './DemoService';
3+
import { declareProviders, useService } from '../src/index';
4+
5+
defineProps({
6+
msg: String,
7+
});
8+
9+
declareProviders([DemoService]);
10+
11+
const service: any = useService(DemoService);
12+
13+
console.log('DemoService => ', service);
14+
</script>
15+
16+
<template>
17+
<div>
18+
<div class="msg">{{ msg }}</div>
19+
<div class="name">{{ service.name }}</div>
20+
<div class="age">{{ service.age }}</div>
21+
</div>
22+
</template>

tests/DemoService.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export class DemoService {
2+
public name = 'demo';
3+
public age = 100;
4+
5+
public increaseAge() {
6+
this.age++;
7+
}
8+
9+
public changeName() {
10+
this.name = `${this.name}-${this.age}`;
11+
}
12+
}

tests/demo.test.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
describe('abc', () => {
2-
test('abc 001', () => {
3-
expect(1).toBe(1);
1+
import 'reflect-metadata';
2+
import { mount } from '@vue/test-utils';
3+
import DemoComp from './DemoComp.vue';
4+
5+
describe('DemoComp', () => {
6+
it('get DemoService instance', () => {
7+
const msg = 'Hello world';
8+
const wrapper = mount(DemoComp, {
9+
props: {
10+
msg,
11+
},
12+
});
13+
expect(wrapper.get('.msg').text()).toBe(msg);
14+
expect(wrapper.get('.name').text()).toBe('demo');
15+
expect(wrapper.get('.age').text()).toBe('100');
416
});
517
});

tsconfig.app.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
"noFallthroughCasesInSwitch": true,
2323
"noUncheckedSideEffectImports": true
2424
},
25-
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
25+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
26+
"exclude": ["src/**/*.ignore.ts"]
2627
}

0 commit comments

Comments
 (0)