简介:
Vue中project与inject的使用比较简单,父级或祖级使用provide提供,子级孙级使用inject接收,这里不做介绍了,简单给个例子。
var Provider = {
provide:{
foo:'bar'
},
//....
}
//子
var Child = {
inject:['foo'],
created(){
console.log(this.foo);
}
}
背景:
在hummer/tenon-vue这个多端框架中,目标为实现换肤这一简单功能,由于编译最终为客户端代码,故不存在css继承、属性选择器、伪类,所以传统在根级标签中添加属性选择器的方案不可行。
所以我想到的方案为将主题theme使用变量通过祖级组件provide提供给所有子孙级组件,再通过把theme作为函数返回值的形式,将函数传给子孙级,子孙级即可获取。
方案比较简单,实用性也还好,但我无意间听到同事的实现方案:
相同点是:theme依然存在根级,区别他每个子孙级容器都会查询自己是否有theme,如果没有去查询自己的父级,若父级也没有,则查找爷爷级,直到查到存在或查到根级为止。
先不考虑他实现方案是否更优,但给我打开了一扇新的大门,这是我常年做业务,少做设计所思考的事情。然后我思考了下,这与我的方案有什么不同,优劣在哪?仔细一想我甚至不知道我的实现原理。知道通过provide/inject实现跨层级数据传输了,故此,我要好好了解下provide/inject的实现了。
源码:
-
入口位置:src/core/instance/init.js 50行左右
initInjections(vm)//子组件将数据注入到自己身上 initProvide(vm);//父组件在初始化的时候提供好数据
-
provide源码:src/core/inject.js:第8行
- 说明:其实就是在vm上增加_provide vm._provide.foo = 'bar'
export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide } }
-
inject源码
- 说明:不停的去从实例上查找source._provided属性,一层层向上查找,找到为止
export function initInjections (vm: Component) { const result = resolveInject(vm.$options.inject, vm) if (result) { toggleObserving(false) Object.keys(result).forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], () => { warn( `Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm ) }) } else { defineReactive(vm, key, result[key]) } }) toggleObserving(true) } } export function resolveInject (inject: any, vm: Component): ?Object { if (inject) { // inject is :any because flow is not smart enough to figure out cached const result = Object.create(null) const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject) for (let i = 0; i < keys.length; i++) { const key = keys[i] // #6574 in case the inject object is observed... if (key === '__ob__') continue const provideKey = inject[key].from let source = vm while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { result[key] = source._provided[provideKey] break } source = source.$parent } if (!source) { if ('default' in inject[key]) { const provideDefault = inject[key].default result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault } else if (process.env.NODE_ENV !== 'production') { warn(`Injection "${key}" not found`, vm) } } } return result } }
-
总结: 简单来说就是全局vm上添加一个属性provide存我们定义的变量,inject一层层向上查找目标属性,找到为止!
结束语:
这么看的话,同事的实现方式实质是与provide/inject的实现一致,甚至连缺点也一致,都是无法知道数据流的来源与流向;当然,这里我们不考虑重名的问题,但重名是依然是这个设计的存在的问题。
我没有看他的代码,但感觉实现方式是构造一个类去自动向上级去取,封装后应该代码也不多,相对比,没有明显优劣,我的主要就是比较容易理解了,但我依然要加强关于程序设计的思考,不能简单局限在业务中。