Skip to content

Vue3高级语法补充

自定义指令

在 Vue 的模板语法中有各种各样的指令:v-show、v-for、v-model 等,除了使用这些指令之外,Vue 还允许我们来自定义自己的指令

  • 注意:在 Vue 中,代码的复用和抽象主要还是通过组件
  • 通常在某些情况下,你需要对 DOM 元素进行底层操作,这个时候就会用到自定义指令

自定义指令种类

  • 局部指令:组件中通过 directive 选项,只能在当前组件中使用
  • 全局指令:app 的 drective 方法,可以在任意组件中被使用

当某个元素挂载完成可以自动获取焦点:

  • 方式一:默认实现方式

    html
    <input ref="input" type="text" />
    
    <script>
    import { ref, onMounted } from 'vue'
    export default {
      setup() {
        const input = ref(null)
        onMounted(() => {
          input.value.focus()
        })
        return {
          input
        }
      }
    }
    </script>
  • 方式二:自定义 v-focus 局部指令

    html
    <input v-focus type="text" />
    
    <script>
    export default {
      directives: {
        focus: {
          mounted(el, bindings, vnode, preVnode) {
            el.focus()
          }
        }
      }
    }
    </script>
  • 方式三:自定义 v-focus 全局指令

    js
    // main.js
    app.directive('focus', {
      mounted(el, bindings, vnode, preVnode) {
        console.log('focus mounted')
        el.focus()
      }
    })

指令的生命周期和参数修饰符

一个指令定义的对象,Vue 提供了如下几个钩子函数:

  • created:在绑定元素的 attribute 或事件监听器被应用之前调用
  • beforeMount(bind):当指令第一次绑定到元素并且在挂载父组件之前调用
  • mounted(inserted):在绑定元素的父组件被挂载后调用
  • beforeUpdate:在更新包含组件的 VNode 之前调用
  • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用
  • beforeUnmount(componentUpdated):在卸载绑定元素的父组件之前调用
  • unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次
html
<button v-if="counter < 2" v-why.aaa.bbb="'cat'" @click="increment">
  当前计数: {{ counter }}
</button>

<script>
import { ref } from 'vue'
export default {
  directives: {
    why: {
      created(el, bindings, vnode, preVnode) {
        console.log('created')
        console.log(bindings.value) // cat
        console.log(bindings.modifiers) // {aaa: true, bbb: true}
      },
      beforeMount() {
        console.log('beforeMount')
      },
      mounted() {
        console.log('mounted')
      },
      beforeUpdate() {
        console.log('beforeUpdate')
      },
      updated() {
        console.log('updated')
      },
      beforeUnmount() {
        console.log('beforeUnmount')
      },
      unmounted() {
        console.log('unmounted')
      }
    }
  },
  setup() {
    const counter = ref(0)
    const increment = () => counter.value++
    return {
      counter,
      increment
    }
  }
}
</script>

自定义指令练习

js
// src/directives/index.js
import registerFormatTime from './format-time';
export default function registerDirectives(app) {
  registerFormatTime(app);
}

// src/directives/format-time.js
import dayjs from 'dayjs'
export default function (app) {
  app.directive('format-time', {
    created(el, bindings) {
      bindings.formatString = 'YYYY-MM-DD HH:mm:ss'
      if (bindings.value) {
        bindings.formatString = bindings.value
      }
    },
    mounted(el, bindings) {
      const textContent = el.textContent
      let timestamp = parseInt(textContent)
      if (textContent.length === 10) {
        timestamp = timestamp * 1000
      }
      el.textContent = dayjs(timestamp).format(bindings.formatString)
    }
  })
}

Teleport

在组件开发中,我们封装一个组件 A,在另一个组件 B 中使用:

  • 那么组件 A 中 template 的元素,会被挂载到组件 B 中的 template 的某个位置
  • 最终我们的应用程序会形成一颗 DOM 树结构

但是某些情况下,我们希望组件不是挂载到这个组件树上,可能是移动到 Vue app 之外的其他位置:

  • 比如移动到 body 元素上,或者我们有其他的 div#app 之外的元素上
  • 这个时候我们就可以通过 teleport 来完成

Teleport 是什么?

  • 它是一个 Vue 提供的内置组件,类似于 react 的 Portals

  • teleport 翻译过来是心灵传输、远距离传输的意思

    它有两个属性:

    • to:指定将其中的内容移动到目标元素,可以使用选择器
    • disabled:是否禁用 teleport 的功能
html
<teleport to="body">
  <span>dog</span>
</teleport>
<teleport to="#why">
  <span>cat</span>
</teleport>
<teleport to="#why">
  <span>bird</span>
</teleport>

Vue 插件

通常我们向 Vue 全局添加一些功能时,会采用插件的模式,它有两种编写方式:

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

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

  • 添加全局方法或者 property,通过把它们添加到 config.globalProperties 上实现
  • 添加全局资源:指令/过滤器/过渡等
  • 通过全局 mixin 来添加一些组件选项
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能
js
app.use({
  install(app) {
    app.config.globalProperties.$name = '$name'
  }
})
app.use(function (app) {
  app.config.globalProperties.$age = '$age'
})

export default {
  setup() {
    const instance = getCurrentInstance()
    console.log(instance.appContext.config.globalProperties.$name)
    console.log(instance.appContext.config.globalProperties.$age)
  },
  created() {
    console.log(this.$name)
    console.log(this.$age)
  }
}

常备不懈,才能有备无患