Skip to content

Commit

Permalink
feat(di): 基于新版di重新重构了一般,并完善了单元测试用例
Browse files Browse the repository at this point in the history
  • Loading branch information
kaokei committed Feb 16, 2022
1 parent f203ae2 commit 845c8f9
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 62 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ npm run release patch 发布新版本
第一种:使用 rxjs 的 observable 把状态变成流,不确定是否和 reactive 有冲突
第二种:就在在父组件中判断服务的状态是否已经 ready,如果 ready 了才显示子组件,否则展示 loading

3. injectFromSelf 实际上并不是和它的名字一样
3. 本库的 inject 和 vue 提供的 inject 不一样,本库的 inject 是从当前组件开始寻找数据的

因为 vue3 自带的 inject 依赖了原型链,并且子组件的 provides 属性默认就是父组件的 provides,从而导致虽然是从当前组件的 provides 开始寻找的服务。但是实际上这个服务有可能是从父组件的 Injector 中获取的。

Expand Down
6 changes: 6 additions & 0 deletions example/services/person.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import CounterService from './counter.service';

@Injectable()
export default class Person {
@Inject()
public logger11!: Logger;

@Inject(Logger)
public logger21!: Logger;

constructor(
public logger1: Logger,
public logger2: Logger,
Expand Down
1 change: 1 addition & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function createConfig(fileSuffix) {
externalLiveBindings: false,
globals: {
vue: 'Vue',
'@kaokei/di': 'DI',
},
};

Expand Down
12 changes: 8 additions & 4 deletions src/declareProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { provide, getCurrentInstance, onUnmounted } from 'vue';
import { INJECTOR_KEY } from './constants';
import { DEFAULT_INJECTOR } from './defaultInjector';

import { injectFromSelf } from './fakeInject';
import { inject } from './fakeInject';
import { getInjector } from './utils';

/**
Expand All @@ -25,11 +25,11 @@ import { getInjector } from './utils';
export function declareProviders(providers: any[]) {
const instance = getCurrentInstance();
if (!instance) {
throw new Error('declareProviders 只能在setup内部使用');
throw new Error('declareProviders can only be used inside setup function.');
}
const parentInjector = injectFromSelf(INJECTOR_KEY, DEFAULT_INJECTOR);
const parentInjector = inject(INJECTOR_KEY, DEFAULT_INJECTOR);
if (parentInjector.uid === instance.uid) {
throw new Error('禁止重复调用declareProviders');
throw new Error('declareProviders repeatedly call.');
}
const currentInjector = getInjector(providers, parentInjector);
(<any>currentInjector).uid = instance.uid;
Expand All @@ -38,6 +38,10 @@ export function declareProviders(providers: any[]) {
currentInjector.dispose();
});

console.log('declareProviders after onUnmounted');

console.log(INJECTOR_KEY, providers, currentInjector, currentInjector.parent);

provide(INJECTOR_KEY, currentInjector);
}

Expand Down
50 changes: 4 additions & 46 deletions src/fakeInject.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,21 @@
import { getCurrentInstance } from 'vue';

/**
* 主要实现逻辑是粘贴复制与vue中
* 在原基础上增加了一个额外的参数selfInject=false
* 所以默认行为和原vue的实现一致
* 但是当selfInject=true时则与原vue实现不一致
* 从当前组件开始查找provider
*
* @export
* @param {*} key
* @param {*} defaultValue
* @param {boolean} [treatDefaultAsFactory=false]
* @param {boolean} [selfInject=false]
* @return {*}
*/
export function inject(
key: any,
defaultValue: any,
treatDefaultAsFactory = false,
selfInject = false
) {
// fallback to `currentRenderingInstance` so that this can be called in
// a functional component
export function inject(key: any, defaultValue: any) {
const instance: any = getCurrentInstance();
if (instance) {
// #2400
// to support `app.use` plugins,
// fallback to appContext's `provides` if the intance is at root
// 注意这里的 instance.provides 的原型链的最末端就是 instance.vnode.appContext.provides
const provides = selfInject
? instance.provides
: instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides;

if (provides && key in provides) {
// TS doesn't allow symbol as index type
return provides[key];
} else if (arguments.length > 1) {
return treatDefaultAsFactory && typeof defaultValue === 'function'
? defaultValue()
: defaultValue;
} else {
console.warn(`injection "${String(key)}" not found.`);
}
const provides = instance.provides;
return provides[key] || defaultValue;
} else {
console.warn(
`inject() can only be used inside setup() or functional components.`
);
}
}

/**
* 从当前组件开始查找provider
*
* @export
* @param {*} key
* @param {*} defaultValue
* @return {*}
*/
export function injectFromSelf(key: any, defaultValue: any) {
return inject(key, defaultValue, false, true);
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export { INJECTOR_KEY } from './constants';

export { DEFAULT_INJECTOR } from './defaultInjector';

export { inject, injectFromSelf } from './fakeInject';
export { inject } from './fakeInject';

export { getInjector, getServiceFromInjector } from './utils';
8 changes: 6 additions & 2 deletions src/useService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { injectFromSelf } from './fakeInject';
import { inject } from './fakeInject';

import { INJECTOR_KEY } from './constants';
import { DEFAULT_INJECTOR } from './defaultInjector';
Expand All @@ -22,10 +22,14 @@ export function useService<R, T = unknown>(
options?: any
): T extends R ? Ret<T> : Ret<R>;
export function useService(token: any, options?: any) {
const currentInjector = injectFromSelf(INJECTOR_KEY, DEFAULT_INJECTOR);
const currentInjector = inject(INJECTOR_KEY, DEFAULT_INJECTOR);
return getServiceFromInjector(currentInjector, token, options);
}

export function useRootService<R, T = unknown>(
token: T,
options?: any
): T extends R ? Ret<T> : Ret<R>;
export function useRootService(token: any, options?: any) {
return getServiceFromInjector(DEFAULT_INJECTOR, token, options);
}
4 changes: 2 additions & 2 deletions tests/App.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { mount } from '@vue/test-utils';

import TestTreeScope from '@containers/TestTreeScope.vue';

import { DECORATOR_KEYS } from '@kaokei/di';
import { DECORATOR_KEYS } from '@src/index';
const { DESIGN_PARAM_TYPES, SERVICE_PARAM_TYPES } = DECORATOR_KEYS;

import Logger from '@services/logger.service';
import Counter from '@services/counter.service';
import Person from '@services/person.service';

describe('App', () => {
describe('TestTreeScope Component', () => {
test('渲染组件、获取服务数据', async () => {
const wrapper = mount(TestTreeScope);
const Earth = wrapper.findComponent({ name: 'Earth' });
Expand Down
2 changes: 1 addition & 1 deletion tests/TestCounter.v1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const TestApp = defineComponent({
},
});

describe('Component', () => {
describe('Component 没有声明服务CounterService', () => {
test('渲染组件、获取服务数据', async () => {
const wrapper = mount(TestApp, {
props: {
Expand Down
4 changes: 2 additions & 2 deletions tests/TestCounter.v2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const TestApp = defineComponent({
},
});

describe('Component', () => {
describe('Component 声明了服务CounterService', () => {
test('渲染组件、获取服务数据', async () => {
const wrapper = mount(TestApp, {
props: {
Expand All @@ -59,7 +59,7 @@ describe('Component', () => {
expect(wrapper.find('.countNum').text()).toBe('5');
});

test('组件快照、服务共享', async () => {
test('组件快照、服务不共享', async () => {
const wrapper = mount(TestApp, {
props: {
name: 'counter2',
Expand Down
49 changes: 49 additions & 0 deletions tests/TestCounter.v3.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'reflect-metadata';

import { mount } from '@vue/test-utils';

import Counter from '@components/Counter.vue';

import CounterService from '@services/counter.service';
import { COUNTER_THEME } from '@services/service.context';

import { declareProviders, useService } from '@src/index';
import { defineComponent, isReactive } from 'vue';

const TestApp = defineComponent({
template: '<Counter :name="name" :counter="counter"></Counter>',
components: { Counter },
props: ['name'],
setup() {
declareProviders([
{
provide: COUNTER_THEME,
useValue: '#69c0ff',
},
]);
// 调用2次declareProviders就会报错
declareProviders([
CounterService, // 这里设置CounterService决定了这个服务是被缓存在当前组件所对应的Injector中的,那么如果有多个组件实例意味着服务也是多例的。
]);
const counterService = useService(CounterService);
console.log(
'isReactive(props.counterService) :>> ',
isReactive(counterService)
);
return {
counter: counterService,
};
},
});

describe('Component 声明了服务CounterService', () => {
test('渲染组件、获取服务数据', async () => {
expect(() => {
mount(TestApp, {
props: {
name: 'counter1',
},
});
}).toThrow();
});
});
108 changes: 108 additions & 0 deletions tests/TestCounter.v4.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import 'reflect-metadata';

import { mount } from '@vue/test-utils';

import Counter from '@components/Counter.vue';

import CounterService from '@services/counter.service';
import { COUNTER_THEME } from '@services/service.context';

import { declareProviders, useService } from '@src/index';
import { defineComponent, isReactive, ref } from 'vue';

const TestAComponent = defineComponent({
template:
'<div id="a-component"><Counter :name="name" :counter="counter"></Counter></div>',
components: { Counter },
setup() {
console.log('setup 111111');

declareProviders([CounterService]);

console.log('setup 2222222');

const counterService = useService(CounterService);

console.log('setup 333333');

return {
name: 'a-component',
counter: counterService,
};
},
});

const TestBComponent = defineComponent({
template:
'<div id="b-component"><Counter :name="name" :counter="counter"></Counter></div>',
components: { Counter },
setup() {
console.log('setup 444444');

declareProviders([CounterService]);

console.log('setup 5555555');

const counterService = useService(CounterService);

console.log('setup 666666');

return {
name: 'b-component',
counter: counterService,
};
},
});

const TestApp = defineComponent({
template:
'<div><button id="btn-toggle" type="button" @click="toggleFlag">点我</button><div v-if="flag"><TestAComponent/></div><div v-else><TestBComponent/></div></div>',
components: { TestAComponent, TestBComponent },
setup() {
console.log('setup 0000001');

declareProviders([
{
provide: COUNTER_THEME,
useValue: '#69c0ff',
},
]);

const flag = ref(true);

const toggleFlag = () => {
flag.value = !flag.value;
};

console.log('setup 0000002');

return {
flag,
toggleFlag,
};
},
});

describe('通过if-else来测试onUnmount事件', () => {
test('渲染组件、获取服务数据', async () => {
const wrapper = mount(TestApp);

expect(wrapper.vm.flag).toBe(true);

expect(wrapper.findComponent(TestAComponent).exists()).toBe(true);
expect(wrapper.find('#a-component').exists()).toBe(true);

expect(wrapper.findComponent(TestBComponent).exists()).toBe(false);
expect(wrapper.find('#b-component').exists()).toBe(false);

await wrapper.find('#btn-toggle').trigger('click');

expect(wrapper.vm.flag).toBe(false);

expect(wrapper.findComponent(TestAComponent).exists()).toBe(false);
expect(wrapper.find('#a-component').exists()).toBe(false);

expect(wrapper.findComponent(TestBComponent).exists()).toBe(true);
expect(wrapper.find('#b-component').exists()).toBe(true);
});
});
2 changes: 1 addition & 1 deletion tests/__snapshots__/App.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`App 组件快照、服务共享 1`] = `
exports[`TestTreeScope Component 组件快照、服务共享 1`] = `
<div>
<div>
<div>
Expand Down
2 changes: 1 addition & 1 deletion tests/__snapshots__/TestCounter.v1.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Component 组件快照、服务共享 1`] = `<div class="container" style="background: rgb(105, 192, 255);"><span class="title">counter2:</span><button class="decrementBtn" type="button"> - </button><span class="countNum">5</span><button class="incrementBtn" type="button"> + </button></div>`;
exports[`Component 没有声明服务CounterService 组件快照、服务共享 1`] = `<div class="container" style="background: rgb(105, 192, 255);"><span class="title">counter2:</span><button class="decrementBtn" type="button"> - </button><span class="countNum">5</span><button class="incrementBtn" type="button"> + </button></div>`;
2 changes: 1 addition & 1 deletion tests/__snapshots__/TestCounter.v2.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Component 组件快照、服务共享 1`] = `<div class="container" style="background: rgb(105, 192, 255);"><span class="title">counter2:</span><button class="decrementBtn" type="button"> - </button><span class="countNum">0</span><button class="incrementBtn" type="button"> + </button></div>`;
exports[`Component 声明了服务CounterService 组件快照、服务不共享 1`] = `<div class="container" style="background: rgb(105, 192, 255);"><span class="title">counter2:</span><button class="decrementBtn" type="button"> - </button><span class="countNum">0</span><button class="incrementBtn" type="button"> + </button></div>`;

0 comments on commit 845c8f9

Please sign in to comment.