likes
comments
collection
share

掌握与使用Vue指令

作者站长头像
站长
· 阅读数 52

起心动念

早年,去“招商城科”面试,当时面试官为了考察我对Vue框架的熟悉程度,就问了一系列Vue指令的问题,当时自己是个铁憨憨,没回答好。本以为会换来一句,“那你回去等通知吧”,可面试官没有赶我走,反而很和善跟我分享,他说除了组件之外,指令也是Vue框架极具魅力的特点之一,修饰符是典型的装饰器模式等等,让我感念至今。

前不久,项目中刚好遇到封装一个v-props-bind指令的需求,索性趁着这个机会,系统整理一下关于Vue指令的知识点与所思所想。

目标

我们通过以下3个目标,完成对Vue指令的整理与掌握:

  1. 自定义指令的创建与使用
  2. 走读Vue指令相关的API
  3. 了解指令解析到运行的原理

封装v-props-bind指令

v-bind有什么问题?

我们都知道,内置指令v-bind是对组件进行attribute/props属性绑定,如下面动态渲染Input组件的例子,已定义的optionsposition属性会进行Props传递赋值,未定义的iddatasetIdcomponent属性会转成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>

完整案例代码

gitee.com/lianghuilin…

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。 其实指令能控制从templatevnode再到挂载后的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-memoVue3.2新增,指定组件缓存。当v-memo="[]"时,与v-once效果相同
v-clock当DOM未完成编译前隐藏该元素,避免白屏的闪烁效果

同时用v-forv-if优先执行哪个?如何进行优化?

Vue2中v-for优先级更高,而Vue3中v-if优先级更高。

优先级文档:

优化分两种情况:

  • 如果是想先进行判断,再进行循环。可以在v-for代码再套一层父节点<template v-if>进行判断,使得代码层次看起来更清晰。
  • 如果是想先循环后判断,对则可以提前通过computed进行处理,视图template仅负责循环渲染。

优化文档:

v-forkey的作用

循环渲染视图时,通过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前调用
insertedmounted在绑定元素的父组件+元素自己+所有子节点都挂载完成后调用
updatebeforeUpdate绑定元素的父组件更新前调用
componentUpdatedupdated绑定元素的父组件+元素自己+所有子节点都更新后调用
unbindbeforeUnmount绑定元素的父组件卸载之前调用
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的双向数据

模板解析运行的原理

掌握与使用Vue指令

模板编译过程是如何解析指令的?

  1. baseParse阶段<template>进行词法分析,生成AST;
  2. transform阶段 :将AST Element进行转换,其中就包含指令的转换;
  3. generator阶段 :将AST Element生成可执行的render函数,执行render函数可得到vnode对象;
  4. patch阶段 :得到vnode对象后,指令挂载在vNode.data.directives属性上;
  5. mounted阶段 :通过vnode对象生成真实的DOM;

指令的钩子如何生效的?

指令只关心组件的3个阶段

虽然组件的生命周期钩子很多,但对于指令来说,只关心vnode渲染的createupdatedestroy三个阶段。 所以只需要监听这三个阶段,执行updateDirectives函数来处理指令的相关逻辑:

export default {
  create: updateDirectives,
  update: updateDirectives,
  destroy: function unbindDirectives (vnode: VNodeWithData) {
    updateDirectives(vnode, emptyNode)
  }
}
通过指令状态调用钩子

创建指令时,配置对象定义了几个钩子函数,这些钩子分别对应着指令的几种状态。updateDirectives函数就是对比新旧vnode对象的指令列表,然后执行不同的钩子函数,让指令生效。

文档参考:vue-js.com/learn-vue/d…

鸣谢

最后,我想说说周星驰的电影《功夫》的开头和结尾两场戏。

拳脚棍棒

《功夫》除了是一部老少咸宜的喜剧片之外,更难得的是它包含了周星驰对于中华武学的深层理解。

电影的第一场高潮戏就是因为周星驰假扮斧头帮收保护费,结果惹来了真的斧头帮。

掌握与使用Vue指令

危难之际,猪笼城寨里擀面的阿鬼( 五郎八卦棍 ),裁缝店的娘娘腔( 洪家铁线拳 ),还有沉默的苦力强( 少林十二路谭腿 )挺身而出,打跑了斧头帮。

这三人刻意的出场顺序,实际就对应了他们的武力值排行: 先腿,后拳,再兵器

而在周星驰看来, 习武之人,基本功就是扎马步。等腿有力,下盘结实了,才可以学拳。拳脚功夫可以了,才能碰兵器。 这是学功夫的过程,顺序不能乱。

而我们学技术也是要循序渐进,要有足够的耐心。从打基础开始,然后慢慢到接触不同的框架/工具。当然,过程中总会有人会抨击你:

  • “Vue这么简单,还要学吗?”
  • “React还要学吗?不就跟Vue一样吗?”
  • “居然还在看CSS,这有什么难的?”

别人眼高手低,但自己不要被CPU就好。学基础不丢人,工作经验好几年了,如果基础还这么差劲才丢人呢!

如来神掌

掌握与使用Vue指令

电影《功夫》的最后一幕,周星驰最终破茧成蝶,用如来神掌打败了火云邪神。而面对偷袭,周星驰一掌击垮了一幢楼,故意打偏的。

意思很明确,我有能力,也有理由干掉你,但我没有这么做。火云邪神,呆住了。

火云邪神是武痴,他的世界里,赢就是唯一准则,即便是偷袭,即便是杀人。他不理解周星驰的如来神掌为什么这么厉害,更不理解如何能做到“止杀”。怔怔问道“这是什么武功?”

周星驰只是把他偷袭的武器,化作竹蜻蜓飞走了,笑着问他“想学吗,我教你啊”

掌握与使用Vue指令

当然,在“耗子尾汁”,“年轻人不讲武德”等表情包横行的当下,“以德服人” 这四个字,看起来就TM是搞笑的。

掌握与使用Vue指令

但电影中,周星驰的确通过化干戈为玉帛,做到了老乞丐说的“维护世界和平的重任”。而不是以暴制暴,更不是以杀止杀。


OK,电影说完了,回到做技术这件事。不可否认,技术圈也是有鄙视链的。

在职场上,要是你基础不扎实,学习态度还不端正,就难免会被人鄙视。又菜又哇哇叫的,当然是不讨人喜欢的。

而面试的场合就更不必说了,本就是在有限的时间内,一个人要对另一个人进行审视和考验,对话双方的地位天然就是不对等的,会放大这种鄙视的感觉。

所以,最后我还想再次感谢“招商城科”面试我的大佬。能够跳脱出当下,用平等/尊重/和善的态度沟通对话。跟我想朋友一样,聊Vue的指令,也给了我学习的动力,让我想成为跟你一样的大佬!

掌握与使用Vue指令

转载自:https://juejin.cn/post/7204345146162479160
评论
请登录