半年没看vue官网,3.5刚刚发布,趁机整理下

5,479 阅读10分钟

如果想要看3.5完整版api,你需要看英文版的vue官网API Reference | Vue.js (vuejs.org)

以下API测试学习仅在<script setup>语法中使用。

响应式 Props 解构 3.5

以前结构props会使props失去响应式,而且必须通过withDefaults进行赋默认值。现在我们直接可以进行解构,并通过js语法即可赋初值。

interface Props {
  msg?: any
  labels?: string[]
}

const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()

useSlots(),defineSlots() 3.3

<script setup>中没啥用,他主要是获取当前组件传入的插槽对象,不管当前组件有没有定义插槽。这个一般在h函数中使用,具体可以看这里

怎么去理解这个插槽呢?看似子组件是定义者,其实插槽的用处之一就是将子组件中的值向父组件传递。对比函数理解,其实父组件传入的插槽内容才是插槽的定义,子组件才是调用者。所以打印slots才是一堆函数。

<AttrSlot zh name foo="foo-props">
    插槽
    <template #footer>
      <div>footer</div>
    </template>
</AttrSlot>

image.png

但是在ts中,官方更推荐我们使用defineSlots(),使用defineSlots()声明插槽的定义有以下好处

增强类型安全

  • defineSlots 可以帮助你在 TypeScript 中获得更好的类型检查。通过显式定义插槽的结构和属性,可以确保插槽的正确使用。

image.png

提高可读性和可维护性

  • 显式定义插槽可以让其他开发人员更容易理解组件的结构和用途。通过定义插槽,可以清晰地表达哪些插槽是必需的,哪些是可选的。难道可选插槽就是为了提示该插槽是有默认值的吗?没有其他实质性的约束? image.png

增强插槽的灵活性

  • 通过 defineSlots,你可以更灵活地处理插槽的内容。例如,你可以为插槽传递属性,并在父组件中使用这些属性(不定义也可以有提示)。

image.png

<template>
  <div>
    <slot name="header" msg="header"></slot>
    <slot sex="default"></slot>
    <slot name="footer" :age="20"></slot>
  </div>
</template>

// 定义当前组件插槽类型,并返回使用该组件传入的插槽对象
const slots = defineSlots<{
  header(props: { msg: string }): any
  default(props: { sex: string }): any
  footer(props: { age: number }): any
}>()

image.png

useAttrs()

获取当前组件非props的属性集合。这些属性在<script setup>中获取基本没啥用。一般都是在模板中直接进行绑定。这样我们直接使用$attrs即可。

    <div v-bind="$attrs">useAttrs</div>

image.png

useModel(),defineModel() 3.4

二者都是和值的双向绑定有关。以前如果我们实现组件props双向绑定我们在更新props值时向外发送事件进行更新来达到props值的双向绑定。其实这两个参数也就是我们以前方法的语法糖而已。

useModel是更底层的api,defineModeluseModel更具体的封装。

const props = defineProps<{
  name: string
}>()
// useModel就比较简单了,参数一组件的props,参数二绑定到v-model的键值,参数三get,set方法对象
const model2 = useModel(props, 'name', {
  get(value) {
    console.log('value======useModel', value)
    return value
  },
  set(value) {
    console.log('value======useModel', value)
    return value + '0000' // 如果定义必须有返回值
  }
})

defineModel提供了很多函数重载方法及各个参数类型介绍。

image.png

image.png

image.png

image.png

  • 参数一:model绑定的变量名。如果不传默认是modelValue
  • 参数二:配置对象
    • type 当前绑定值的类型
    • default 当前绑定值的默认值
    • required 是否必传的v-model,如果父组件在使用子组件时不进行定义那么将报错,并且约束了该v-model绑定的变量不能为undefinedimage.png
    • validator 验证函数。这个函数可以拿到当前绑定的变量最新值和v-model的修饰符,所以可以在这个函数中做数据校验,至于返回的boolean并没有实际用处?
    • get,set 如果我们想要通过修饰符来处理变量的值,我们就可以在get,set方法中统一处理。注意:如果设置了set方法,我们必须设置返回值,不然外部更新绑定的变量将不会有任何结果响应。
    <script setup>
     const [model, modifiers] = defineModel({
       set(value) {
         if (modifiers.capitalize) {
           return value.charAt(0).toUpperCase() + value.slice(1)
         }
         return value
       }
     })
     </script>
    
     <template>
       <input type="text" v-model="model" />
     </template>
    

我们定义的修饰符泛型类型并不能在我们使用v-model指定修饰符时进行提示(只能提示内置的修饰符),而是在获取修饰符时进行提示。

image.png

image.png

这里有介绍defineModel的怪异现象

useTemplateRef() 3.5

这个就是以前ref引用ref变量获取dom元素的替代品。绑定关系更容易让人理解。

<script setup>
import { useTemplateRef, onMounted } from 'vue'

const inputRef = useTemplateRef('input')

