Vue进阶

VUE2.X自定义指令

私有自定义指令

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令

directives: {
  color:{
    // 为绑定的元素设置红色字体
    bind(el){
      // el 是绑定此指令的 dom 对象
      el.style.color='red'
    }
  }
}

使用:需要加上 v- 前缀

<!-- 上面声明的自定义指令名为 color -->
<!-- 使用自定义指令名 color,需要加上 v- 前缀 -->
<h1 v-color>app组件</h1>

动态绑值

data:{
  return{
    color:'red'
  }
}
<h1 v-color="color">app组件</h1>

通过 binding 获取指令的参数值

directives: {
  color:{
    bind(el,binding){
      // el 是绑定此指令的 dom 对象
      // 通过 binding 对象.value 属性获取动态的参数值
      el.style.color=bing.value
    }
  }
}

update 函数

bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函数会在每次 DOM 更新时被调用

directives: {
  color:{
    // 指令第一次绑定元素时调用
    bind(el,binding){
      // el 是绑定此指令的 dom 对象
      // 通过 binding 对象.value 属性获取动态的参数值
      el.style.color=bing.value
    }
    // 每次 dom 更新都调用
    update(el,binding){
       el.style.color=bing.value
    }
  }
}

简写

insert 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式

directives: {
  color(el,binding){
     el.style.color=bing.value
  }
}

全局自定义指令

全局共享的自定义指令需要通过 Vue.directive()进行声明

// 全局写法
Vue.directive('color', (el, binding) => {
  el.style.color = binding.value
}))

自定义指令钩子函数

  • bind:只会调用一次,指令 第一次 绑定到元素时会调用
  • inserted:被绑定元素插入父节点时调用 。
  • update:元素第一次绑定不会触发,绑定的值发生更新触发,可能发生在其子节点更新之前。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • u0nbind:只调用一次,指令与元素解绑时调用。

注意事项

  • 自定义指令使用时需要添加 v- 前缀
  • 指令名如果是多个单词,要使用 camel-case 短横线命名方式,不要用 camelCase 驼峰命名
  • 自定义指令三个函数里的 this 指向 window
<span v-big-number="n"></span>
data() {
  return {
    n: 1
  }
},
directives: {
  // 添加引号才是对象键名完整写法
  // 平时不加引号都是简写形式
  // 遇到短横线的键名就必须添加引号
  'big-number': {
    bind(el, binding) {
      console.log(this) // Window
      el.innerText = binding.value * 10
    }
  }
}

VUE3.X自定义指令

钩子函数的变化

  • created:在绑定元素的 attribute 或事件监听器被应用之前调用;
  • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用;
  • mounted:在绑定元素的父组件被挂载后调用;
  • beforeUpdate:在更新包含组件的 VNode 之前调用;
  • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用;
  • beforeUnmount:在卸载绑定元素的父组件之前调用;
  • unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次;

在setup语法糖使用

<input v-focus ></input>
<script setup>
// 局部指令, 变量名为驼峰命名(vFocus = v-focus)
const vFocus = {
  mounted: (el) => {
    el.focus()
    console.log(el, '已经自动获得焦点')
  }
}
</script>

指令的参数和修饰符

<!--  
info是参数的名称
aaa是修饰符的名称
后面是传入的具体的值
-->
<span v-msg:info.aaa="{name:'zs',age:'18'}">11</span>
const vMsg = {
  mounted: (el, bindings) => {
    // 把标签内容改成传入的值
    el.textContent = bindings.value.age
    console.log(bindings)
  }
}

Teleport

一般情况下组件是挂载在 Vue app template的某个位置,形成一颗DOM树结构

如果要组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置

比如:移动到body元素上,或者我们有其他的div#app之外的元素上

Teleport 就可以实现

Teleport 两个属性:

  • to:指定将其中的内容移动到的目标元素,可以使用选择器;
  • disabled:是否禁用 teleport 的功能;
<template>
	<div class="myapp">
    <teleport to="body">
      <h2>哈哈哈</h2>
  	</teleport>
  </div>
</template>

同理也可以把组件放进去

多个teleport应用到同一个目标上(to的值相同),那么这些目标会进行合并

异步组件和Suspense

Suspense显示的是一个实验性的特性,API随时可能会修改

Suspense是一个内置的全局组件,该组件有两个插槽:

  • default:如果 default 可以显示,那么显示default的内容;
  • fallback:如果 default 无法显示,那么会显示fallback插槽的内容;
