分享Vue3.5最新变化

Vue3.5最新变化

useTemplateRef()

要在组合式 API 中获取引用,我们可以使用辅助函数 useTemplateRef()

html 复制代码
<script setup>
    import { useTemplateRef, onMounted } from 'vue'

    // 第一个参数必须与模板中的 ref 值匹配
    const input = useTemplateRef('my-input')

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

<template>
    <input ref="my-input" />
</template>
3.5 前的用法

在 3.5 之前的版本尚未引入 useTemplateRef(),我们需要声明一个与模板里 ref attribute 匹配的引用:

html 复制代码
<script setup>
import { ref, onMounted } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

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

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

为模板引用标注类型

在 Vue 3.5 和 @vue/language-tools 2.1 (为 IDE 语言服务和 vue-tsc 提供支持) 中,在 SFC 中由 useTemplateRef() 创建的 ref 类型可以基于匹配的 ref attribute 所在的元素自动推断为静态类型。

在无法自动推断的情况下,仍然可以通过泛型参数将模板 ref 转换为显式类型。

js 复制代码
const el = useTemplateRef<HTMLInputElement>(null)
3.5 前的用法
html 复制代码
<script setup lang="ts">
import { ref, onMounted } from 'vue'

const el = ref<HTMLInputElement | null>(null)

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

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

defineModel()

v-model 可以在组件上使用以实现双向绑定。

从 Vue 3.4 开始,推荐的实现方式是使用 defineModel()

html 复制代码
<!-- Child.vue -->
<script setup>
    const model = defineModel()

    function update() {
        model.value++
    }
</script>

<template>
    <div>Parent bound v-model is: {{ model }}</div>
    <button @click="update">Increment</button>
</template>

父组件可以用 v-model 绑定一个值:

html 复制代码
<!-- Parent.vue -->
<Child v-model="countModel" />

defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:

  • 它的 <font style="color:#DF2A3F;">.value</font> 和父组件的 <font style="color:#DF2A3F;">v-model</font> 的值同步;
  • 当它被子组件变更了,会触发父组件绑定的值一起更新。

这意味着你也可以用 v-model 把这个 ref 绑定到一个原生 input 元素上,在提供相同的 v-model 用法的同时轻松包装原生 input 元素:

html 复制代码
<script setup>
  const model = defineModel()
</script>

<template>
  <input v-model="model" />
</template>

底层机制

defineModel 是一个便利宏。编译器将其展开为以下内容:

  • 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
  • 一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。
在 3.4 版本之前,你一般会按照如下的方式来实现上述相同的子组件:
html 复制代码
<!-- Child.vue -->
<script setup>
  const props = defineProps(['modelValue'])
  const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="emit('update:modelValue', $event.target.value)"
    />
</template>

然后,父组件中的 v-model="foo" 将被编译为:

html 复制代码
<!-- Parent.vue -->
<Child
  :modelValue="foo"
  @update:modelValue="$event => (foo = $event)"
  />

如你所见,这显得冗长得多。然而,这样写有助于理解其底层机制。

因为 defineModel 声明了一个 prop,你可以通过给 defineModel 传递选项,来声明底层 prop 的选项:

javascript 复制代码
// 使 v-model 必填
const model = defineModel({ required: true })

// 提供一个默认值
const model = defineModel({ default: 0 })
注意

如果为 defineModel prop 设置了一个 default 值且父组件没有为该 prop 提供任何值,会导致父组件与子组件之间不同步。在下面的示例中,父组件的 myRef 是 undefined,而子组件的 model 是 1:

javascript 复制代码
// 子组件:
const model = defineModel({ default: 1 })

// 父组件
const myRef = ref()
html 复制代码
<Child v-model="myRef"></Child>

v-model 的参数

组件上的 v-model 也可以接受一个参数:

html 复制代码
<MyComponent v-model:title="bookTitle" />

在子组件中,我们可以通过将字符串作为第一个参数传递给 defineModel() 来支持相应的参数:

html 复制代码
<!-- MyComponent.vue -->
<script setup>
  const title = defineModel('title')
</script>

<template>
  <input type="text" v-model="title" />
</template>

如果需要额外的 prop 选项,应该在 model 名称之后传递:

javascript 复制代码
const title = defineModel('title', { required: true })
3.4 之前的用法
html 复制代码
<!-- MyComponent.vue -->
<script setup>
    defineProps({
        title: {
            required: true
        }
    })
    defineEmits(['update:title'])
</script>

<template>
    <input
        type="text"
        :value="title"
        @input="$emit('update:title', $event.target.value)"
        />
</template>

多个 v-model 绑定

利用刚才在 v-model的参数小节中学到的指定参数与事件名的技巧,我们可以在单个组件实例上创建多个 v-model 双向绑定。

组件上的每一个 v-model 都会同步不同的 prop,而无需额外的选项:

html 复制代码
<UserName
    v-model:first-name="first"
    v-model:last-name="last"
    />
html 复制代码
<script setup>
    const firstName = defineModel('firstName')
    const lastName = defineModel('lastName')
</script>

<template>
    <input type="text" v-model="firstName" />
    <input type="text" v-model="lastName" />
</template>
3.4 之前的用法
html 复制代码
<script setup>
    defineProps({
        firstName: String,
        lastName: String
    })

    defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
    <input
        type="text"
        :value="firstName"
        @input="$emit('update:firstName', $event.target.value)"
        />
    <input
        type="text"
        :value="lastName"
        @input="$emit('update:lastName', $event.target.value)"
        />
</template>

处理 v-model 修饰符

在学习输入绑定时,我们知道了 v-model 有一些内置的修饰符,例如 .trim.number.lazy。在某些场景下,你可能想要一个自定义组件的 v-model 支持自定义的修饰符。

我们来创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:

html 复制代码
<MyComponent v-model.capitalize="myText" />

通过像这样解构 defineModel() 的返回值,可以在子组件中访问添加到组件 v-model 的修饰符:

html 复制代码
<script setup>
    const [model, modifiers] = defineModel()

    console.log(modifiers) // { capitalize: true }
</script>

<template>
    <input type="text" v-model="model" />
</template>

为了能够基于修饰符选择性地调节值的读取和写入方式,我们可以给 defineModel() 传入 getset 这两个选项。这两个选项在从模型引用中读取或设置值时会接收到当前的值,并且它们都应该返回一个经过处理的新值。下面是一个例子,展示了如何利用 set 选项来应用 capitalize (首字母大写) 修饰符:

html 复制代码
<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>
3.4 之前的用法
html 复制代码
<script setup>
    const props = defineProps({
        modelValue: String,
        modelModifiers: { default: () => ({}) }
    })

    const emit = defineEmits(['update:modelValue'])

    function emitValue(e) {
        let value = e.target.value
        if (props.modelModifiers.capitalize) {
            value = value.charAt(0).toUpperCase() + value.slice(1)
        }
        emit('update:modelValue', value)
    }
</script>

<template>
    <input type="text" :value="modelValue" @input="emitValue" />
</template>

带参数的 v-model 修饰符

这里是另一个例子,展示了如何在使用多个不同参数的 v-model 时使用修饰符:

html 复制代码
<UserName
    v-model:first-name.capitalize="first"
    v-model:last-name.uppercase="last"
    />
html 复制代码
<script setup>
    const [firstName, firstNameModifiers] = defineModel('firstName')
    const [lastName, lastNameModifiers] = defineModel('lastName')

    console.log(firstNameModifiers) // { capitalize: true }
    console.log(lastNameModifiers) // { uppercase: true }
</script>
3.4 之前的用法
html 复制代码
<script setup>
    const props = defineProps({
        firstName: String,
        lastName: String,
        firstNameModifiers: { default: () => ({}) },
        lastNameModifiers: { default: () => ({}) }
    })
    defineEmits(['update:firstName', 'update:lastName'])

    console.log(props.firstNameModifiers) // { capitalize: true }
    console.log(props.lastNameModifiers) // { uppercase: true }
</script>

defineProps()

最大亮点就是响应式 Props 解构

Vue 的响应系统基于属性访问跟踪状态的使用情况。例如,在计算属性或侦听器中访问 props.foo 时,foo 属性将被跟踪为依赖项。

因此,在以下代码的情况下:

javascript 复制代码
const { foo } = defineProps(['foo'])