onMounted(() => {
  inputRef.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

即使我们不指定绑定的组件类型,他也可以很好的推断出来组件实例暴露了那些数据。

// <InstanceType<typeof RefTemplate>>
const refTemplateRef = useTemplateRef('customComponent')

onMounted(() => {
  console.log('====================refTemplateRef', refTemplateRef.value)
})

image.png

useId() 3.5

生成当前程序的唯一ID。

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

const id = useId()
</script>

<template>
  <form>
    <label :for="id">Name:</label>
    <input :id="id" type="text" />
  </form>
</template>

hasInjectionContext() 3.3

查看当前组件使用注入数据的地方是否允许被使用,如果可以则返回true,这样方便我们做出做一些适配。

例如在非setup中使用就会返回false。并不是我认为的在非provide组件下的子组件中调用hasInjectionContext就会返回false。这个是vue组件自动会判断,并且会在控制台抛出警告。

image.png

onWatcherCleanup() 3.5

watchwatchEffect函数中可以使用的回调函数,用于在依赖变化导致副作用函数重新执行前执行一些清理操作。注意:不能在await之后调用该钩子,即不能用在具有async回调的watch, watchEffect中。

这个钩子的作用同watch, watchEffect回调中的onCleanup函数。

watch, watchEffect中的once 3.4

监听回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。

watch, watchEffect监听的回调触发时机

  • 'sync':同步执行回调。这意味着当触发依赖变化时,立即执行回调函数,而不是等待下一个事件循环周期。
  • 'pre':在组件更新之前触发回调。这个阶段会在 Vue 开始计算新的状态和 DOM 变化之前执行。
  • 'post':在组件更新之后触发回调。这个阶段在 Vue 完成了所有的状态更新和 DOM 渲染之后执行。

尽管监听回调在不同时机执行,但是他们都可以拿到更新前后的值。

image.png

如果都设置immediate: true,那么他们都优先于onMounted钩子执行。

image.png

name 3.2.34

在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,即使是在配合 <KeepAlive> 使用时也无需再手动声明。

v-memo 3.2

当前元素或者组件是否可以根据v-memo中指定的值跳过更新。即类似于v-once,但是依赖的值发生变化,元素和组件还是会响应式的更新。v-memo 传入空依赖数组 (v-memo="[]") 将与 v-once 效果相同。

当搭配 v-for 使用 v-memo,确保两者都绑定在同一个元素上。v-memo 不能用在 v-for 内部。

这里有一个demo

  • MemoTest2组件依赖于非响应式数据age,当改变age时发现MemoTest2组件并不会更新数据,并且如果只改变num由于memo以来于age也不会更新组件,所以当age发生变化后在进行num改变才会更新MemoTest2组件(这是由于更新响应式数据,vue会更新组件树中的所有组件,而v-memo有依赖了age所以只有当age更新后再更新响应式数据num,memotest2组件才会更新)。由此可见v-memo依赖的数据只有为响应式数据才会立刻有效果。
  • MemoTest1中的p标签依赖于num,name所以更新任何一个数据都将被更新。

所以说v-memo依赖项尽量设置为响应式数据。

// memotest1
<template>
  <!-- age发生变化,在num响应式数据发生变化后,需要更新子组件状态,所以也会导致 MemoTest2组件更新 -->
  <MemoTest2 v-memo="[age]" :age="age" :num="num" />
  <h1>memotest1</h1>
  <p v-memo="[num, name]">响应式数据name:{{ name }}</p>
  <p>响应式数据num:{{ num }}</p>

  <button @click="handleData('num')">修改num</button>
  <button @click="handleData('name')">修改name</button>
  <button @click="handleData('age')">修改age</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import MemoTest2 from './MemoTest2.vue'

const num = ref(1)
const name = ref('zh')
let age = 0
const handleData = (key: string) => {
  if (key === 'num') {
    num.value = num.value + 1
  } else if (key === 'name') {
    name.value = name.value === 'zh' ? 'llm' : 'zh'
  } else {
    age = age + 1
    console.log('更新age', age)
  }
}
</script>
// memotest2
<template>
  <h1>memotest2</h1>
  <p>响应式数据num:{{ num }}</p>
  <p>非响应式数据:{{ age }}</p>
</template>

<script setup lang="ts">
import { onUpdated } from 'vue'

const { age, num } = defineProps<{
  age: number
  num?: number
}>()
onUpdated(() => {
  console.log('更新数据。。。。')
})
console.log('num', num)
</script>

memo.gif

ComponentPublicInstance

我们通过useTemplateRef, ref引用元素或者组件实例时,如果不知道具体的类型,我们可以通过ComponentPublicInstance类型代替。这只会包含所有组件都共享的属性,比如 $el

is 3.1

这个主要是用于和component组件绑定动态组件的。当使用在原生 HTML 元素上时,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件。 这个也是为了解决有些元素强制子元素为某些元素的限制。

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

src 导入

如果你更喜欢将 *.vue 组件分散到多个文件中,可以为一个语块使用 src 这个 attribute 来导入一个外部文件:

<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>

<script setup>

  • 每个.vue文件只能有一个<script setup>和一个<script>
  • 由于里面的代码会被编译成组件 setup() 函数的内容,所以<script setup> 中的代码会在每次组件实例被创建的时候执行。普通的 <script> 代码(不是setup函数中的)只在组件被首次引入的时候执行一次。
  • 局部自定义指令不需要显式注册,但他们必须遵循 vNameOfDirective 这样的命名规范。
  • Vue3.3 支持在类型参数的位置引用导入的和有限的复杂类型。然而,由于类型到运行时的转换仍然基于 AST,因此并不支持使用需要实际类型分析的复杂类型,例如条件类型等。你可以在单个 prop 的类型上使用条件类型,但不能对整个 props 对象使用。

自定义元素相关的api下篇文章继续分享,如果还不了解自定义元素可以查看这里 《了解Web Components,在现代框架中使用它》

往期年度总结

往期文章

专栏文章

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏✍️评论,    支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!