<suspense>
  <template #default>
    <async-home/>
  </template>
  <template #fallback>
    <h2>Loading</h2>
  </template>
</suspense>

Vue插件

如果要在Vue全局添加一些功能,可以采用插件模式

两种方式:

  • 对象:一个对象,但是必须包含一个 install 的函数,该函数会在安装插件时执行

    app.use({
      install: function(app,options) {
        console.log("传入对象的install被执行:", app)
      }
    })
  • 函数:这个函数会在安装插件时自动执行

    app.use(function(app,options) {
      console.log("传入函数被执行:", app)
    })
    

插件可以完成的功能没有限制,比如下面的几种都是可以的:

  • 添加全局方法或者 property,通过把它们添加到 config.globalProperties 上实现;
  • 添加全局资源:指令/过滤器/过渡等;
  • 通过全局 mixin 来添加一些组件选项;
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能;

h 函数

<template> 内容 Vue在生成真实的DOM之前,会将我们的节点转换成VNode,而VNode组合在一起形成一颗树结构,就是虚拟DOM

编写 createVNode / h 函数,生成对应的VNode

// 完整参数签名
function h(
  type: string | Component,
  props?: object | null,
  children?: Children | Slot | Slots
): VNode

参数:HTML标签名/组件,标签的属性 比如class,内容

import {h} from 'vue'
export default {
  render() {
    return h('div',{class:"app"},'hello h')
  }
}

setup中使用

import {h} from 'vue'
export default {
  setup() {
    return () => h('div',{class:"app"},'hello h')
  }
}

注意事项:

  • 如果没有props,那么通常可以将children作为第二个参数传入;
  • 如果会产生歧义,可以将null作为第二个参数传入,将children作为第三个参数传入;

动画

vue 中为提供了内置组件和对于的api来完成动画
vue 提供 transition 封装组件,可以给元素和组件添加进入离开动画
例如:

  • 条件渲染(v-if / v-show)
  • 动态组件
  • 组件根节点

代码示例:

<transition name="fade">
  <h2 v-if="show">hello world</h2>
</transition>
<style scoped>
  .fade-leave-to,
  .fade-leave-from{
    opacity: 0;
  }

  .fade-enter-to,
  .fade-leave-form{
    opacity: 1;
  }
  
  .fade-enter-active,
  .fade-leave-active{
    transition: opacity 1s ease;
  }
</style>

上面代码类名说明

  • v-enter-from :进入的开始阶段,元素被插入之前的生效,在元素被插入之后的下一帧移除
  • v-enter-active : 进入的过渡阶段,定义过度时间,延迟和曲线函数
  • v-enter-to :进入的结束阶段,在过渡动画完成后移除 v-enter-from
  • v-leave-from :离开的开始阶段
  • v-leave-active :离开的生效过度阶段
  • v-leave-to : 离开的结束阶段,同样也移除 v-leave-from

class的name命名规则如下:

  • 如果我们使用的是一个没有name的transition,那么所有的class是以 v- 作为默认前缀
  • 如果我们添加了一个name属性,比如 <transtion name="fade">,那么所有的class会以 fade- 开头

原理:

  1. 自动嗅探目标元素是否应用了CSS过渡或者动画,如果有,那么在恰当的时机添加/删除 CSS类名
  2. 如果 transition 组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用
  3. 如果没有找到JavaScript钩子并且也没有检测到CSS过渡/动画,DOM插入、删除操作将会立即执行

同理也可以通过animation 实现

.fade-enter-active{
  animation: fade-in 0.5s;
}
.fade-leave-active{
    animation:fade-in 0.5s reverse;
}

@keyframs: fade-in {
  0% { transform: scale(0) }
  50% {transform: scale(1.25)}
  100% {transform: scale(1)}
}

transtion组件属性:

  • mode :解决进入和离开的元素都是在同时开始动画
    • in-out: 新元素先进行过渡,完成之后当前元素过渡离开
    • out-in: 当前元素先进行过渡,完成之后新元素过渡进入
  • appear:解决首次渲染没有动画

<transition-group>
官方文档链接
如果我们希望渲染的是一个列表可以使用<transtion-group>
特点:
- 默认情况下,它不会渲染一个元素的包裹器,但是你可以指定一个元素并以 tag attribute 进行渲染
- 过渡模式不可用,因为我们不再相互切换特有的元素
- 内部元素总是需要提供唯一的 key attribute 值
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身