浅析vue封装自定义插件(vue自己封装组件 vue项目中)
ztj100 2024-10-27 18:36 34 浏览 0 评论
前言
在使用vue的过程中,经常会用到Vue.use,但是大部分对它一知半解,不了解在调用的时候具体做了什么,因此,本文简要概述下在vue中,如何封装自定义插件。
在开始之前,先补充一句,其实利用Vue封装自定义插件的本质就是组件实例化的过程或者指令等公共属性方法的定义过程,比较大的区别在于封装插件需要手动干预,就是一些实例化方法需要手动调用,而Vue的实例化,很多逻辑内部已经帮忙处理掉了。插件相对于组件的优势就是插件封装好了之后,可以开箱即用,而组件是依赖于项目的。对组件初始化过程不是很熟悉的可以参考这篇博文。
我们从vue源码中,可以看到Vue.use的方法定义如下:
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 已经存在插件,则直接返回插件对象
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
// vue插件形式可以是对象,也可以是方法,默认会传递一个Vue的构造方法作为参数
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
从上述代码中,我们可以看出,Vue.use代码比较简洁,处理逻辑不是很多。我们常用的Vue.use(xxx),xxx可以是方法,也可以是对象。在Vue.use中,通过apply调用插件方法,传入一个参数,Vue的构造方法。举个栗子,最简单的Vue插件封装如下:
// 方法
function vuePlugins (Vue) {
Vue.directive('test', {
bind (el) {
el.addEventListener('click', function (e) {
alert('hello world')
})
}
})
}
// 对象
const vuePlugins = {
install (Vue) {
Vue.directive('test', {
bind (el) {
el.addEventListener('click', function (e) {
alert('hello world')
})
}
})
}
}
以上两种封装方法都可以,说白了,就是将全局注册的指令封装到一个方法中,在Vue.use时调用。这个比较显然易懂。现在举一个稍微复杂点的例子,tooltip在前端开发中经常会用到,直接通过方法能够调用显示,防止不必要的组件注册引入,如果我们单独封装一个tooltip组件,应该如何封装呢?这种封装方式需要了解组件的初始化过程。区别在于将组件封装成插件时,不能通过template将组件实例化挂载到真实DOM中,这一步需要手动去调用对应组件实例化生命周期中的方法。具体实现代码如下:
// component
let toast = {
props: {
show: {
type: Boolean,
default: false
},
msg: {
type: String
}
},
template: '<div v-show="show" class="toast">{{msg}}</div>'
}
组件初始化过程:
// JavaScript初始化逻辑
// 获取toast构造实例
const TempConstructor = Vue.extend(toast)
// 实例化toast
let instance = new TempConstructor()
// 手动创建toast的挂载容器
let div = document.createElement('div')
// 解析挂载toast
instance.$mount(div)
// 将toast挂载到body中
document.body.append(instance.$el)
// 将toast的调用封装成一个方法,挂载到Vue的原型上
Vue.prototype.$toast = function (msg) {
instance.show = true
instance.msg = msg
setTimeout(() => {
instance.show = false
}, 5000)
}
组件的定义,和普通的组件声明一致。组件的插件化过程,和普通的组件实例化一致,区别在于插件化时组件部分初始化方法需要手动调用。比如:
- Vue.extend作用是组装组件的构造方法VueComponent
- new TempConstructor()是实例化组件实例。实例化构造方法,只是对组件的状态数据进行了初始化,并没有解析组件的template,也没有后续的生成vnode和解析vnode
- instance.$mount(div)的作用是解析模板文件,生成render函数,进而调用createElement生成vnode,最后生成真实DOM,将生成的真实DOM挂载到实例instance的$el属性上,也就是说,实例instance.$el即为组件实例化最终的结果。
- 组件中,props属性最终会声明在组件实例上,所以直接通过实例的属性,也可以响应式的更改属性的传参。组件的属性初始化方法如下:
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
// 属性代理,从一个原对象中拿数据
export function proxy (target: Object, sourceKey: string, key: string) {
// 设置对象属性的get/set,将data中的数据代理到组件对象vm上
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
从上述可以看出,最终会在构造方法中,给所有的属性声明一个变量,本质上是读取_props中的内容,_props中的属性,会在实例化组件,initState中的InitProps中进行响应式的声明,具体代码如下:
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
这里会遍历所有订单props,响应式的声明属性的get和set。当对属性进行读写时,会调用对应的get/set,进而会触发视图的更新,vue的响应式原理在后面的篇章中会进行介绍。这样,我们可以通过方法参数的传递,来动态地去修改组件的props,进而能够将组件插件化。
有些人可能会有疑问,到最后挂载到body上的元素是通过document.createElement('div')创建的div,还是模板的template解析后的结果。其实,最终挂载只是组件解析后的结果。在调用__patch__的过程中,执行流程是,首先,记录老旧的节点,也就是$mount(div)中的div;然后,根据模板解析后的render生成的vnode的节点,去创建DOM节点,创建后的DOM节点会放到instance.$el中;最后一步,会将老旧节点给移除掉。所以,在我们封装一个插件的过程中,实际上手动创建的元素只是一个中间变量,并不会保留在最后。可能大家还会注意到,插件实例化完成后的DOM挂载也是我们手动挂载的,执行的代码是document.body.append(instance.$el)。
附:test.html 测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
.toast{
position: absolute;
left: 45%;
top: 10%;
width: 10%;
height: 5%;
background: #ccc;
border-radius: 5px;
}
</style>
<title>Hello World</title>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
</head>
<body>
<div id='app' v-test>
<button @click="handleClick">我是按钮</button>
</div>
<script>
function vuePlugins (Vue) {
Vue.directive('test', {
bind (el) {
el.addEventListener('click', function (e) {
alert('hello world')
})
}
})
}
// const vuePlugins = {
// install (Vue) {
// Vue.directive('test', {
// bind (el) {
// el.addEventListener('click', function (e) {
// alert('hello world')
// })
// }
// })
// }
// }
Vue.use(vuePlugins)
let toast = {
props: {
show: {
type: Boolean,
default: false
},
msg: {
type: String
}
},
template: '<div v-show="show" class="toast">{{msg}}</div>'
}
// 获取toast构造实例
const TempConstructor = Vue.extend(toast)
// 实例化toast
let instance = new TempConstructor()
// 手动创建toast的挂载容器
let div = document.createElement('div')
// 解析挂载toast
instance.$mount(div)
// 将toast挂载到body中
document.body.append(instance.$el)
// 将toast的调用封装成一个方法,挂载到Vue的原型上
Vue.prototype.$toast = function (msg) {
instance.show = true
instance.msg = msg
setTimeout(() => {
instance.show = false
}, 5000)
}
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello World',
a: 11
},
methods: {
test () {
console.log('这是一个主方法')
},
handleClick () {
this.$toast('hello world')
}
},
created() {
console.log('执行了主组件上的created方法')
},
})
</script>
</body>
</html>
相关推荐
- Jquery 详细用法
-
1、jQuery介绍(1)jQuery是什么?是一个js框架,其主要思想是利用jQuery提供的选择器查找要操作的节点,然后将找到的节点封装成一个jQuery对象。封装成jQuery对象的目的有...
- 前端开发79条知识点汇总
-
1.css禁用鼠标事件2.get/post的理解和他们之间的区别http超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信。HTTP的工作方式是客户机与服务器之间的请求-应答协议。...
- js基础面试题92-130道题目
-
92.说说你对作用域链的理解参考答案:作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。...
- Web前端必备基础知识点,百万网友:牛逼
-
1、Web中的常见攻击方式1.SQL注入------常见的安全性问题。解决方案:前端页面需要校验用户的输入数据(限制用户输入的类型、范围、格式、长度),不能只靠后端去校验用户数据。一来可以提高后端处理...
- 事件——《JS高级程序设计》
-
一、事件流1.事件流描述的是从页面中接收事件的顺序2.事件冒泡(eventbubble):事件从开始时由最具体的元素(就是嵌套最深的那个节点)开始,逐级向上传播到较为不具体的节点(就是Docu...
- 前端开发中79条不可忽视的知识点汇总
-
过往一些不足的地方,通过博客,好好总结一下。1.css禁用鼠标事件...
- Chrome 开发工具之Network
-
经常会听到比如"为什么我的js代码没执行啊?","我明明发送了请求,为什么反应?","我这个网站怎么加载的这么慢?"这类的问题,那么问题既然存在,就需要去解决它,需要解决它,首先我们得找对导致问题的原...
- 轻量级 React.js 虚拟美化滚动条组件RScroll
-
前几天有给大家分享一个Vue自定义滚动条组件VScroll。今天再分享一个最新开发的ReactPC端模拟滚动条组件RScroll。...
- 一文解读JavaScript事件对象和表单对象
-
前言相信做网站对JavaScript再熟悉不过了,它是一门脚本语言,不同于Python的是,它是一门浏览器脚本语言,而Python则是服务器脚本语言,我们不光要会Python,还要会JavaScrip...
- Python函数参数黑科技:*args与**kwargs深度解析
-
90%的Python程序员不知道,可变参数设计竟能决定函数的灵活性和扩展性!掌握这些技巧,让你的函数适应任何场景!一、函数参数设计的三大进阶技巧...
- 深入理解Python3密码学:详解PyCrypto库加密、解密与数字签名
-
在现代计算领域,信息安全逐渐成为焦点话题。密码学,作为信息保护的关键技术之一,允许我们加密(保密)和解密(解密)数据。...
- 阿里Nacos惊爆安全漏洞,火速升级!(附修复建议)
-
前言好,我是threedr3am,我发现nacos最新版本1.4.1对于User-Agent绕过安全漏洞的serverIdentitykey-value修复机制,依然存在绕过问题,在nacos开启了...
- Python模块:zoneinfo时区支持详解
-
一、知识导图二、知识讲解(一)zoneinfo模块概述...
- Golang开发的一些注意事项(一)
-
1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...
- Python鼠标与键盘自动化指南:从入门到进阶——键盘篇
-
`pynput`是一个用于控制和监控鼠标和键盘的Python库...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)