1. 首页

给你1小时,实现自定义一个Vue.js插件


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-plugin

插件到底是什么?

插件不是vue.js特有的东西,你通常会在很多软件中找到它们。根据定义,它们表明提供了一个接口来支持可扩展性。

换句更简单的话来说,他们是向应用程序添加全局功能的一种方法.

在vue.js中,一个插件应该暴露出带有2个参数的安装方法:

  1. 全局Vue对象。

  2. 包含用户定义选项的对象。

好消息是他们不是那么令人生畏。vue.js的基本知识就可以让你马上开始摆弄插件。

为什么要使用他们?

因为他们简单又强大。

如果你想要提高你的vue技能,不使用插件就是一个很大的失误。

根据官方 Vue.js 文档,以下是不同类型的Vue插件:

  1. 添加全局方法或属性。

  2. 添加一个或多个全局资产 (指令、过滤器、转换等。)

  3. 通过全局mixin添加组件选项。

  4. 通过将Vue示例方法附加到Vue.prototype来添加Vue实例方法。

  5. 创建一个库,在注入上述API的同时提供自己的API。

如果你看到这些类别中的任何一个是你需要的,你将很高兴的知道,Vue.js社区中已经提出了许多用于使用的解决方案。

流行的Vue.js插件

在开始一个新的Vue项目之前。我认为你知道以下插件的存在是很重要的:

Vue-router

如果你正在构建单页面应用程序,那么毫无疑问你需要使用Vue-router。作为Vue.js的官方路由,它与其核心深度集成,以完成映射组件和嵌套路由等任务。

Vuex

作为应用程序中所有组件的集中存储,如果你希望构建具有高维护性的大型应用程序,Vuex是一个明智的选择。

Js中文网 – 前端进阶资源教程 www.javascriptC.com,typescript 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到

Vee-validate

当构建典型的业务应用程序时,如果不小心处理,表单验证很快就会变得难以管理。Vee-validate以优雅的方式处理这一切。它使用指令,并在构建时考虑了本地化。

我仅仅使用了这些插件,但是要知道 还有许多其他插件 等待着帮助Vue.js开发人员!

然而,你有时会偶然大仙一个未发现的用例,并陷入未知的领域。幸运的是,正如你将在下面文章中看到的,定制vue.js插件并不像你想象的那样具有挑战性。

创建一个自定义Vue.js插件

背景

Snipcart的母公司Spektrum,每一项设计工作都需要经过审批程序。客户可以对设计提出意见和建议,并最终予以批准。为了支持这种协作, 他们使用 InVision 平台。

评论系统是InVision的核心部分。它允许人们点击设计的任何部分,并为协作者留下范围注释。

然后评论会以徽章的形式出现在评论者点击的地方。

invision-commenting-system

让我们开发一个功能齐全的Vue.js插件!

它必须可插入到任何HTML元素上,并且在宿主程序中尽可能不具有侵入性。

预备知识

而已,让我们来实现它!

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 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到

在上面代码片段中重要的点:

  1. 该应用程序存在于body最末尾的新div中。

  2. options对象中定义的时间处理程序被挂在到根实例上的匹配事件。这将在本教程结束时变的有意义。

  3. 添加到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 }) }) } }) } }

该指令做了两件事:

  1. 它将目标添加到根实例的数据中。为它定义键binding.value。它使使用者能够为目标元素指定自己的id,如下所示:

  2. 它在目标元素上注册了一个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仓库

vue-js-plugin-demo

这里看demo演示

这里看GitHub仓库

结论

比起以往任何时候都使用CLI 3总是欣喜与Vue.js一起使用。我迫不及待地想要通过我们即将推出的Snipcart开发来进一步扩展它的界限。我们一定会让你们跟上这些实验的进度!

我花了不到一天的时间来构建这个插件。为了简洁起见,我省略了重新调整大小。虽然我们存储的是ratioX和ratioY,这对于在组件渲染时计算定位非常合适,但在初始加载后调整页面大小会破坏注释布局。

这可以使用window.onresize或者不久以后的 ResizeObserver来修复。在写这篇文章的时候, ResizeObserver是Chrome专有的; 看看这里 是否还有。

你对我的插件和Vue.js开发有什么看法?你有什么实验想和我们分享吗?在下面的部分中发表评论!

原文链接:snipcart.com

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文来源于网络,其版权属原作者所有,如有侵权,请与小编联系,谢谢!

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:给你1小时,实现自定义一个Vue.js插件

链接:https://www.javascriptc.com/1914.html

原文链接:

« 在2018年每一个Javascript开发人员都应该关注的博客
函数组件与类有什么区别?»
Flutter 中文教程资源

相关推荐

QR code