Vue 是什么 & 版本差异

  • Vue 是一个渐进式前端框架,专注于“视图层 + 组件化 UI”。

  • 常见版本:

    • Vue 2:Options API(data / methods / computed / watch)+ new Vue()

    • Vue 3:推荐使用 Composition APIsetup()/ref/reactive)+ createApp,性能更好。

建议:新项目全部用 Vue 3 + Composition API + <script setup>


1. “最小可运行”示例

1.1 直接 CDN 引入(练习/小 Demo)

<div id="app">
  <h1>{{ title }}</h1>
  <button @click="count++">Clicked {{ count }} times</button>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref } = Vue

createApp({
  setup() {
    const title = ref('Hello Vue 3')
    const count = ref(0)
    return { title, count }
  }
}).mount('#app')
</script>

关键点:

  • createApp({ setup() { ... } }).mount('#app')

  • setup 里定义响应式变量 ref,return 出去给模板用。

1.2 单文件组件(SFC) + <script setup>

典型 HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <button @click="count++">Clicked {{ count }} times</button>
  </div>
</template>

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

const props = defineProps({
  msg: { type: String, default: 'Hello Vue 3' }
})

const count = ref(0)
</script>

<style scoped>
.hello { padding: 16px; }
</style>

要点:

  • <script setup>:语法糖,自动帮你做 setup()export default

  • defineProps<script setup> 中直接用。

  • <style scoped>:样式只作用于当前组件。


2. 响应式系统:ref / reactive / computed / watch

2.1 ref:基本类型 + DOM 引用

import { ref } from 'vue'

const count = ref(0)
count.value++      // JS 内部用 .value
// 模板里用 {{ count }} 即可(会自动 .value)
  • 适合:数字、字符串、布尔、对象也可以。

2.2 reactive:对象/数组响应式

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: { name: 'Alice', age: 18 }
})

state.count++
state.user.name = 'Bob'
  • 适合:复杂对象状态。

  • 不要对 reactive 对象再 ref() 一次。

2.3 computed:计算属性(带缓存)

import { ref, computed } from 'vue'

const firstName = ref('Ada')
const lastName = ref('Lovelace')

const fullName = computed(() => `${firstName.value} ${lastName.value}`)
  • 当依赖的 firstName/lastName 变时才重新计算。

  • 模板里直接 {{ fullName }}

2.4 watch / watchEffect:监听变化

import { ref, watch, watchEffect } from 'vue'

const count = ref(0)

watch(count, (newVal, oldVal) => {
  console.log('count change:', oldVal, '->', newVal)
})

// 会自动收集依赖
watchEffect(() => {
  console.log('count now:', count.value)
})

常见用法:

  • watch:精确监听某个值,适合异步请求、防抖等。

  • watchEffect:自动依赖收集,适合简单打印/调试/副作用。


3. 模板语法 & 常用指令

3.1 插值 & 表达式

<p>{{ msg }}</p>
<p>{{ count + 1 }}</p>
<p>{{ user.name.toUpperCase() }}</p>

限制:

  • 只能写简单表达式,不要写 if/for/赋值。

3.2 常用指令(v- 开头)

3.2.1 条件渲染

<div v-if="isLogin">已登录</div>
<div v-else>未登录</div>

<div v-show="isOpen">这个只是 display: none 切换</div>

区别:

  • v-if:真的插入/销毁 DOM,有开销。

  • v-show:只切换 CSS display,频繁切换建议用 v-show

3.2.2 列表渲染 v-for

<li v-for="(item, index) in list" :key="item.id">
  {{ index }} - {{ item.name }}
</li>
  • 必须写 :key,通常用唯一 id。

  • 可以遍历对象:(value, key, index) in obj

3.2.3 事件绑定 v-on / @

<button @click="increment">+1</button>
<button @click="incrementBy(10)">+10</button>
<button @click="onClick($event)">with event</button>
const increment = () => { count.value++ }
const incrementBy = (n) => { count.value += n }
const onClick = (e) => { console.log(e.target) }

