Vue速查手册
Vue3指令/组合式API/组件通信速查
🟢 模板与 SFC
Vue 3 推荐 <script setup>,顶层声明可直接在模板使用。
<script setup>
import { ref } from 'vue'
const msg = ref('Hello Vue 3')
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<button @click="count++">+1 {{ count }}</button>
</template>
<style scoped>
h1 { color: #42b883; }
</style>
v-bind(:)绑属性,v-on(@)绑事件,{{ }} 插文本。
<template>
<p>{{ name }} - {{ age + 1 }}</p>
<a v-bind:href="url">完整写法</a>
<a :href="url">缩写写法</a>
<button v-on:click="say">完整写法</button>
<button @click="say">缩写写法</button>
<button @click="count++">内联语句 {{ count }}</button>
</template>
v-html 渲染原始 HTML,注意 XSS;$attrs 接收未声明透传属性。
<div>{{ raw }}</div>
<!-- 原样输出 <b>hi</b> -->
<div v-html="raw"></div>
<!-- 渲染为加粗 hi(内容必须可信) -->
<button :disabled="isLoading" :id="`btn-${type}`">提交</button>
<input :readonly="readonly" :required="true">
支持对象语法与数组语法,可与静态 class 共存。
<div class="base" :class="{ active: isActive, error: hasErr }"></div>
<div :class="[isActive ? 'active' : '', 'size-' + size]"></div>
<div :class="[{ active: isActive }, cls]"></div>
<!-- 组件上使用会合并到根元素 -->
<MyCard class="rounded" />
对象键名驼峰或短横线均可;浏览器会自动加前缀。
<div :style="{ color: textColor, fontSize: size + 'px' }"></div>
<div :style="{ '--theme': themeColor }">CSS 变量</div>
<div :style="[baseStyle, extraStyle]">数组合并</div>
🔀 条件与列表
真正的增删 DOM;可配合 <template> 分组多元素。
<p v-if="type === 'A'">甲</p>
<p v-else-if="type === 'B'">乙</p>
<p v-else>其他</p>
<template v-if="isLogin">
<h2>欢迎回来</h2>
<p>退出入口</p>
</template>
v-show 只切 CSS display,频繁切换用它;v-if 有更高初始切换成本。
<div v-show="visible">始终在 DOM 中,仅 display:none</div>
<!-- 频繁显示隐藏:v-show -->
<Modal v-show="showModal" />
<!-- 很少变化、希望惰性渲染:v-if -->
<HeavyChart v-if="tab === 'chart'" />
始终绑定唯一 key;可遍历数组、对象、数字、字符串。
<li v-for="(item, index) in list" :key="item.id">
{{ index }} - {{ item.name }}
</li>
<li v-for="value in 5" :key="value">{{ value }}</li>
<tr v-for="(val, key) in user" :key="key">
{{ key }}: {{ val }}
</tr>
<!-- v-if 与 v-for 不要同时放同一元素,用 <template> 包裹 -->
<template v-for="u in users" :key="u.id">
<UserRow v-if="u.active" :user="u" />
</template>
📊 响应式 API
JS 中读写要 .value,模板里自动解包无需 .value。
<script setup>
import { ref } from 'vue'
const count = ref(0)
const user = ref({ name: 'Tom' })
function inc() {
count.value++
user.value.name = 'Amy'
}
</script>
<template>
<button @click="inc">{{ count }} {{ user.name }}</button>
</template>
Proxy 代理对象,直接改属性即可;不能整体替换丢失响应式。
import { reactive } from 'vue'
const state = reactive({
list: [],
loading: false,
filter: { kw: '' }
})
state.loading = true
state.list.push({ id: 1 })
state.filter.kw = 'vue'
// ❌ 不要整体替换(会断开与原代理的联系)
// state = { list: [], loading: false }
// ✅ 逐属性替换
Object.assign(state, { list: [], loading: false })
reactive 直接解构会失去响应式,要用 toRefs 包一层。
import { reactive, toRefs, toRef } from 'vue'
const state = reactive({ name: 'Tom', age: 18 })
const { name, age } = toRefs(state)
name.value = 'Amy' // state.name 同步变化
const name2 = toRef(state, 'name')
function useUser() {
const s = reactive({ loading: false, data: null })
return { ...toRefs(s) } // return 时保持响应式
}
🧮 computed 与 watch
有缓存,依赖不变不重算;可写计算属性需 get/set。
import { ref, computed } from 'vue'
const items = ref([1, 2, 3, 4])
const keyword = ref('')
const evens = computed(() => items.value.filter(x => x % 2 === 0))
const total = computed(() => items.value.reduce((a, b) => a + b, 0))
const fullName = computed({
get: () => firstName.value + lastName.value,
set: (v) => { [firstName.value, lastName.value] = v.split(' ') }
})
显式指定数据源;immediate 立即执行,deep 深度侦听。
const id = ref(1)
watch(id, (newVal, oldVal) => {
console.log('变化', oldVal, '->', newVal)
fetchUser(newVal)
}, { immediate: true })
watch([id, keyword], ([newId, newKw]) => {
console.log(newId, newKw)
})
watch(() => state.user.id, (n) => {})
watch(state, (n) => {}, { deep: true })
立即执行一次,内部用到谁变了就重跑;返回停止函数。
import { watchEffect } from 'vue'
const stop = watchEffect(async (onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort())
const data = await fetchUser(id.value, controller.signal)
user.value = data
})
// 手动停止
stop()
// watch 是懒执行,watchEffect 立即执行;
// watch 能拿到新旧值,watchEffect 不能
📦 组件通信
运行时声明可指定类型、默认值、校验;对象默认值必须用工厂函数。
<script setup>
const props = defineProps({
title: String,
list: {
type: Array,
required: true,
default: () => []
},
size: { type: String, default: 'medium', validator: v => ['small','medium'].includes(v) }
})
</script>
<template>
<h2>{{ title }}</h2>
<li v-for="i in list" :key="i">{{ i }}</li>
</template>
在 <script setup lang="ts"> 中直接用泛型描述 props/emits。
<script setup lang="ts">
interface User { id: number; name: string }
const props = withDefaults(
defineProps<{
title?: string
users: User[]
}>(),
{ title: '默认标题' }
)
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'delete', id: number): void
}>()
</script>
子组件不直接改 props,通过事件通知父组件。
<script setup>
const emit = defineEmits(['confirm', 'cancel'])
function ok() {
emit('confirm', { id: 1, ts: Date.now() })
}
</script>
<template>
<button @click="ok">确定</button>
<button @click="emit('cancel')">取消</button>
</template>
<!-- 父组件 -->
<Dialog @confirm="onConfirm" @cancel="onCancel" />
Vue 3.4+ 用 defineModel 实现双向绑定最简洁;老写法是 modelValue/update。
<!-- 子组件 MyInput.vue -->
<script setup>
const model = defineModel()
</script>
<template>
<input :value="model" @input="model = $event.target.value" />
</template>
<!-- 父组件 -->
<MyInput v-model="keyword" />
<!-- 具名 v-model -->
const first = defineModel('first')
<MyInput v-model:first="fn" v-model:last="ln" />
祖先 provide,任意深层后代 inject;推荐传响应式对象并声明默认值。
// 祖先
import { provide, ref, readonly } from 'vue'
const theme = ref('dark')
provide('theme', readonly(theme))
provide('setTheme', (v) => { theme.value = v })
// 后代
import { inject } from 'vue'
const theme = inject('theme', 'light')
const setTheme = inject('setTheme', () => {})
⏳ 生命周期
setup 中用 onXxx 注册;没有 beforeCreate/created(setup 即其时点)。
import {
onBeforeMount, onMounted,
onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted
} from 'vue'
onBeforeMount(() => console.log('挂载前'))
onMounted(() => console.log('已挂载,可访问 DOM'))
onBeforeUpdate(() => console.log('更新前'))
onUpdated(() => console.log('DOM 更新后'))
onBeforeUnmount(() => console.log('卸载前'))
onUnmounted(() => console.log('已卸载,清定时器'))
在 onMounted 启动、onUnmounted 清理,防止内存泄漏。
<script setup>
import { onMounted, onUnmounted } from 'vue'
let timer = null
function onResize() { console.log(window.innerWidth) }
onMounted(() => {
timer = setInterval(() => console.log('tick'), 1000)
window.addEventListener('resize', onResize)
})
onUnmounted(() => {
clearInterval(timer)
window.removeEventListener('resize', onResize)
})
</script>
🪝 组合式函数与模板 ref
把响应式状态与逻辑封装到 use 开头的函数,跨组件复用。
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
const move = (e) => { x.value = e.clientX; y.value = e.clientY }
onMounted(() => window.addEventListener('mousemove', move))
onUnmounted(() => window.removeEventListener('mousemove', move))
return { x, y }
}
// 组件中
const { x, y } = useMouse()
同名 ref 挂载 DOM;<script setup> 默认封闭,需 expose 才能被父组件调用。
<!-- 子组件 -->
<script setup>
import { ref } from 'vue'
const inputRef = ref(null)
function focus() { inputRef.value.focus() }
defineExpose({ focus })
</script>
<template><input ref="inputRef" /></template>
<!-- 父组件 -->
<script setup>
const childRef = ref(null)
const onBtn = () => childRef.value.focus()
</script>
<template>
<MyInput ref="childRef" />
<button @click="onBtn">让子组件聚焦</button>
</template>
✨ 常用进阶
链式书写,减少手写 e.preventDefault 等样板代码。
<a @click.stop="onClick">阻止冒泡</a>
<form @submit.prevent="onSubmit">阻止默认提交</form>
<button @click.once="init">只触发一次</button>
<div @click.self="onMask">仅点自身才触发</div>
<input @keyup.enter="search" />
<input @keyup.esc="close" />
<input v-model.trim="name" />
<input v-model.number="age" />
<input v-model.lazy="msg" />
改完数据后 DOM 是异步刷新的,nextTick 后再测量才准确。
import { nextTick } from 'vue'
async function addAndScroll() {
list.value.push(newItem)
await nextTick() // 等待新 li 渲染完成
lastRef.value.scrollIntoView({ behavior: 'smooth' })
}
nextTick(() => {
console.log('DOM 已更新')
})
Teleport 把 DOM 渲染到 body 等容器(弹窗常用);component 动态切换。
<Teleport to="body" :disabled="false">
<div class="modal-mask">
<div class="modal">弹窗内容</div>
</div>
</Teleport>
<component :is="currentTab === 'a' ? TabA : TabB" />
<KeepAlive>
<component :is="comp" />
</KeepAlive>
😶 没有匹配的条目,换个关键词试试
📖 使用说明
全程在浏览器本地运行。
操作步骤:
- 搜索或浏览分类条目;
- 查看语法与示例代码;
- 点击复制代码块。
💬 用户评论 (0)
还没有评论,快来抢沙发!