watchEffect(() => {
  // 在 3.5 之前只运行一次
  // 在 3.5+ 中在 "foo" prop 变化时重新执行
  console.log(foo)
})

在 3.4 及以下版本,foo 是一个实际的常量,永远不会改变。在 3.5 及以上版本,当在同一个 <script setup> 代码块中访问由 defineProps 解构的变量时,Vue 编译器会自动在前面添加 props.。因此,上面的代码等同于以下代码:

java 复制代码
const props = defineProps(['foo'])

watchEffect(() => {
    // `foo` 由编译器转换为 `props.foo`
    console.log(props.foo)
})

此外,你可以使用 JavaScript 原生的默认值语法声明 props 默认值。这在使用基于类型的 props 声明时特别有用。

html 复制代码
const { foo = 'hello' } = defineProps<{ foo?: string }>()

如果你希望在 IDE 中在解构的 props 和普通变量之间有更多视觉上的区分,Vue 的 VSCode 扩展提供了一个设置来启用解构 props 的内联提示。

将解构的 props 传递到函数中

当我们将解构的 prop 传递到函数中时,例如:

js 复制代码
const { foo } = defineProps(['foo'])

watch(foo, /* ... */)

这并不会按预期工作,因为它等价于 watch(props.foo, ...)------我们给 watch 传递的是一个值而不是响应式数据源。实际上,Vue 的编译器会捕捉这种情况并发出警告。

与我们可以使用 watch(() => props.foo, ...) 侦听普通 prop 类似,我们也可以通过将其包装在 getter 中来侦听解构的 prop:

js 复制代码
watch(() => foo, /* ... */)

此外,当我们需要传递解构的 prop 到外部函数中并保持响应性时,这是推荐做法:

js 复制代码
useComposable(() => foo)

外部函数可以调用 getter (或使用 toValue 进行规范化) 来追踪提供的 prop 变更。例如,在计算属性或侦听器的 getter 中。

使用一个对象绑定多个 prop

如果你想要将一个对象的所有属性都当作 props 传入,你可以使用没有参数的v-bind,即只使用 v-bind 而非 :prop-name。例如,这里有一个 post 对象:

javascript 复制代码
const post = {
  id: 1,
  title: 'My Journey with Vue'
}

以及下面的模板:

html 复制代码
<BlogPost v-bind="post" />

而这实际上等价于:

html 复制代码
<BlogPost :id="post.id" :title="post.title" />

Teleport延迟解析

在 Vue 3.5 及更高版本中,我们可以使用 defer prop 推迟 Teleport 的目标解析,直到应用的其他部分挂载。这允许 Teleport 将由 Vue 渲染且位于组件树之后部分的容器元素作为目标:

html 复制代码
<Teleport defer to="#late-div">...</Teleport>

<!-- 稍后出现于模板中的某处 -->
<div id="late-div"></div>

请注意,目标元素必须与 Teleport 在同一个挂载/更新周期内渲染,即如果 <div> 在一秒后才挂载,Teleport 仍然会报错。延迟 Teleport 的原理与 mounted 生命周期钩子类似。

useId()

用于为无障碍属性或表单元素生成每个应用内唯一的 ID。

类型

js 复制代码
function useId(): string

示例

html 复制代码
<script setup>
    import { useId } from 'vue'

    const id = useId();
    
    console.log(id);		// 输出v-0
    console.log(useId());	// 输出v-1
    console.log(useId());	// 输出v-2
    console.log(useId());	// 输出v-3
</script>

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

详细信息

useId() 生成的每个 ID 在每个应用内都是唯一的。它可以用于为表单元素和无障碍属性生成 ID。在同一个组件中多次调用会生成不同的 ID;同一个组件的多个实例调用 useId() 也会生成不同的 ID。

useId() 生成的 ID 在服务器端和客户端渲染之间是稳定的,因此可以安全地在 SSR 应用中使用,不会导致激活不匹配。

如果同一页面上有多个 Vue 应用实例,可以通过 app.config.idPrefix 为每个应用提供一个 ID 前缀,以避免 ID 冲突。

了解更多,传送门→ https://cn.vuejs.org/guide/quick-start.html

相关推荐
辻戋37 分钟前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保39 分钟前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun2 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp2 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.3 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl5 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫6 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友6 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理8 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻8 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js