修饰符:

<form @submit.prevent="onSubmit">...</form>
<input @keyup.enter="search" />
  • .stop 阻止冒泡

  • .prevent 阻止默认

  • .enter.esc 等键盘修饰

3.2.4 属性绑定 v-bind / :

<img :src="imageUrl" :alt="title" />
<button :class="{ active: isActive }">Btn</button>
<div :style="{ color: activeColor, fontSize: size + 'px' }"></div>

v-bind 对象语法:

<div v-bind="someAttrs"></div>

4. 表单与 v-model

4.1 基本用法

<input v-model="username" placeholder="用户名" />
<textarea v-model="remark"></textarea>

<input type="checkbox" v-model="checked" />
<select v-model="selected">
  <option value="A">A</option>
  <option value="B">B</option>
</select>

4.2 修饰符

<input v-model.trim="name" />
<input v-model.number="age" />
<input v-model.lazy="keyword" />
  • .trim:自动去两端空格

  • .number:自动转数字

  • .lazy:改为 change 事件触发(默认是 input

4.3 组件上的 v-model(父 ↔ 子)

子组件 MyInput.vue

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

<script setup>
const props = defineProps({ modelValue: String })
const emit = defineEmits(['update:modelValue'])
</script>

父组件:

<MyInput v-model="username" />

5. 组件系统:Props / Emits / 插槽

5.1 Props:父传子

子组件:

<script setup>
const props = defineProps({
  title: { type: String, required: true },
  count: { type: Number, default: 0 }
})
</script>

<template>
  <h3>{{ title }}: {{ count }}</h3>
</template>

父组件:

<MyCard title="User" :count="users.length" />

5.2 Emits:子传父事件

<script setup>
const emit = defineEmits(['save', 'cancel'])

function onSave() {
  emit('save', formData)
}
</script>

<template>
  <button @click="onSave">Save</button>
  <button @click="$emit('cancel')">Cancel</button>
</template>

父组件:

<EditForm @save="handleSave" @cancel="closeDialog" />

5.3 插槽(slots)

父组件:

<Card>
  <template #header>
    <h3>标题</h3>
  </template>

  正文内容

  <template #footer>
    <button>确定</button>
  </template>
</Card>

子组件 Card.vue

<template>
  <div class="card">
    <header><slot name="header">默认头部</slot></header>
    <main><slot>默认正文</slot></main>
    <footer><slot name="footer" /></footer>
  </div>
</template>

6. 生命周期(Vue 3 + Composition API)

常用钩子(在 setup 内使用):

import { onMounted, onUnmounted, onUpdated } from 'vue'

onMounted(() => {
  console.log('组件挂载完成')
})

onUpdated(() => {
  console.log('组件更新')
})

onUnmounted(() => {
  console.log('组件销毁')
})

更多:

  • onBeforeMount

  • onBeforeUpdate

  • onBeforeUnmount

  • onErrorCaptured

  • onActivated / onDeactivated(配合 <keep-alive>


7. 路由:vue-router(只记最常用)

7.1 安装 & 创建 Router

npm install vue-router
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/user/:id', component: () => import('../views/User.vue') },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

export default router

7.2 在 main.js 挂载

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
  .use(router)
  .mount('#app')

7.3 在模板中使用

<!-- App.vue -->
<template>
  <nav>
    <RouterLink to="/">Home</RouterLink>
    <RouterLink to="/about">About</RouterLink>
  </nav>
  <RouterView />
</template>

在组件中获取路由信息:

import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

console.log(route.params.id)

function goHome() {
  router.push('/')
}

8. 全局状态管理:Pinia(推荐)

8.1 安装 & 挂载

npm install pinia
// main.js
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

8.2 定义一个 Store

// stores/useCounterStore.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

在组件里用:

<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/useCounterStore'

const counter = useCounterStore()
const { count, doubleCount } = storeToRefs(counter)

const inc = () => counter.increment()
</script>

<template>
  <p>count: {{ count }}</p>
  <p>double: {{ doubleCount }}</p>
  <button @click="inc">+1</button>
</template>

9. 与后端交互:axios 简单封装

npm install axios
// api/request.js
import axios from 'axios'

const service = axios.create({
  baseURL: '/api',      // 根据开发环境配置代理
  timeout: 10000,
})

// 简单拦截器(可加 token)
service.interceptors.response.use(
  (res) => res.data,
  (err) => Promise.reject(err)
)

export default service

使用:

// api/user.js
import request from './request'

export function getUser(id) {
  return request.get(`/users/${id}`)
}

export function login(data) {
  return request.post('/login', data)
}

组件内:

const user = ref(null)
onMounted(async () => {
  user.value = await getUser(123)
})

10. DOM 操作 & 模板 ref

有时要直接操作 DOM 或子组件实例:

<template>
  <input ref="inputEl" />
  <ChildComp ref="childRef" />
</template>

<script setup>
import { ref, onMounted } from 'vue'
import ChildComp from './ChildComp.vue'

const inputEl = ref(null)
const childRef = ref(null)

onMounted(() => {
  inputEl.value.focus()         // DOM 元素
  console.log(childRef.value)   // 子组件暴露的实例
})
</script>

在子组件中通过 defineExpose 暴露方法:

<script setup>
function doSomething() { ... }
defineExpose({ doSomething })
</script>

11. 组合式函数(Composables):复用逻辑

代替 Vue 2 的 mixins。

// composables/useCounter.js
import { ref } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)
  const inc = () => count.value++
  const dec = () => count.value--
  return { count, inc, dec }
}

