一、前言😃
我目前在做的一个项目,前端这块虽然说使用的是CS架构,但其实是C#
提供了一层Chrome
内核来运行app的。也就是混合开发的一种。现在的工作是将以前的jquery
一把梭的开发模式转移到vue
上来,这就带来了vue
在混合开发中应用的思考。
二、混合开发
像Android
,WPF
和IOS
等等这些原生(或桌面)应用都会提供一个WebView
的控件来支持原生与web交互。
而交互的方式分为两种:
- 一是原生调用js中window上的对象(或函数)。
- 二是js调用原生提供在window上的对象。
一般情况都是原生容器提供一个对象,js去调用这个对象的方法,若为同步处理,直接返回值;若是异步的处理,原生容器会在处理结束后再调用window上的回调函数。
总的就是
// 原生容器提供的对象
window.OBJ.callback("123");
// js提供回调给原生容器调用
window.onOBJCallback = function (data){
console.log(data);
}
三、vue应用于混合开发
1. 一代
可以看到js
和原生交互都在window
上,而vue
这种内部处理数据的方式和这种window交互其实不是很搭的,要写也可以,但是极为别扭。如:
export default {
data() {
return {
text: ""
};
},
mounted() {
// 挂载到window上
window.onCallback = this.onCallback;
},
methods: {
onCallback(data) {
this.text = data;
}
}
};
这样虽然直观,但如果回调太多,代码写的多而易读性差不说还丑😐。
2. 二代⚡
混入(mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
由于混入是在已有组件的调用前调用的,所以在使用恰当的方式下不影响已有组件的开发。
设计思路
- 考虑到可以混入其他属性,那可以混入一个叫
callbacks
的属性,就像methods
属性一样去写方法,但可以很明显地区别开内部方法和回调方法。 - vue的配置可以通过组件内
this.$options
来获取,那么若要混入callbacks
,就可以this.$options.callbacks
来获取到所有组件里的方法。 - 拿到定义好的
callbacks
,那就可以混入mounted()
来挂载回调方法到window
上。 - 说干就干,代码如下
export class Callackable {
// 用于缓存每个页面的callbacks
callbacks = {};
install(Vue, options) {
// 让Vue的组件能访问到这个实例里
const self = this;
// 开始混入
Vue.mixin({
beforeCreate() {
if (this.$options.callbacks && !(this.$route.name in self.callbacks)) {
self.installCallback(this, this.$options.callbacks)
}
},
mounted() {
const name = this.$route.name;
if (self.callbacks[name]) {
self.callbacks[name].forEach(callback => {
// 挂载
window[callback.name] = callback.func
});
}
}
})
}
installCallback(vm, callbacks) {
const routeName = vm.$options.name
Object.keys(callbacks).forEach(name => {
(this.callbacks[routeName] || (this.callbacks[routeName] = [])).push({
name,
// 将回调方法的this指向绑定到自己的组件实例上
func: callbacks[name].bind(vm)
})
})
}
}
主要思路就是每次组件加载时,在beforeCreated
中获取callbacks
,然后根据组件名称缓存起来,然后再在mounted
时挂载组件。
重点主要在:绑定this。如果直接挂载到winodw
上,那么调用这个回调函数时,this
指向的就是window
,所以通过bind
方法来绑定到当前组件上
然后在main.js
中通过Vue.use(new Callbackable())
就可以了。
开发时如下:
export default {
data() {
return {
text: ""
};
},
callbacks: {
onCallback(data) {
this.text = data;
}
}
};
然而这其实是bug版本!!!👾
3. 二代改良版🔥
后面开发中发现,如果回调里处理data
数据,切换路由后到其他页面再切换回来就会出现不能绑定到template
上的情况!经过各种调试,发现虽然会触发data
数据的改变,但并不会触发该组件内的watch
去监听,自然就没有渲染。奇怪的是this
也是组件本身。。。🙉
偶然地机会,🤔发现了两次this._uid
不一样,恍然大悟下知道了vue-router
其实每次进入路由,若没有通过<keep-alive>
包裹<router-view>
,那么都会新建一个组件,这就是解释了之前的问题,也就是this
指向的是之前的那个组件,所以回调改变的其实也是之前的组件的data
。所以每次路由切换都要重新绑定一次this
才行。代码如下:
export class Callackable {
callbacks = [];
install(Vue, options) {
const self = this;
Vue.mixin({
beforeCreate() {
/**
* 不能通过缓存来取callback。
* 因为路由切换会导致组件实例的重新创建,this便指向过去的组件实例,而不是当前新创建的实例
*/
if (this.$options.callbacks) {
self.installCallback(this, this.$options.callbacks)
}
},
mounted() {
self.callbacks.forEach(callback => {
window[callback.name] = callback.func
});
}
})
}
installCallback(vm, callbacks) {
Object.keys(callbacks).forEach(name => {
this.callbacks.push({
name,
func: callbacks[name].bind(vm)
})
})
}
}
这次就完美地解决了之前的问题。
四、总结
vue在混合开发中的应用就暂时告一段落了,大概设计了回调的使用方式和了解了vue-router
的机制,还是要多读源码啊:P。
网友评论