# 一、逻辑复用

组合式函数 (opens new window)

# 1、组合式函数 ✍

  “组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数(hooks)。

VueUse (opens new window):一个日益增长的 vue3 组合式函数集合

鼠标跟踪功能:useMouse

在 VueUse 中收集了该 Hooks 函数:useMouse (opens new window)

  • 逻辑封装
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}
  • 使用示例
<script setup>
  import { useMouse } from './mouse.js'

  const { x, y } = useMouse()
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>
计数器:useCounter

在 VueUse 中收集了该 Hooks 函数:useCounter (opens new window)

使用标题:useTitle

在 VueUse 中收集了该 Hooks 函数:useTitle (opens new window)

# 2、Hooks 优势

  vue2 中的 mixin 复用的是共用的一些相同的配置,但也存在不少问题:

问题

  官方总结了几个点,但总结来说,就一个点:

多个 mixin 时,数据未与 mixin 做到分离(主要问题)

  这就是 vue2 官方声明:如果使用全局 mixin 时,推荐使用 vue 插件的原因。

  vue3 中的组合式函数中使用 ref + 解构模式,让属性的来源在消费组件时一目了然

自定义 hook 的优势: 复用代码, 让 setup 中的逻辑更清楚易懂

# 3、vue3 插件

  vue3 的插件和 vue2 的插件使用几乎一致,只是变换了一些全局 API 罢了:

插件可以是一个带 install() 方法的对象,亦或直接是一个将被用作 install() 方法的函数。插件选项 (app.use() 的第二个参数) 将会传递给插件的 install() 方法。

官方使用示例
  • i18n.js
// plugins/i18n.js
export default {
  install: (app, options) => {
    // 注入一个全局可用的 $translate() 方法
    app.config.globalProperties.$translate = (key) => {
      // 获取 `options` 对象的深层属性
      // 使用 `key` 作为索引
      return key.split('.').reduce((o, i) => {
        if (o) return o[i]
      }, options)
    }

    // 将插件接收到的 options 参数提供给整个应用
    app.provide('i18n', options)
  }
}
  • main.js
import i18nPlugin from './plugins/i18n'

app.use(i18nPlugin, {
  greetings: {
    hello: '你好'
  }
})
  • 使用
<template>
  <h1>{{ $translate('greetings.hello') }}</h1>
</template>

<script setup>
import { inject } from 'vue'

const i18n = inject('i18n')

console.log(i18n.greetings.hello)
</script>

  下面写一个真实开发的示例:

  • main.ts
import registerIcons from './global/register-element-icons.ts'

app.use(registerIcons)
  • register-element-icons.ts
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import type { App } from 'vue'

const registerIcons = (app: App<Element>) => {
  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
  }
}

export default registerIcons

# 4、自定义指令

  vue3 的自定义指令和 vue2 的自定义指令在没有使用 setup 语法糖的情况下,使用几乎一致,只是变换了一些全局 API 罢了:

vue2 中的全局自定义指令
// 简写形式
Vue.directive('color', function (el, binding){
    el.style.color = binding.value
})

// 完整形式
Vue.directive('color', {
  bind (el, binding) {
    el.style.color = binding.value
  }

  update (el, binding) {
    el.style.color = binding.value
  }
})

提示

  • 在 vue2 中,仅仅提供了bindinsertedupdate、(componentUpdated、unbind)几个钩子函数。
  • 在 vue3 中,它提供的指令钩子大多生命周期钩子,如:created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted
  • setup 语法糖中 🤔
<script setup>
// 在模板中启用 v-color
const vColor = (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value
}
</script>
  • 全局自定义指令
const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('color', (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value
})

# 二、vue 发展

# 1、vue2 - vue3.0

  vue3.0 是 vue 自 2014 年以来的一个重大版本。从开发角度来开,变化如下:

  • 重大变化

全面拥抱 TypeScript

Composition API

  • 细微变化
scoped 样式
  • Vue 2.x 中的深度选择器可以使用>>>/deep/::v-deep操作符:

    Vue Loader 中有相关描述:Scoped CSS (opens new window)

    <style scoped>
      .a ::v-deep .b {
        /* ... */
      }
    </style>
    
  • Vue3.0 中做出了调整,改为使用:deep() 这个伪类:

    vue3 官方文档中的相关描述:CSS 功能 (opens new window)

    <style scoped>
      .a :deep(.b) {
        /* ... */
      }
    </style>
    
全局 API 转移
  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。
  //注册全局组件
  Vue.component('MyButton', {
    data: () => ({
      count: 0
    }),
    template: '<button @click="count++">Clicked {{ count }} times.</button>'
    })

  //注册全局指令
  Vue.directive('focus', {
    inserted: el => el.focus()
  }
  • Vue3.0 中对这些 API 做出了调整:

    • 将全局的 API,即:Vue.xxx调整到应用实例(app)上
2.x 全局 API(Vue 3.x 实例 API (app)
Vue.config.xxxx app.config.xxxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties
Vue.filter 移除
Vue.extend 移除
部分 API 移除
  • 移除v-on.native修饰符

    • 父组件中绑定事件

      <my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
      
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close'] // 由子组件指定哪些是自定义事件(自然父组件的click就可以作为原生事件被筛选出来)
        }
      </script>
      
  • 移除实例中的 $on$off$once 方法(即 vue3 去除了 EventBus)

  • 移除keyCode 作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • ......

# 2、vue3.2 发布

<script setup>
import { ref } from 'vue'

const color = ref('red')
</script>

<template>
  <button @click="color = color === 'red' ? 'green' : 'red'">Color is: {{ color }}</button>
</template>

<style scoped>
button {
  color: v-bind(color);
}
</style>
  • 其他新特性
新特性 学习文档
Web Components definecustomelement (opens new window)自定义元素 (opens new window)
性能提升 v-memo (opens new window)
服务端渲染 @vue/server-renderer (opens new window)
Effect 作用域 API effectScope (opens new window)

# 3、vue3.3 发布

此版本侧重于开发人员体验改进 - 特别是 TypeScript 的 SFC <script setup> 用法。

  • 重大新特性
setup 语法糖中的参数类型

  以前,在 和 defineEmits 的类型 defineProps 参数位置中使用的类型仅限于本地类型,并且仅支持类型文本和接口

让 defineProps 支持外部类型文件导入的方式,并支持一组有限的复杂类型

拓展

  其实在 vue3.3 之前,我们也可以通过某种方式实现间接的引入外部类型文件:

<script setup lang="ts">
import type { PropsType } from './foo'

interface IProps {
  data: PropsType
}

cosnt props = defineProps<IProps>()
</script>
// ./foo.ts
export interface Props {
  name: string
  age: number
}
<script setup lang="ts">
import type { Props } from './foo'

// imported + intersection type
defineProps<Props & { extraProp?: string }>()
</script>

<script setup> 组件现在可以通过以下 generic 属性接受泛型类型参数

<script setup lang="ts" generic="T extends string | number, U extends Item">
import type { Item } from './types'
defineProps<{
  id: T
  list: U[]
}>()
</script>

对原 defineEmits 参数的调用签名语法,提供一个更加简洁的语法

// 原来的调用签名语法
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
  change: [id: number] // 具名元组语法
  update: [value: string]
}>()
  • 其他新特性

defineSlots (opens new window)defineOptions (opens new window)toValue (opens new window)

  • 原功能增强

toRef (opens new window)

  • 实验性功能

defineModel

更新于 : 8/7/2024, 2:16:31 PM