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.

@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.

@yyx990803

yyx990803 Feb 20, 2017

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.

@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.

@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.

@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.

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.

Member

LinusBorg replied Mar 3, 2017

This is already in the latest 2.2 release ...

@linde12

This comment has been minimized.

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.