vue3 语法拾遗
[TOC]
还没有完整的看过 官方文档,决定速读一下。如果碰到不知道的知识点在这里记录下。
模板语法相关
v-bind
完整写法
<div v-bind:id="dynamicId"></div>
简写
<div :id="dynamicId"></div>
如果key和value一致可以简写如下(v3.4+)
<!-- same as :id="id" -->
<div :id></div>
<!-- this also works -->
<div v-bind:id></div>
一次绑定多个值
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
<div v-bind="objectOfAttrs"></div>
使用解构
<li v-for="{ message } in items">
</li>
<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">
</li>
动态参数
<a v-bind:[attributeName]="url"> ... </a>
<a :[attributeName]="url"> ... </a>
这里的 attributeName
会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName
,其值为 “href”,那么这个绑定就等价于 v-bind:href
同理,也可以绑定到事件上:
<a v-on:[eventName]="doSomething"> ... </a>
<a @[eventName]="doSomething">
在此示例中,当 eventName
的值是 “focus” 时,v-on:[eventName]
就等价于 v-on:focus
修饰符 Modifiers
修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent
修饰符会告知 v-on
指令对触发的事件调用 event.preventDefault()
:
<form @submit.prevent="onSubmit">...</form>
DOM 更新时机
当你修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// 现在 DOM 已经更新了
}
可写的计算属性
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
类与样式绑定
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
也可以是一个对象
const classObject = reactive({
active: true,
'text-danger': false
})
<div :class="classObject"></div>
也可以是一个计算属性
const isActive = ref(true)
const error = ref(null)
const classObject = computed(() => ({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal'
}))
<div :class="classObject"></div>
在组件中使用时
对于只有一个根元素的组件,当你使用了 class attribute 时,这些 class 会被添加到根元素上并与该元素上已有的 class 合并。
譬如:
<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>
<!-- 在使用组件时 -->
<MyComponent class="baz boo" />
最终会被渲染成
<p class="foo bar baz boo">Hi!</p>
内联样式
const styleObject = reactive({
color: 'red',
fontSize: '13px'
})
<div :style="styleObject"></div>
$event
在内联事件处理器中访问原生 DOM 事件。
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
事件修饰符
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
按键修饰符
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
<!--仅会在 $event.key 为 'PageDown' 时调用事件处理。-->
<input @keyup.page-down="onPageDown" />
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
鼠标修饰键
- .left
- .right
- .middle
值绑定
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no" />
true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 ‘yes’,取消选择时设为 ‘no’。你同样可以通过 v-bind 将其绑定为其他动态值:
<input
type="checkbox"
v-model="toggle"
:true-value="dynamicTrueValue"
:false-value="dynamicFalseValue" />
值限制
如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入:
<input v-model.number="age" />
自动去除用户输入内容中两端的空格
<input v-model.trim="msg" />
模板引用
v-for 中的模板引用
当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素:
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
/* ... */
])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
</li>
</ul>
</template>
函数模板引用
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">
组件
子组件通知父组件
父组件
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
子组件 @click="$emit('enlarge-text')"
直接抛出一个事件
<!-- BlogPost.vue, 省略了 <script> -->
<template>
<div class="blog-post">
<h4></h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
动态组件
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>
TS 中定义 $emit
<script setup lang="ts">
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
$emit 事件校验
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>
v-model
从 Vue 3.4 开始,推荐的实现方式是使用 defineModel()
宏:
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>parent bound v-model is: </div>
</template>
<!-- Parent.vue -->
<Child v-model="count" />
defineModel() 返回的值是一个 ref。 它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:
- 它的 .value 和父组件的 v-model 的值同步;
- 当它被子组件变更了,会触发父组件绑定的值一起更新。
底层机制
defineModel 是一个便利宏。 编译器将其展开为以下内容:
- 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
- 一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。
3.4 版本之前是这样实现
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
// 使 v-model 必填
const model = defineModel({ required: true })
// 提供一个默认值
const model = defineModel({ default: 0 })
v-model 参数
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
const title = ref('11111')
</script>
<template>
<h1></h1>
<MyComponent v-model:name="title" />
</template>
<!--MyComponent-->
<script setup>
const model = defineModel('name')
</script>
<template>
<input type="text" v-model="model" />
</template>
多个model
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
<!-- UserName.vue -->
<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>
带修饰符的model可以根据修饰符自定义操作
<MyComponent v-model.capitalize="myText" />
<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>
透传 Attributes
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。
<!-- <MyButton> 的模板 -->
<button>click me</button>
<!--父组件调用-->
<MyButton class="large" />
最终渲染
<button class="large">click me</button>
禁用 Attributes 继承
从 3.3
开始你也可以直接在 <script setup>
中使用 defineOptions:
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
在模板中使用 $attrs 访问属性
<span>Fallthrough attribute: </span>
slot 插槽
理解类似于
// 传入不同的内容给不同名字的插槽
BaseLayout({
header: `...`,
default: `...`,
footer: `...`
})
// <BaseLayout> 渲染插槽内容到对应位置
function BaseLayout(slots) {
return `<div class="container">
<header>${slots.header}</header>
<main>${slots.default}</main>
<footer>${slots.footer}</footer>
</div>`
}
```js vue
##### 作用域插槽
```js
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
</MyComponent>
自定义指令
简化形式
<div v-color="color"></div>
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
插件
在插件里面处理一系列相关的逻辑,注册一组相关的组件、指令 提供全局的方法、数据
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
/* 可选的选项 */
})
const myPlugin = {
install(app, options) {
// 配置此应用
}
}
挂载全局方法
// 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)
}
}
}
提供全局参数 Provide / Inject
// plugins/i18n.js
export default {
install: (app, options) => {
app.provide('i18n', options)
}
}
使用
<script setup>
import { inject } from 'vue'
const i18n = inject('i18n')
console.log(i18n.greetings.hello)
</script>
应用规模化
性能优化
页面加载优化
TS
defineProps
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
withDefaults
默认值
export interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
为组件的 emits 标注类型
<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])
// 基于选项
const emit = defineEmits({
change: (id: number) => {
// 返回 `true` 或 `false`
// 表明验证通过或失败
},
update: (value: string) => {
// 返回 `true` 或 `false`
// 表明验证通过或失败
}
})
// 基于类型
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>
为模板引用标注类型
<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>
组件模板引用标注类型
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
defineExpose({
open
})
</script>
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
modal.value?.open()
}
</script>
const modal = ref<InstanceType<typeof MyModal> | null>(null)
通用的模板类型
import { ref } from 'vue'
import type { ComponentPublicInstance } from 'vue'
const child = ref<ComponentPublicInstance | null>(null)
渲染函数 & JSX
基本使用
Vue 提供了一个 h() 函数用于创建 vnodes:
// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })
// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })
// 像 `.prop` 和 `.attr` 这样的的属性修饰符
// 可以分别通过 `.` 和 `^` 前缀来添加
h('div', { '.name': 'some-name', '^width': '100' })
// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })
// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')
// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])
// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])
声明渲染函数
jsx
vite vue3 使用 tsx vite 需要安装插件
pnpm i @vitejs/plugin-vue-jsx
在vite.config.js加入jsx配置
// vite.config.js
import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx()],
})