组件中:

import { useCounter } from '@/composables/useCounter'

const { count, inc, dec } = useCounter(10)

12. 自定义指令(高级一点)

// 全局注册(main.js)
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

使用:

<input v-focus />

13. 常见项目结构(Vue 3 + Vite)

src/
  main.js           # 入口
  App.vue           # 根组件
  router/
    index.js        # 路由配置
  stores/           # Pinia store
  views/            # 页面级组件
  components/       # 通用组件
  composables/      # 组合式函数 (useXXX)
  api/              # axios 封装 & 接口
  assets/           # 静态资源

14. 快速对照表(Vue 2 → Vue 3)

功能

Vue 2 (Options API)

Vue 3 (Composition API)

根实例

new Vue({ el: '#app', data(){...} })

createApp(App).mount('#app')

data

data(){ return { count:0 } }

const count = ref(0)

methods

methods: { inc() { this.count++ } }

const inc = () => count.value++

computed

computed: { double() { ... } }

const double = computed(() => count.value * 2)

watch

watch: { count(n,o){...} }

watch(count, (n,o)=>{})

生命周期

mounted(){...}

onMounted(()=>{})

mixins

mixins: [...]

组合式函数 useXxx()

Vue 3 快速自查清单(核心知识一页通)

(Composition API + <script setup>


1. 项目最小模板

<template>
  <h1>{{ msg }}</h1>
  <button @click="count++">{{ count }}</button>
</template>

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

const msg = 'Hello Vue'
const count = ref(0)
</script>

2. 响应式(核心)

2.1 ref() —— 基础类型

const count = ref(0)
count.value++

模板里不需要 .value

<p>{{ count }}</p>

2.2 reactive() —— 对象/数组

const state = reactive({
  user: { name: 'Alice' },
  list: []
})
state.user.name = 'Bob'

2.3 computed() —— 计算属性

const double = computed(() => count.value * 2)

2.4 watch() —— 监听变化

watch(count, (newVal, oldVal) => {
  console.log(newVal)
})

2.5 watchEffect() —— 自动依赖收集

watchEffect(() => {
  console.log(count.value)
})

3. 模板指令(核心)

3.1 v-if / v-else

<p v-if="isLogin">已登录</p>
<p v-else>未登录</p>

3.2 v-show(频繁切换更快)

<p v-show="open">内容</p>

3.3 v-for

<li v-for="item in list" :key="item.id">{{ item.name }}</li>

3.4 v-bind / :

<img :src="imgUrl" />
<div :class="{ active: isActive }"></div>

3.5 v-on / @

<button @click="doIt">Click</button>
<button @keyup.enter="search">Search</button>

4. v-model(双向绑定)

4.1 基础

<input v-model="name" />

4.2 修饰符

<input v-model.trim="name" />
<input v-model.number="age" />
<input v-model.lazy="text" />

4.3 组件上的 v-model(父 ↔ 子)

子组件:

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

父:

<MyInput v-model="username" />

5. 组件通信

5.1 Props(父 → 子)

子组件:

const props = defineProps({
  title: String,
  count: Number
})

父组件:

<MyComp title="A" :count="10" />

5.2 Emits(子 → 父)

子组件:

const emit = defineEmits(['save'])
emit('save', data)

父组件:

<MyForm @save="onSave" />

5.3 插槽(slot)

父:

<Card>
  <template #header>头</template>
  内容
</Card>

子:

<slot name="header" />
<slot />

6. 生命周期(Composition API)

onMounted(() => {})
onUnmounted(() => {})
onUpdated(() => {})
onBeforeMount(() => {})
onBeforeUpdate(() => {})
onBeforeUnmount(() => {})

7. 路由(vue-router)

7.1 定义

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/user/:id', component: User }
  ]
})

