We're very excited to sponsor **VueConf TO 2018** https://vuetoronto.com
Come hang out and learn some Vue.js with world-class developers (Nov. 15-16).
很着急? 跳到 实例.
我们 ❤️ Vue.js已经不是什么秘密了。
以至于它是我们产品重构的一个关键部分。
在过去的几个月里, 我们学到了 很多 关于vue的知识。 从创建 SEO友好的SPA 到制作 杀手级博客 或玩 过渡 & 动画,我们已经彻底的试验了这个框架。
但是对于缺失的功能:
Js中文网 – 前端进阶资源教程 www.javascriptC.com,typescript 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到
框架大多数支持者不得不使用Vue.js插件。
为了我的乐趣 (也希望是你的)我创建了一个自定义插件,向你展示如何在一步一步的教程中完成它。
我也将用这篇文章来回答一些重要的vue插件问题:
- 什么是插件?
-
他们对什么有用?
-
什么是流行的Vue.js插件?
魔术时间到了!
Vue.js插件的简短故事
插件到底是什么?
插件不是vue.js特有的东西,你通常会在很多软件中找到它们。根据定义,它们表明提供了一个接口来支持可扩展性。
换句更简单的话来说,他们是向应用程序添加全局功能的一种方法.
在vue.js中,一个插件应该暴露出带有2个参数的安装方法:
- 全局Vue对象。
-
包含用户定义选项的对象。
好消息是他们不是那么令人生畏。vue.js的基本知识就可以让你马上开始摆弄插件。
为什么要使用他们?
因为他们简单又强大。
如果你想要提高你的vue技能,不使用插件就是一个很大的失误。
根据官方 Vue.js 文档,以下是不同类型的Vue插件:
- 添加全局方法或属性。
-
添加一个或多个全局资产 (指令、过滤器、转换等。)
-
通过全局mixin添加组件选项。
-
通过将Vue示例方法附加到Vue.prototype来添加Vue实例方法。
-
创建一个库,在注入上述API的同时提供自己的API。
如果你看到这些类别中的任何一个是你需要的,你将很高兴的知道,Vue.js社区中已经提出了许多用于使用的解决方案。
流行的Vue.js插件
在开始一个新的Vue项目之前。我认为你知道以下插件的存在是很重要的:
如果你正在构建单页面应用程序,那么毫无疑问你需要使用Vue-router。作为Vue.js的官方路由,它与其核心深度集成,以完成映射组件和嵌套路由等任务。
→ Vuex
作为应用程序中所有组件的集中存储,如果你希望构建具有高维护性的大型应用程序,Vuex是一个明智的选择。
Js中文网 – 前端进阶资源教程 www.javascriptC.com,typescript 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到
当构建典型的业务应用程序时,如果不小心处理,表单验证很快就会变得难以管理。Vee-validate以优雅的方式处理这一切。它使用指令,并在构建时考虑了本地化。
我仅仅使用了这些插件,但是要知道 还有许多其他插件 等待着帮助Vue.js开发人员!
然而,你有时会偶然大仙一个未发现的用例,并陷入未知的领域。幸运的是,正如你将在下面文章中看到的,定制vue.js插件并不像你想象的那样具有挑战性。
创建一个自定义Vue.js插件
背景
在 Snipcart的母公司Spektrum,每一项设计工作都需要经过审批程序。客户可以对设计提出意见和建议,并最终予以批准。为了支持这种协作, 他们使用 InVision 平台。
评论系统是InVision的核心部分。它允许人们点击设计的任何部分,并为协作者留下范围注释。
然后评论会以徽章的形式出现在评论者点击的地方。
让我们开发一个功能齐全的Vue.js插件!
它必须可插入到任何HTML元素上,并且在宿主程序中尽可能不具有侵入性。
预备知识
- Vue.js基础知识。
而已,让我们来实现它!
1. 准备代码库
感谢Vue CLI 3,现在可以比以往更轻松的初始化Vue.js代码库。安装CLI后,只需运行一下命令:
$ vue create vue-comments-overlay
# Answer the few questions
$ cd vue-comments-overlay
$ npm run serve
你将运行经典的Vue.js”Hello Word”应用程序。这将使你的测试应用。
2. 开发Vue.js插件
由于会有一些组件,所以最好将它们全部放在一个文件夹中。
$ mkdir src/plugins
$ mkdir src/plugins/CommentsOverlay
$ cd src/plugins/CommentsOverlay
2.1 编写基础
Vue.js 插件 基本上是具有安装功能的对象。 当使用者应用程序中包含带有Vue.use()的插件时,它就会被执行。
install函数接收全局Vue对象以及options对象最为参数。有了这个全局对象,扩展Vue的可能性实际上是无限的:扩充Vue的原型,添加自定义指令,甚至在插件中启动一个新的Vue实例(阻止警告框)。
为什么我们不从创建插件的框架开始呢?
// src/plugins/CommentsOverlay/index.js
//
export default {
install(vue, opts){
console.log('Installing the CommentsOverlay plugin!')
// Fun will happen here
}
}
现在,我们将它插入在你的测试应用中。
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import CommentsOverlay from './plugins/CommentsOverlay'
Vue.use(CommentsOverlay)
Vue.config.productionTip = false
new Vue({ render: createElement => createElement(App)}).$mount('#app')
2.2 支持的选项
插件可以使用选项进行配置,选项是install函数的第二个参数。让我们创建表示插件基本行为的默认选项,即在没有指定自定义选项时如何操作。
// src/plugins/CommentsOverlay/index.js
const optionsDefaults = {
// Retrieves the current logged in user that is posting a comment
commenterSelector() {
return {
id: null,
fullName: 'Anonymous',
initials: '--',
email: null
}
},
data: {
// Hash object of all elements that can be commented on
targets: {},
onCreate(created) {
this.targets[created.targetId].comments.push(created)
},
onEdit(editted) {
// This is obviously not necessary
// It's there to illustrate what could be done in the callback of a remote call
let comments = this.targets[editted.targetId].comments
comments.splice(comments.indexOf(editted), 1, editted);
},
onRemove(removed) {
let comments = this.targets[removed.targetId].comments
comments.splice(comments.indexOf(removed), 1);
}
}
}
然后,合并默认值和传递给install函数的选项。
// src/plugins/CommentsOverlay/index.js
export default {
install(vue, opts){
// Merge options argument into options defaults
const options = { ...optionsDefaults, ...opts }
...
}
}
2.3 评论层的vue实例
使用这个插件要避免的一件事是,它的DOM和样式会干扰它所安装的应用程序。为了最大限度地减少这种情况发生的可能性,一种方法是使插件在主应用程序组件树之外的另一个根Vue实例中生效。
给install函数里添加以下内容:
// src/plugins/CommentsOverlay/index.js
export default {
install(vue, opts){
...
// Create plugin's root Vue instance
const root = new Vue({
data: { targets: options.data.targets },
render: createElement => createElement(CommentsRootContainer)
})
// Mount root Vue instance on new div element added to body
root.$mount(document.body.appendChild(document.createElement('div')))
// Register data mutation handlers on root instance
root.$on('create', options.data.onCreate)
root.$on('edit', options.data.onEdit)
root.$on('remove', options.data.onRemove)
// Make the root instance available in all components
vue.prototype.$commentsOverlay = root
...
}
}
Js中文网 – 前端进阶资源教程 www.javascriptC.com,typescript 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到
在上面代码片段中重要的点:
- 该应用程序存在于body最末尾的新div中。
-
options对象中定义的时间处理程序被挂在到根实例上的匹配事件。这将在本教程结束时变的有意义。
-
添加到Vue原型的$commentsOverlay属性将根实例暴露给应用程序中的所有Vue组件。
2.4 自定义Vue.js指令
最后,你需要一种方法让使用应用程序者告诉插件哪个元素将启用comments。 这是一个自定义Vue.js指令的例子Vue.js directive。由于插件可以访问全局Vue对象,因此可以定义新指令。
将被命名为comments-enabled,它是这样的:
// src/plugins/CommentsOverlay/index.js
export default {
install(vue, opts){
...Js中文网 - 前端进阶资源教程 [www.javascriptC.com]
// Register custom directive tha enables commenting on any element
vue.directive('comments-enabled', {
bind(el, binding) {
// Add this target entry in root instance's data
root.$set(
root.targets,
binding.value,
{
id: binding.value,
comments: [],
getRect: () => el.getBoundingClientRect(),
});
el.addEventListener('click', (evt) => {
root.$emit(commentTargetClicked__${binding.value}, {
id: uuid(),
commenter: options.commenterSelector(),
clientX: evt.clientX,
clientY: evt.clientY
})
})
}
})
}
}
该指令做了两件事:
- 它将目标添加到根实例的数据中。为它定义键binding.value。它使使用者能够为目标元素指定自己的id,如下所示:
-
它在目标元素上注册了一个click时间处理程序,该处理程序在这个特定目标的根实例上触发一个事件。我们稍后再讨论如何处理它。
install函数现在完成了!
2.5 CommentsRootContainer 组件
CommentsRootContainer 是UI插件的根组件。我们来看看。
// src/plugins/CommentsOverlay/CommentsRootContainer.vue
<template>
<div>
<comments-overlay
v-for="target in targets"
:target="target"
:key="target.id">
</comments-overlay>
</div>
</template>
<\script>
import CommentsOverlay from "./CommentsOverlay";
export default {
components: { CommentsOverlay },
computed: {
targets() {
return this.$root.targets;
}
}
};
</script>
注意,target计算属性是如何从根组件的数据派生出来的。
现在,overlay组件是见证奇迹的时刻。让我们开始吧!
2.6 CommentsOverlay 组件
// src/plugins/CommentsOverlay/CommentsRootContainer.vue
<template>
<div class="comments-overlay">
<div class="comments-overlay__container" v-for="comment in target.comments" :key="comment.id" :style="getCommentPostition(comment)">
<button class="comments-overlay__indicator" v-if="editting != comment" @click="onIndicatorClick(comment)">
{{ comment.commenter.initials }}
</button>
<div v-else class="comments-overlay__form">
<p>{{ getCommentMetaString(comment) }}</p>
<textarea ref="text" v-model="text" />
<button @click="edit" :disabled="!text">Save</button>
<button @click="cancel">Cancel</button>
<button @click="remove">Remove</button>
</div>
</div>
//Js中文网 - 前端进阶资源教程 [www.javascriptC.com]
<div class="comments-overlay__form" v-if="this.creating" :style="getCommentPostition(this.creating)">
<textarea ref="text" v-model="text" />
<button @click="create" :disabled="!text">Save</button>
<button @click="cancel">Cancel</button>
</div>
</div>
</template>
<\script>
export default {
props: ['target'],
data() {
return {
text: null,
editting: null,
creating: null
};
},
methods: {
onTargetClick(payload) {
this._resetState();
const rect = this.target.getRect();
this.creating = {
id: payload.id,
targetId: this.target.id,
commenter: payload.commenter,
ratioX: (payload.clientX - rect.left) / rect.width,
ratioY: (payload.clientY - rect.top) / rect.height
};
},
onIndicatorClick(comment) {
this._resetState();
this.text = comment.text;
this.editting = comment;
},
getCommentPostition(comment) {
const rect = this.target.getRect();
const x = comment.ratioX * rect.width + rect.left;
const y = comment.ratioY * rect.height + rect.top;
return { left: ${x}px, top: ${y}px };
},
getCommentMetaString(comment) {
return ${
comment.commenter.fullName
} - ${comment.timestamp.getMonth()}/${comment.timestamp.getDate()}/${comment.timestamp.getFullYear()};
},
edit() {
this.editting.text = this.text;
this.editting.timestamp = new Date();
this._emit("edit", this.editting);
this._resetState();
},
create() {
this.creating.text = this.text;
this.creating.timestamp = new Date();
this._emit("create", this.creating);
this._resetState();
//Js中文网 - 前端进阶资源教程 [www.javascriptC.com]
},
cancel() {
this._resetState();
},
remove() {
this._emit("remove", this.editting);
this._resetState();
},
_emit(evt, data) {
this.$root.$emit(evt, data);
},
_resetState() {
this.text = null;
this.editting = null;
this.creating = null;
}
},
mounted() {
this.$root.$on(commentTargetClicked__${this.target.id}, this.onTargetClick
);
},
beforeDestroy() {
this.$root.$off(commentTargetClicked__${this.target.id}, this.onTargetClick
);
}
};
</script>
这里有几点需要注意:
- 组件接收完整的target对象作为属性。这里是存储comments数组和定位信息的地方。
-
我们之前看到的 commentTargetClicked 事件处理程序在mounted和beforeDestory钩子中进行管理。
-
根实例被用作事件的总线。即使这种方法通常不被鼓励,但我认为在这种情况下它是合理的,因为这些组件不是公开暴露的,可以看作是一个整体单元。
Aaaand,我们都准备好了! 现在,经过一些样式 (我不会扩展我没有把握的CSS技能), 我们的插件已准备好接受用户对目标元素的评论了。
demo演示和GitHub仓库
在这里看demo演示
在 这里看GitHub仓库
结论
比起以往任何时候都使用CLI 3总是欣喜与Vue.js一起使用。我迫不及待地想要通过我们即将推出的Snipcart开发来进一步扩展它的界限。我们一定会让你们跟上这些实验的进度!
我花了不到一天的时间来构建这个插件。为了简洁起见,我省略了重新调整大小。虽然我们存储的是ratioX和ratioY,这对于在组件渲染时计算定位非常合适,但在初始加载后调整页面大小会破坏注释布局。
这可以使用window.onresize或者不久以后的 ResizeObserver来修复。在写这篇文章的时候, ResizeObserver是Chrome专有的; 看看这里 是否还有。
你对我的插件和Vue.js开发有什么看法?你有什么实验想和我们分享吗?在下面的部分中发表评论!
原文链接:snipcart.com
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程
本文来源于网络,其版权属原作者所有,如有侵权,请与小编联系,谢谢!
转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com
链接:https://www.javascriptc.com/1914.html
原文链接: