「译」 如何在 Vue 中(安全的)使用 jQuery 插件

作者:Anthony Gore
原文链接:How To (Safely) Use A jQuery Plugin With Vue.js

在一个 UI 中同时使用 Vue.js 和 jQuery 并不是一个好主意。如果可能的话要避免这种情形。

不过既然你已经看到这里了,估计你是非要同时用 Vue 和 jQuery 不可了。或许是客户坚持要使用某个 jQuery 插件而你又没时间去把它用 Vue 重写。

只要你谨慎的操作,还是可以同时使用 Vue 和 jQuery 的。这篇文章里我将演示如何在 Vue 项目中使用一个 jQuery UI 的 Datepicker 插件。

而且为了小小的炫一下技,我还要在 jQuery 插件和 Vue 之间传递数据!

点击这里查看示例代码 JS Bin

jquery ui datepicker

jQuery UI Datepicker

同时使用 jQuery 和 Vue 存在的问题

为什么说这种用法会有潜在的风险呢?

可以说 Vue 是一个占有欲非常强的框架,她总想完全控制你交给她的这部分 DOM 元素(传递给 el 属性的元素)。如果 jQuery 修改了 Vue 管辖下的元素,例如为某个元素添加了一个 class, Vue 无法感知到这次修改,所以下次 Vue 更新 DOM 时会直接覆盖 jQuery 所做的修改。

解决方案:把 jQuery 插件包装成 Vue 组件

既然 Vue 和 jQuery 绝不会分享同一份 DOM,我们必须让 Vue 分割出一块区域交给 jQuery。

看起来把 jQuery 插件包装成 Vue 组件是个办法,因为:

  • 我们可以利用 Vue 生命周期勾子来设置或卸载 jQuery 代码
  • 我们可以通过组件接口使用 props 和 events 来与 Vue 应用通信
  • 组件可以通过设置 v-once 避免被 Vue 更新

设置 jQuery UI Datepicker

首先要做的当然是在你的项目中引入 jQuery UI 插件。然后你只需要把 datepicker 插件绑定到一个 input 元素上:

1
Date: <input id="datepicker">

选中并初始化 Datepicker

1
$('#datepicker').datepicker();

Datepicker 组件

我们的 datepicker 组件很简单,template 只有一个 input 元素:

1
2
3
4
5
6
7
Vue.component('date-picker', function() {
template: ''
});

new Vue({
el: '#app'
});
1
2
3
<div id="app">
Date: <date-picker>date-picker>
div>

注意:组件只是为 jQuery 插件提供一个包装。不要试图为组件元素添加任何 data 属性或 Vue directives 或 slots。

插件实例化

在这里我们将使用 this.$el 而不是 input 元素的 ID 来选择插件元素,每个 Vue 组件都可以像这样来访问到自己的根元素。我们这里的根元素就是 input

然后我们可以把这个元素引用传递给 jQuery 选择器并调用 datepicker 方法。例:$(this.$el).datepicker()

注意我们的代码是写在在 Vue 组件生命周期的 mounted 阶段,因为在此之前 this.$el 还是 undefined 状态。

1
2
3
4
5
6
Vue.component('date-picker', function() {
template: '',
mounted: function() {
$(this.$el).datepicker();
}
});

卸载

类似的,卸载 datepicker 插件同样也会用到生命周期勾子。注意我们必须在 beforeDestroy 阶段执行卸载,因为在这个阶段 input 元素仍然存在于 DOM 中,我们才能选中它(在 destroy 阶段它将变成 undefined)。

1
2
3
4
5
6
7
8
9
Vue.component('date-picker', {
template: '',
mounted: function() {
$(this.$el).datepicker();
},
beforeDestroy: function() {
$(this.$el).datepicker('hide').datepicker('destroy');
}
});

通过 props 传递参数

为了使我们的组件可复用,最好让它能接受自定义设置参数,例如通过设置属性 dateFormat 来制定日期格式。通过 props 可以实现这一目标:

1
2
3
4
5
6
7
8
9
10
Vue.component('date-picker', {
template: '',
props: [ 'dateFormat' ],
mounted: function() {
$(this.$el).datepicker({
dateFormat: this.dateFormat
});
},
beforeDestroy: function() { ... }
});
1
2
3
<div id="app">
<date-picker date-format="yy-mm-dd">date-picker>
div>

让 jQuery 接收更新

比方说你不满足于只是传给 dateFormat prop 一个字符串,而是把 dateFormat 设置成 Vue 根实例上的一个属性,例:

1
2
3
4
5
6
var vm = new Vue({
data: {
...
dateFormat: 'yy-mm-dd'
}
});
1
2
3
<div id="app">
<date-picker date-format="dateFormat">date-picker>
div>

这样一来,dateFormat 就变成了一个响应式的 data 属性。你可以随时在应用中更改它的值:

1
2
// 更改日期显示格式
vm.dateFormat = 'yy-dd-mm';

由于 dateFormat prop 是 datepicker 组件的 mounted 钩子的依赖项,更新它的值将导致组件重新渲染。这就不妙了。因为 jQuery 已经在 input 元素上设置好了 datepicker 并且添加了自己的 class 和 事件监听。组件重新渲染将导致 input 元素被替换而且 jQuery 所做的设置也被重置了。

我们需要将组件设置为接收数据不重新渲染…

v-once

v-once 指令用于缓存一个包含大量静态内容的组件。导致的结果就是这个组件渲染不会受数据更新的影响。

我们的组件正好用到这个特性,因为它有效的避免了被 Vue 重新渲染。因此我们有理由相信 jQuery 将可以在应用的生命周期中无障碍的操作这个 input 元素。

1
2
3
<div id="app">
<date-picker date-format="yy-mm-dd" v-once>date-picker>
div>

从 jQuery 向 Vue 传递数据

如果我们不能在 app 中获取到 datepicker 选中的日期,那它就没啥用了。让我们来实现这个目标。

首先给 root 实例一个 date 属性:

1
2
3
4
5
6
new Vue({
el: '#app',
data: {
date: null
}
});
1
2
3
4
<div id="app">
<date-picker date-format="yy-mm-dd" v-once>date-picker>
<p>{{ date }}p>
div>

datepicker 插件有一个 onSelect 回调函数来处理选中日期的操作。通过它我们可以在组件中使用自定义事件发出数据。

1
2
3
4
5
6
7
8
9
mounted: function() {
var self = this;
$(this.$el).datepicker({
dateFormat: this.dateFormat,
onSelect: function(date) {
self.$emit('update-date', date);
}
});
}

在根实例上监听自定义事件并接收新数据:

1
2
3
4
<div id="app">
<date-picker @update-date="updateDate" date-format="yy-mm-dd" v-once>date-picker>
<p>{{ date }}p>
div>
1
2
3
4
5
6
7
8
9
10
11
new Vue({
el: '#app',
data: {
date: null
},
methods: {
updateDate: function(date) {
this.date = date;
}
}
});

感谢这个 Stack Overflow 回答 提供的灵感

「译」使用 CSS 硬件加速提升网站性能 Testing post
广告: