掌握与使用Vue指令
起心动念
早年,去“招商城科”面试,当时面试官为了考察我对Vue框架的熟悉程度,就问了一系列Vue指令的问题,当时自己是个铁憨憨,没回答好。本以为会换来一句,“那你回去等通知吧”,可面试官没有赶我走,反而很和善跟我分享,他说除了组件之外,指令也是Vue框架极具魅力的特点之一,修饰符是典型的装饰器模式等等,让我感念至今。
前不久,项目中刚好遇到封装一个v-props-bind
指令的需求,索性趁着这个机会,系统整理一下关于Vue指令的知识点与所思所想。
目标
我们通过以下3个目标,完成对Vue指令的整理与掌握:
- 自定义指令的创建与使用
- 走读Vue指令相关的API
- 了解指令解析到运行的原理
封装v-props-bind
指令
v-bind
有什么问题?
我们都知道,内置指令v-bind
是对组件进行attribute/props属性绑定,如下面动态渲染Input
组件的例子,已定义的options
和position
属性会进行Props传递赋值,未定义的id
、datasetId
、component
属性会转成DOM对象的attribute属性暴露到页面上。
// config.js
export default [
{
id: "1471EB96-BBCF-303F-DB22-4EEEB0032D5C",
component: "Input",
datasetId: "D19867BE-3750-E7E8-1ED2-4457AF95D9EC",
options: {
placehold: "请输入昵称",
disabled: false,
},
position: {
width: "200px",
height: "20px",
},
}
]
// Input.vue 的props定义
defineProps({
options: {
required: false,
default: {
placehold: "请输入",
disabled: true,
},
},
position: {
required: false,
},
});
<template>
<template v-for="config in kanbanComponentList" :key="config.id">
<!-- v-bind会对整个config对象进行绑定 -->
<component
:is="componentMap[config.component]"
v-bind="config"
></component>
<!-- 渲染出来的DOM是这样婶的 -->
<input
data-v-469af010=""
type="text" placeholder="请输入昵称"
component="Input"
id="1471EB96-BBCF-303F-DB22-4EEEB0032D5C"
datasetId="D19867BE-3750-E7E8-1ED2-4457AF95D9EC"
style="width: 500px; height: 20px;"/>
<!-- 但我们期望渲染成这样 -->
<input
data-v-469af010=""
type="text" placeholder="请输入昵称"
style="width: 500px; height: 20px;">
</template>
</template>
虽然把额外的配置属性转成HTML attribute属性,给暴露到页面上也没有报错,但这显然是不合适的。
所以,我们能不能封装一个v-props-bind
指令,当配置对象进行绑定时,能根据当前组件定义的props key
过滤绑定的数据。
自定义指令中,如何使用内置指令?
一开始,想着在自定义指令的生命周期钩子里面,筛选数据后,直接调用v-bind
进行数据绑定的。然而找了一圈资料,四处问人,也没有找到在JS代码中应用内置指令的办法。于是转换思路,将要绑定的数据赋值到vnode
对象中。
// setup中创建指令
const vPropsBind = {
beforeMount (el, binding, vnode) {
const { value } = binding;
const {
ctx: { props },
} = vnode;
// 根据组件propKeys进行数据绑定
const propKeys = Object.keys(props);
propKeys.forEach((propKey) => {
props[propKey] = value[propKey];
});
}
}
<!-- 视图中运用指令 -->
<component :is="config.component" v-props-bind="config"></component>
完整案例代码
Vue指令的API走读
Vue中代码的封装方式有哪些?
1. 插件(Plugin)
通过Vue.use()
注册使用的全局的类库,通常是单例的。大致有两大类:
(1) 扩展能力的工具库
比如官方提供的 vue-router
路由管理库,Vuex
状态管理库
(2) 开箱即用的UI组件库
比如(element-ui
/iview
/Vuetify
),各类移动端/PC端的UI库,提供
2. 组件(Component)
日常最熟悉的代码封装方式,通常将可复用的视图和交互逻辑封装到一个以.vue
后缀的文件组织的SFC(single file component)中。组件可以相互嵌套,具有相似的生命周期钩子。最终可以注册成为全局组件,也可以注册成局部组件。
3. 混入(Mixin)
Vue2的代码封装方式,所谓Mixin对象实际上就是组件的可复用选项(option),比如data/methods/computed等。目标组件的mixins
配置项可以“混入合并”多个mixin对象。
4. 组合式函数(Composable Functions)
Vue3的代码封装方式,和React Hooks一样,本质就是闭包函数,封装复用那些有状态的业务逻辑,用以替代Vue2 Mixin
。
5. 指令(Directive)
Vue提供一系列的内置指令并允许自定义指令,进行视图层的逻辑控制,比如v-if
/v-show
/v-for
。
其实指令能控制从template
到vnode
再到挂载后的DOM
,的每一层对象。
这也是为什么Vue不属于纯粹的MVVM框架的地方,它不但能操作Virtual DOM
,还直接操作真实DOM,相当于补上了配送的最后一里路。
Vue常见的指令和修饰符有哪些?
简单罗列一下Vue的指令,详情参考Vue官网
指令 | 一句话解释 |
---|---|
v-if | 根据表达式,判断视图渲染 |
v-show | 根据表达式,判断DOM元素的display属性是否显示 |
v-show | 进行视图的循环渲染 |
v-bind | 进行组件的属性绑定(attribute或者props) |
v-on | 进行组件的事件绑定,并可通过一系列修饰符如.stop /.prevent /.once 等改变事件的行为 |
v-pre | 跳过编译,直接显示template中的Mustache标签 |
v-once | 组件只渲染一次,此后都视为静态内容,跳过diff检查 |
v-model | 对输入场组件,如input/select等,进行双向数据绑定 |
v-memo | Vue3.2新增,指定组件缓存。当v-memo="[]"时,与v-once效果相同 |
v-clock | 当DOM未完成编译前隐藏该元素,避免白屏的闪烁效果 |
同时用v-for
与v-if
优先执行哪个?如何进行优化?
Vue2中v-for
优先级更高,而Vue3中v-if
优先级更高。
优先级文档:
优化分两种情况:
- 如果是想先进行判断,再进行循环。可以在
v-for
代码再套一层父节点<template v-if>
进行判断,使得代码层次看起来更清晰。 - 如果是想先循环后判断,对则可以提前通过
computed
进行处理,视图template
仅负责循环渲染。
优化文档:
v-for
key的作用
循环渲染视图时,通过key对vnode
对象进行唯一标识,从而计算机因为下标进行数组对比带来的更新问题
Vue2/3如何自定义指令?
函数式与配置的声明方式
Vue2可以通过directives
配置项的方式创建指令,Vue3 setup中则更简介,只要声明以小写v
开头驼峰命名的对象变量即可。
// 配置项的声明方式
const focust = {
mounted: (el) => el.focus()
}
export default {
directives: {
focus
}
}
// setup下的声明方式
const vFocus = {
mounted: (el) => el.focus()
}
Vue2与3指令钩子对比
Vue2指令 | Vue3指令 | 说明 |
---|---|---|
bind | 指令第一次绑定到元素时调用,可进行初始化操作 | |
created | 在元素attribute和事件绑定前调用 | |
beforeMount | 在元素被插入DOM前调用 | |
inserted | mounted | 在绑定元素的父组件+元素自己+所有子节点都挂载完成后调用 |
update | beforeUpdate | 绑定元素的父组件更新前调用 |
componentUpdated | updated | 绑定元素的父组件+元素自己+所有子节点都更新后调用 |
unbind | beforeUnmount | 绑定元素的父组件卸载之前调用 |
unmounted | 绑定元素的父组件卸载后调用 |
文档参考:
Vue3 withDirectives()
/resolveDirective()
API
withDirectives
给vnode对象添加自定义指令,并返回这个vnode对象resolveDirective()
按名称解析已注册的指令
文档参考:
深入指令原理
v-if
/v-show
/v-html
的实现原理
如上面提到的,v-if
在template解析的时候,会根据表达式结果跳过vnode的生成,而v-show
会生成vnode对象,render也会生成对应的DOM节点,只是表达式结果,修改display属性进行隐藏或显示
而v-html
渲染流程是先移除当前元素下的所有子节点,然后设置innerHTML的值为v-html
的值。
v-model
的实现原理
- “响应式数据”和“双向数据绑定”是两个概念,“响应式数据”是指数据驱动VDOM,无需操作DOM就能实现视图的刷新,是单向的;而“双向数据绑定”是双向的,不但JS数据能刷新视图,视图的输入也能改变JS数据
v-model
就是双向数据绑定的应用,以<input v-model="firstName">
为例,它是<input v-bind:value="firstName" v-on:change="(val) => firstName = value">
的语法糖v-model
不但可以在input
/select
/raio
等表单组件使用,也可以在自定义组件中使用;Vue3还可以在同一个组件中,多个绑定v-model
的双向数据
模板解析运行的原理
模板编译过程是如何解析指令的?
- baseParse阶段 :
<template>
进行词法分析,生成AST; - transform阶段 :将AST Element进行转换,其中就包含指令的转换;
- generator阶段 :将AST Element生成可执行的
render
函数,执行render
函数可得到vnode
对象; - patch阶段 :得到vnode对象后,指令挂载在
vNode.data.directives
属性上; - mounted阶段 :通过vnode对象生成真实的DOM;
指令的钩子如何生效的?
指令只关心组件的3个阶段
虽然组件的生命周期钩子很多,但对于指令来说,只关心vnode渲染的create
,update
,destroy
三个阶段。
所以只需要监听这三个阶段,执行updateDirectives
函数来处理指令的相关逻辑:
export default {
create: updateDirectives,
update: updateDirectives,
destroy: function unbindDirectives (vnode: VNodeWithData) {
updateDirectives(vnode, emptyNode)
}
}
通过指令状态调用钩子
创建指令时,配置对象定义了几个钩子函数,这些钩子分别对应着指令的几种状态。updateDirectives
函数就是对比新旧vnode对象的指令列表,然后执行不同的钩子函数,让指令生效。
鸣谢
最后,我想说说周星驰的电影《功夫》的开头和结尾两场戏。
拳脚棍棒
《功夫》除了是一部老少咸宜的喜剧片之外,更难得的是它包含了周星驰对于中华武学的深层理解。
电影的第一场高潮戏就是因为周星驰假扮斧头帮收保护费,结果惹来了真的斧头帮。
危难之际,猪笼城寨里擀面的阿鬼( 五郎八卦棍 ),裁缝店的娘娘腔( 洪家铁线拳 ),还有沉默的苦力强( 少林十二路谭腿 )挺身而出,打跑了斧头帮。
这三人刻意的出场顺序,实际就对应了他们的武力值排行: 先腿,后拳,再兵器 。
而在周星驰看来, 习武之人,基本功就是扎马步。等腿有力,下盘结实了,才可以学拳。拳脚功夫可以了,才能碰兵器。 这是学功夫的过程,顺序不能乱。
而我们学技术也是要循序渐进,要有足够的耐心。从打基础开始,然后慢慢到接触不同的框架/工具。当然,过程中总会有人会抨击你:
- “Vue这么简单,还要学吗?”
- “React还要学吗?不就跟Vue一样吗?”
- “居然还在看CSS,这有什么难的?”
别人眼高手低,但自己不要被CPU就好。学基础不丢人,工作经验好几年了,如果基础还这么差劲才丢人呢!
如来神掌
电影《功夫》的最后一幕,周星驰最终破茧成蝶,用如来神掌打败了火云邪神。而面对偷袭,周星驰一掌击垮了一幢楼,故意打偏的。
意思很明确,我有能力,也有理由干掉你,但我没有这么做。火云邪神,呆住了。
火云邪神是武痴,他的世界里,赢就是唯一准则,即便是偷袭,即便是杀人。他不理解周星驰的如来神掌为什么这么厉害,更不理解如何能做到“止杀”。怔怔问道“这是什么武功?”
周星驰只是把他偷袭的武器,化作竹蜻蜓飞走了,笑着问他“想学吗,我教你啊”
当然,在“耗子尾汁”,“年轻人不讲武德”等表情包横行的当下,“以德服人” 这四个字,看起来就TM是搞笑的。
但电影中,周星驰的确通过化干戈为玉帛,做到了老乞丐说的“维护世界和平的重任”。而不是以暴制暴,更不是以杀止杀。
OK,电影说完了,回到做技术这件事。不可否认,技术圈也是有鄙视链的。
在职场上,要是你基础不扎实,学习态度还不端正,就难免会被人鄙视。又菜又哇哇叫的,当然是不讨人喜欢的。
而面试的场合就更不必说了,本就是在有限的时间内,一个人要对另一个人进行审视和考验,对话双方的地位天然就是不对等的,会放大这种鄙视的感觉。
所以,最后我还想再次感谢“招商城科”面试我的大佬。能够跳脱出当下,用平等/尊重/和善的态度沟通对话。跟我想朋友一样,聊Vue的指令,也给了我学习的动力,让我想成为跟你一样的大佬!
转载自:https://juejin.cn/post/7204345146162479160