Skip to content
Permalink
Browse files
feat: provide/inject (close #4029)
  • Loading branch information
yyx990803 committed Feb 20, 2017
1 parent 1861ee9 commit f916bcf37105903290ad2353db9a9436536d6859
Showing with 153 additions and 0 deletions.
  1. +2 鈭0 src/core/instance/init.js
  2. +24 鈭0 src/core/instance/inject.js
  3. +127 鈭0 test/unit/features/options/inject.spec.js
@@ -4,6 +4,7 @@ import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { initInjections } from './inject'
import { initLifecycle, callHook } from './lifecycle'
import { mergeOptions } from '../util/index'

@@ -42,6 +43,7 @@ export function initMixin (Vue: Class<Component>) {
initRender(vm)
callHook(vm, 'beforeCreate')
initState(vm)
initInjections(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
@@ -0,0 +1,24 @@
export function initInjections (vm) {
const { provide, inject } = vm.$options
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
if (inject) {
const isArray = Array.isArray(inject)
const keys = isArray ? inject : Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const provideKey = isArray ? key : inject[key]
let source = vm
while (source) {
if (source._provided && source._provided[provideKey]) {
vm[key] = source._provided[provideKey]
break
}
source = source.$parent
}
}
}
}
@@ -0,0 +1,127 @@
import Vue from 'vue'

describe('Options provide/inject', () => {
let injected
const injectedComp = {
inject: ['foo', 'bar'],
render () {},
created () {
injected = [this.foo, this.bar]
}
}

beforeEach(() => {
injected = null
})

it('should work', () => {
new Vue({
template: `<child/>`,
provide: {
foo: 1,
bar: 2
},
components: {
child: {
template: `<injected-comp/>`,
components: {
injectedComp
}
}
}
}).$mount()

expect(injected).toEqual([1, 2])
})

it('should use closest parent', () => {
new Vue({
template: `<child/>`,
provide: {
foo: 1,
bar: 2
},
components: {
child: {
provide: {
foo: 3
},
template: `<injected-comp/>`,
components: {
injectedComp
}
}
}
}).$mount()

expect(injected).toEqual([3, 2])
})

it('provide function', () => {
new Vue({
template: `<child/>`,
data: {
a: 1,
b: 2
},
provide () {
return {
foo: this.a,

This comment has been minimized.

Copy link
@Akryum

Akryum Feb 20, 2017

Member

If a is an object, will it be reactive in the component that gets injection?

This comment has been minimized.

Copy link
@yyx990803

yyx990803 Feb 20, 2017

Author Member

Yes. However the injection binding itself is one-time, i.e. if you replace this.a it won't have any effect

This comment has been minimized.

Copy link
@pbastowski

pbastowski Feb 26, 2017

Is there a technical or architectural reason for not keeping the injected object reactive after reassignment?

In some cases I wish to reassign a key fully, without having to, say, import the original object definition, or refer to it through $root. I do know that I can change key values within the object while retaining reactivity, but sometimes it is just easier to replace the whole object.

As an example, I have a "settings" key that gets injected into a Settings Form component. I fully reassign the injected "settings" object when the user clicks Update in the settings form.

The "store" (not Vuex) is provided in the initial Vue instance

const store = {
    settings: { ... }
}

new Vue({
    provide: state,
})

and then it's keys are injected into components, as required.

const SettingsForm = {
    name: 'settings-form',
    inject: ['settings'],
    data() { return { 
        newSettings: {} 
    }},
    methods: {
        handleUpdate() {
            this.settings = this.newSettings
        }
    }
}

Sometimes a key needs to be fully replaced, which is my use case, when the user updates the settings by clicking the Update button, see handleUpdate, above.

BTW, I know Vuex exists, but I don't really like the extra boilerplate involved with creating getters/setters for each field that I wish to use. It seems to defeat the simplicity of Vue's two way binding between state and input controls, when I have to manually code "@input" and ":value", in addition to the getters/setters.

Sorry for the long note, just wanted to get my point across. Should my comment, perhaps, be moved to an issue?

Thanks for Vue!

This comment has been minimized.

Copy link
@posva

posva Feb 26, 2017

Member

It may be to keep things simpler. As you said you can simply provide the whole state: provide: { state } and then inject it. If you want a better syntax, you can create a plugin that allows that for you.

This comment has been minimized.

Copy link
@pbastowski

pbastowski Feb 26, 2017

Thanks for the quick answer and the suggested workaround. I have created such a plugin as you mentioned already, but, would rather deprecate it in favour of easy to use built-in functionality.

Keeping things simple is a good idea, however keeping things congruent is also important. To me, it feels un-natural for a reassignment of a reactive object in Vue 2 to not result in another reactive object. Other reactive object reassignments, such as to objects declared in the data option or in the Vuex "store.state", result in a reactive object, right?

bar: this.b
}
},
components: {
child: {
template: `<injected-comp/>`,
components: {
injectedComp
}
}
}
}).$mount()

expect(injected).toEqual([1, 2])
})

it('inject with alias', () => {
const injectAlias = {
inject: {
baz: 'foo',
qux: 'bar'
},
render () {},
created () {
injected = [this.baz, this.qux]
}
}

new Vue({
template: `<child/>`,
provide: {
foo: 1,
bar: 2
},
components: {
child: {
template: `<inject-alias/>`,
components: {
injectAlias
}
}
}
}).$mount()

expect(injected).toEqual([1, 2])
})

it('self-inject', () => {
const vm = new Vue({
provide: {
foo: 1
},
inject: ['foo']
})

expect(vm.foo).toBe(1)
})
})

3 comments on commit f916bcf

@linde12

This comment has been minimized.

Copy link

@linde12 linde12 replied Mar 3, 2017

Looking forward to this. Been missing this feature as i come from React. When using React i used a plugin called redux-form which depends on context. I want to try to make something similar to Vue once this is ready! Great work!

@LinusBorg

This comment has been minimized.

Copy link
Member

@LinusBorg LinusBorg replied Mar 3, 2017

This is already in the latest 2.2 release ...

@linde12

This comment has been minimized.

Copy link

@linde12 linde12 replied Mar 3, 2017

Ah, yes, i see! Came here via some googling on my phone so i didn't realise. Thanks. Will upgrade to 2.2! 馃憤

Please sign in to comment.