8000 feat: inject and provide · nathee/vue-function-api@be9455b · GitHub
[go: up one dir, main page]

Skip to content

Commit be9455b

Browse files
committed
feat: inject and provide
BREAKING CHANGE: `provide` now receives a `key` and a `value` as the parameters. `inject` will always return a `Wrapper` now.
1 parent cb201ed commit be9455b

File tree

4 files changed

+99
-37
lines changed

4 files changed

+99
-37
lines changed

src/functions/inject.ts

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { VueConstructor } from 'vue';
22
import { getCurrentVue } from '../runtimeContext';
3-
import { ensureCurrentVMInFn } from '../helper';
4-
import { UnknownObject } from '../types/basic';
3+
import { state } from '../functions/state';
4+
import { Wrapper, ComputedWrapper } from '../wrappers';
5+
import { ensureCurrentVMInFn, isWrapper } from '../helper';
56
import { hasOwn } from '../utils';
67

7-
function resolveInject(
8-
provideKey: InjectKey,
9-
vm: InstanceType<VueConstructor>
10-
): UnknownObject | void {
8+
const UNRESOLVED_INJECT = {};
9+
export interface Key<T> extends Symbol {}
10+
11+
function resolveInject(provideKey: Key<any>, vm: InstanceType<VueConstructor>): any {
1112
let source = vm;
1213
while (source) {
1314
// @ts-ignore
@@ -18,29 +19,36 @@ function resolveInject(
1819
source = source.$parent;
1920
}
2021

21-
if (process.env.NODE_ENV !== 'production') {
22-
getCurrentVue().util.warn(`Injection "${String(provideKey)}" not found`, vm);
23-
}
22+
return UNRESOLVED_INJECT;
2423
}
2524

26-
type InjectKey = string | symbol;
27-
type ProvideOption = { [key: string]: any } | (() => { [key: string]: any });
28-
29-
export function provide(provideOption: ProvideOption) {
30-
if (!provideOption) {
31-
return;
25+
export function provide<T>(key: Key<T>, value: T | Wrapper<T>) {
26+
const vm: any = ensureCurrentVMInFn('provide');
27+
if (!vm._provided) {
28+
vm._provided = {};
3229
}
33-
34-
const vm = ensureCurrentVMInFn('provide');
35-
(vm as any)._provided =
36-
typeof provideOption === 'function' ? provideOption.call(vm) : provideOption;
30+
vm._provided[key as any] = value;
3731
}
3832

39-
export function inject(injectKey: InjectKey) {
40-
if (!injectKey) {
33+
export function inject<T>(key: Key<T>): Wrapper<T> | void {
34+
if (!key) {
4135
return;
4236
}
4337

4438
const vm = ensureCurrentVMInFn('inject');
45-
return resolveInject(injectKey, vm);
39+
const val = resolveInject(key, vm);
40+
if (val !== UNRESOLVED_INJECT) {
41+
if (isWrapper<T>(val)) {
42+
return val;
43+
}
44+
const reactiveVal = state<T>(val);
45+
return new ComputedWrapper<T>({
46+
read: () => reactiveVal,
47+
write() {
48+
getCurrentVue().util.warn(`The injectd value can't be re-assigned`, vm);
49+
},
50+
});
51+
} else if (process.env.NODE_ENV !== 'production') {
52+
getCurrentVue().util.warn(`Injection "${String(key)}" not found`, vm);
53+
}
4654
}

src/wrappers/ComputedWrapper.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ interface ComputedInternal<T> {
88
}
99

1010
export default class ComputedWrapper<V> extends AbstractWrapper<V> {
11-
constructor(private _internal: ComputedInternal<V>) {
11+
constructor(private internal: ComputedInternal<V>) {
1212
super();
1313
}
1414

1515
get value() {
16-
return this._internal.read();
16+
return this.internal.read();
1717
}
1818

1919
set value(val: V) {
20-
if (!this._internal.write) {
20+
if (!this.internal.write) {
2121
if (process.env.NODE_ENV !== 'production') {
2222
getCurrentVue().util.warn(
2323
'Computed property' +
@@ -27,7 +27,7 @@ export default class ComputedWrapper<V> extends AbstractWrapper<V> {
2727
);
2828
}
2929
} else {
30-
this._internal.write(val);
30+
this.internal.write(val);
3131
}
3232
}
3333

src/wrappers/ValueWrapper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ interface ValueInternal<T> {
66
}
77

88
export default class ValueWrapper<V> extends AbstractWrapper<V> {
9-
constructor(private _internal: ValueInternal<V>) {
9+
constructor(private internal: ValueInternal<V>) {
1010
super();
1111
}
1212

1313
get value() {
14-
return this._internal.$$state;
14+
return this.internal.$$state;
1515
}
1616

1717
set value(v: V) {
18-
this._internal.$$state = v;
18+
this.internal.$$state = v;
1919
}
2020

2121
exposeToDevtool() {

test/functions/inject.spec.js

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,19 @@ beforeEach(() => {
2222
});
2323

2424
describe('Hooks provide/inject', () => {
25+
beforeEach(() => {
26+
warn = jest.spyOn(global.console, 'error').mockImplementation(() => null);
27+
});
28+
afterEach(() => {
29+
warn.mockRestore();
30+
});
31+
2532
it('should work', () => {
2633
new Vue({
2734
template: `<child/>`,
2835
setup() {
29-
provide({
30-
foo: 1,
31-
bar: false,
32-
});
36+
provide('foo', 1);
37+
provide('bar', false);
3338
},
3439
components: {
3540
child: {
@@ -45,19 +50,18 @@ describe('Hooks provide/inject', () => {
4550
});
4651

4752
it('should work for reactive value', done => {
53+
const Msg = Symbol();
4854
const app = new Vue({
4955
template: `<child/>`,
5056
setup() {
51-
provide({
52-
msg: value('hello'),
53-
});
57+
provide(Msg, value('hello'));
5458
},
5559
components: {
5660
child: {
5761
template: `<div>{{ msg }}</div>`,
5862
setup() {
5963
return {
60-
msg: inject('msg'),
64+
msg: inject(Msg),
6165
};
6266
},
6367
},
@@ -69,4 +73,54 @@ describe('Hooks provide/inject', () => {
6973
expect(app.$el.textContent).toBe('bar');
7074
}).then(done);
7175
});
76+
77+
it('should return wrapper values', done => {
78+
const State = Symbol();
79+
const app = new Vue({
80+
template: `<child/>`,
81+
setup() {
82+
provide(State, { msg: 'foo' });
83+
},
84+
components: {
85+
child: {
86+
template: `<div>{{ state.msg }}</div>`,
87+
setup() {
88+
const obj = inject(State);
89+
expect(obj.value.msg).toBe('foo');
90+
91+
return {
92+
state: obj,
93+
};
94+
},
95+
},
96+
},
97+
}).$mount();
98+
99+
app.$children[0].state.msg = 'bar';
100+
waitForUpdate(() => {
101+
expect(app.$el.textContent).toBe('bar');
102+
}).then(done);
103+
});
104+
105+
it('should warn when assign to a injected value', () => {
106+
const State = Symbol();
107+
new Vue({
108+
template: `<child/>`,
109+
setup() {
110+
provide(State, { msg: 'foo' });
111+
},
112+
components: {
113+
child: {
114+
setup() {
115+
const obj = inject(State);
116+
expect(obj.value.msg).toBe('foo');
117+
obj.value = {};
118+
expect(warn.mock.calls[0][0]).toMatch(
119+
"[Vue warn]: The injectd value can't be re-assigned."
120+
);
121+
},
122+
},
123+
},
124+
}).$mount();
125+
});
72126
});

0 commit comments

Comments
 (0)
0