7.2 在 main.js

app.use(router)

7.3 在组件里获取路由

const route = useRoute()
const router = useRouter()

router.push('/login')
console.log(route.params.id)

8. Pinia(Vuex 替代)

8.1 定义 Store

export const useCounter = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() { this.count++ }
  }
})

8.2 使用

const counter = useCounter()
counter.increment()

9. Axios(最常用)

import axios from 'axios'

const api = axios.create({ baseURL: '/api' })

api.get('/user/1').then(res => {
  console.log(res)
})

10. 模板 ref(DOM 引用)

<input ref="inputEl" />

<script setup>
const inputEl = ref(null)
onMounted(() => inputEl.value.focus())
</script>

11. 子组件方法暴露

子组件:

<script setup>
function doSomething() {}
defineExpose({ doSomething })
</script>

父组件:

<Child ref="child" />
<script setup>
const child = ref(null)
child.value.doSomething()
</script>

12. 组合式函数(复用逻辑)

export function useCounter() {
  const count = ref(0)
  const inc = () => count.value++
  return { count, inc }
}

13. 条件 class / style

<div :class="{ active: ok, disabled: !ok }"></div>

<div :style="{ color: color, fontSize: size + 'px' }"></div>

14. 异步组件(懒加载)

const Dialog = defineAsyncComponent(() => import('./Dialog.vue'))

15. KeepAlive 缓存页面

<keep-alive>
  <RouterView />
</keep-alive>

16. Teleport(传送到指定 DOM)

<Teleport to="body">
  <Modal />
</Teleport>

17. 自定义指令

注册:

app.directive('focus', {
  mounted(el) { el.focus() }
})

使用:

<input v-focus />

18. 常用项目结构(推荐)

src/
  api/             # 后端接口
  assets/
  components/
  composables/     # useXxx 组合函数
  views/
  router/
  stores/          # pinia
  App.vue
  main.js

19. 性能优化快速清单

  • 使用 v-show 替代 v-if(频繁切换)

  • 使用 key 提升列表性能

  • 使用 computed 而不是 watch

  • 大组件切分成小组件

  • 路由懒加载 () => import(...)

  • 使用 KeepAlive 缓存页面


20. Vue 2 → Vue 3 迁移对照

Vue2

Vue3

data()

ref / reactive

methods

普通函数

computed

computed

watch

watch

mounted

onMounted

props

defineProps

emit

defineEmits

this

不再需要

mixins

composables