就创建可移植和跨框架库而言,我认为 jQuery 只是一个依赖项,它允许您创建某些元素并执行某些任务,您可以根据目标框架的拦截和/或修改要求。因此,您实际上是在围绕它创建一个包装器组件,因为当今排名前三的 JavaScript 框架(React、Vue、Angular)都是基于组件的。
主要区别之一(简单地说)是:反应性系统与 DOM 操作。
现在,谈论将 jQuery 插件移植到 Vue — 我不是这两个库的专家,但我自己来自 jQuery,我想说它可以像在 Vue 上保留对小部件/插件实例的引用一样简单组件内部 data 和/或 props 并使其可选公开相应的方法。方法公开部分是可选的原因与一个库与另一个库的区别是相同的——Vue 在库和框架之间进行扩展时更加通用。
在 jQuery 中,您将创建一个对象的实例并将其传递给它的公共方法使用;而在 Vue 中,您不会显式创建除根实例之外的实例(您 could,但您通常不必这样做)——因为组件本身 是 (内部构造的)实例。维护其状态和数据是组件的责任;兄弟组件和/或父组件通常不会直接访问它们。
Vue 和 jQuery 的相似之处在于它们都支持状态/数据同步。使用 jQuery,很明显,因为所有引用都在全局范围内;对于 Vue,可以使用 v-model 或 .sync 修饰符(在 Vue 3 中替换为 v-model 上的参数)。此外,他们还有事件订阅方式略有不同。
让我们使用 jQuery Autocomplete widget 并为其添加一些 Vue 支持。我们将专注于 3 件事(选项、事件和方法),并以它们各自的 3 个项目作为示例和比较。我不能在这里涵盖所有内容,但这应该会给你一些基本的想法。
设置:jQuery
>
为了符合您的规范,我们假设这个小部件/插件是new-able 类在window 范围内。
在 jQuery 中,您将编写以下内容(在 document 准备好或包装在 IIFE 中,然后关闭 <body> 标记):
var autocomplete = new Autocomplete({
source: [
'vue',
'react',
'angular',
'jquery'
],
appendTo: '#autocomplete-container',
disabled: false,
change: function(event, ui) { },
focus: function(event, ui) { },
select: function(event, ui) { }
});
// And then some other place needing manual triggers on this instance
autocomplete.close();
var isDisabled = autocomplete.option('disabled');
autocomplete.search('ue'); // Matches 'vue' and 'jquery' ;)
在父范围内的某处预定义或动态创建目标元素:
<input type="search" class="my-autocomplete" />
移植到 Vue
由于您没有提及使用的任何特定版本的 Vue,我将假设 Macross(最新稳定版本:2.6.12,ATTOW)带有 ES 模块;否则,请尝试 ES 模块 compatible build。
对于 Vue 中的这个特殊用例,我们希望在 mounted 钩子中实例化这个插件,因为这是我们的目标元素将被创建并且可用于构建的地方。在图表here 中了解有关生命周期挂钩的更多信息。
创建组件:Autocomplete.vue
<template>
<!--
Notice how this `input` element is added right here rather than we requiring
the parent component to add one, because it's now part of the component. :)
-->
<input type="search" class="my-autocomplete" />
</template>
<script>
export default {
// Basically, this is where you define IMMUTABLE "options", so to speak.
props: {
source: {
type: Array,
default: () => []
},
disabled: {
type: Boolean,
default: false
}
},
// And this is where to prepare and/or specify the internal options of a component.
data: () => ({
instance: null
}),
mounted() {
// `this` here refers to the local Vue instance
this.instance = new Autocomplete({
source: this.source,
disabled: this.disabled,
appendTo: this.$el // Refers to the `input` element on the template,
change: (event, ui) => {
// You can optionally pass anything in the second argument
this.$emit('change', this.instance);
},
focus: (event, ui) => {
this.$emit('focus', this.instance, event);
},
select: (event, ui) => {
this.$emit('select', this, event, ui);
}
});
},
methods: {
close() {
this.instance.autocomplete('close');
},
getOption(optionName) {
return this.instance.autocomplete('option', optionName);
},
search(keyword) {
this.instance.autocomplete('search', keyword);
}
}
}
</script>
使用组件:Parent.vue(或其他)
<template>
<div class="parent">
<autocomplete
ref="autocomplete"
:source="items"
:disabled="disabled"
@change="onChange"
@focus="onFocus"
@select="onSelect">
</autocomplete>
</div>
</template>
<script>
import Autocomplete from 'path/to/your-components/Autocomplete.vue';
export default {
data: () => ({
items: [
'vue',
'react',
'angular',
'jquery'
],
disabled: false
}),
methods: {
onChange() {
},
onFocus() {
},
onSelect() {
}
},
mounted() {
// Manually invoke a public method as soon as the component is ready
this.$refs.autocomplete.search('ue');
},
components: {
Autocomplete
}
}
</script>
我们还没有到那里!我故意省略了上述示例的"two-way binding" 部分,以便我们现在仔细查看。但是,此步骤是可选的,仅当您需要在组件(父 ↔ 子)之间同步数据/状态时才应执行,例如:您在组件上有一些逻辑,当某些值获取时将输入的边框颜色设置为红色进入。现在,由于您正在修改作为道具绑定到此组件的父状态(例如 invalid 或 error),因此您需要通过 $emit-ting 新值通知他们其更改。
所以,让我们进行以下更改(在同一个 Autocomplete.vue 组件上,为简洁起见省略其他所有内容):
{
model: {
prop: 'source',
event: 'modified' // Custom event name
},
async created() {
// An example of fetching remote data and updating the `source` property.
const newSource = await axios.post('api/fetch-data').then(res => res.data);
// Once fetched, update the jQuery-wrapped autocomplete
this.instance.autocomplete('option', 'source', newSource);
// and tell the parent that it has changed
this.$emit('modified', newSource);
},
watch: {
source(newData, oldData) {
this.instance.autocomplete('option', 'source', newData);
}
}
}
我们基本上是watch-ing“急切地”进行数据更改。如果愿意,您可以使用 $watch 实例方法懒惰地执行此操作。
父方的必要更改:
<template>
<div class="parent">
<autocomplete
ref="autocomplete"
v-model="items"
:disabled="disabled"
@change="onChange"
@focus="onFocus"
@select="onSelect">
</autocomplete>
</div>
</template>
这将启用上述双向绑定。您可以对需要“反应性”的其余道具执行相同的操作,例如此示例中的 disabled 道具 - 只是这次您将使用 .sync 修饰符;因为在 Vue 2 中,不支持多个 v-model。 (如果您还没有走得太远,我建议您一直使用 Vue 3 ?)。
最后,您可能需要注意一些注意事项和常见问题:
我们已经为自己创建了一个 Vue 移植版本的 jQuery Autocomplete!再说一遍,这些只是帮助您入门的一些基本想法。
现场演示
const Autocomplete = Vue.extend({
template: `
<div class="autocomplete-wrapper">
<p>{{label}}</p>
<input type="search" class="my-autocomplete" />
</div>
`,
props: {
source: {
type: Array,
default: () => []
},
disabled: {
type: Boolean,
default: false
},
label: {
type: String
}
},
model: {
prop: 'source',
event: 'modified'
},
data: () => ({
instance: null
}),
mounted() {
const el = this.$el.querySelector('input.my-autocomplete');
this.instance = $(el).autocomplete({
source: this.source,
disabled: this.disabled,
change: (event, ui) => {
// You can optionally pass anything in the second argument
this.$emit('change', this.instance);
},
focus: (event, ui) => {
this.$emit('focus', this.instance, event);
},
select: (event, ui) => {
this.$emit('select', this, event, ui);
}
});
},
methods: {
close() {
this.instance.autocomplete('close');
},
getOption(optionName) {
return this.instance.autocomplete('option', optionName);
},
search(keyword) {
this.instance.autocomplete('search', keyword);
},
disable(toState) {
this.instance.autocomplete('option', 'disabled', toState);
}
},
watch: {
source(newData, oldData) {
this.instance.autocomplete('option', 'source', newData);
},
disabled(newState, oldState) {
this.disable(newState);
}
}
});
new Vue({
el: '#app',
data: () => ({
items: [
'vue',
'react',
'angular',
'jquery'
],
disabled: false
}),
computed: {
computedItems: {
get() {
return this.items.join(', ');
},
set(val) {
this.items = val.split(', ')
}
}
},
methods: {
onChange() {
// Do something
},
onFocus() {},
onSelect(instance, event, ui) {
console.log(`You selected: "${ui.item.value}"`);
}
},
components: {
Autocomplete
}
})
#app {
display: flex;
justify-content: space-between;
}
#app > div {
flex: 0 0 50%;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" />
<link rel="stylesheet" href="/resources/demos/style.css" />
<script src="https://vuejs.org/js/vue.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id="app">
<autocomplete
v-model="items"
:disabled="disabled"
label='Type something (e.g. "ue")'
@change="onChange"
@focus="onFocus"
@select="onSelect">
</autocomplete>
<div>
<p>Edit this comma-separated list of items and see them reflected on the component</p>
<textarea
v-model.lazy="computedItems"
cols="30"
rows="3">
</textarea>
</div>
</div>
附:如果这些小部件实际上在全局 window 范围内并且您正在使用 ESLint,那么您需要确保将它们指定为全局变量;否则,no-undef 规则将对已访问但未在同一文件中定义的变量发出警告。解决方法见this post。
P.P.S.如果您需要将它们作为插件发布,请参阅:Writing a Plugin(不用担心,不需要太多额外的工作)。