Vue.js 教程

项目概述

Vue.js 是一款渐进式 JavaScript 框架,用于构建用户界面。它采用自底向上增量开发的设计,核心库只关注视图层,易于上手,同时可以与第三方库或现有项目整合。Vue.js 具有响应式数据绑定、组件化开发、指令系统等特点,为开发者提供了构建现代化 Web 应用的强大工具。

主要特点

  • 响应式数据绑定:自动追踪依赖,当数据变化时自动更新视图
  • 组件化开发:将 UI 拆分为独立、可复用的组件
  • 指令系统:提供丰富的指令简化 DOM 操作
  • 虚拟 DOM:通过内存中的虚拟 DOM 提高渲染性能
  • 过渡动画:内置过渡效果系统,使动画更加简单
  • 易于学习:API 设计简洁明了,文档完善
  • 灵活渐进:可以根据需要逐步采用其特性

适用场景

  • 单页应用(SPA)
  • 交互式用户界面
  • 移动端应用(通过 Vue Native 或 Weex)
  • 渐进式 Web 应用(PWA)
  • 与现有项目集成

安装与设置

方法一:使用 CDN

最简单的开始方式是通过 CDN 引入 Vue.js。

<!DOCTYPE html>
<html>
<head>
  <title>Vue.js 示例</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
  <div id="app">
    {{ message }}
  </div>

  <script>
    const { createApp } = Vue

    createApp({
      data() {
        return {
          message: 'Hello Vue!'
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

方法二:使用 Vite

Vite 是 Vue 官方推荐的构建工具,提供了快速的开发体验。

# 使用 npm
npm create vite@latest my-vue-app -- --template vue

# 使用 yarn
yarn create vite my-vue-app --template vue

# 使用 pnpm
pnpm create vite my-vue-app --template vue

# 进入项目目录
cd my-vue-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

方法三:使用 Vue CLI

Vue CLI 是官方提供的脚手架工具,适合构建大型应用。

# 安装 Vue CLI
npm install -g @vue/cli

# 创建新的 Vue 项目
vue create my-vue-app

# 进入项目目录
cd my-vue-app

# 启动开发服务器
npm run serve

核心概念

1. 应用实例

每个 Vue 应用都是通过 createApp 函数创建的应用实例。

import { createApp } from 'vue'

const app = createApp({
  // 根组件选项
})

app.mount('#app') // 挂载到 DOM 元素

2. 模板语法

Vue 使用基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定到底层组件实例的数据。

<template>
  <div>
    <h1>{{ message }}</h1>
    <p>{{ count * 2 }}</p>
    <p>{{ isActive ? 'Active' : 'Inactive' }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue!',
      count: 10,
      isActive: true
    }
  }
}
</script>

3. 响应式数据

Vue 的响应式系统会自动追踪依赖,当数据变化时更新视图。

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++ // 数据变化时,视图会自动更新
    }
  }
}
</script>

4. 组件

组件是 Vue 应用的基本构建块,允许开发者将 UI 拆分为独立、可复用的部分。

定义组件

<!-- HelloWorld.vue -->
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

使用组件

<template>
  <div id="app">
    <HelloWorld msg="Welcome to Your Vue.js App" />
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

5. 指令

指令是带有 v- 前缀的特殊属性,用于在模板中应用特殊的响应式行为。

常用指令

  • v-if:条件渲染
  • v-for:列表渲染
  • v-bind:绑定属性(简写为 :
  • v-on:事件监听(简写为 @
  • v-model:双向数据绑定
<template>
  <div>
    <!-- 条件渲染 -->
    <p v-if="isVisible">This is visible</p>
    <p v-else>This is hidden</p>

    <!-- 列表渲染 -->
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>

    <!-- 属性绑定 -->
    <img :src="imageSrc" :alt="imageAlt">

    <!-- 事件监听 -->
    <button @click="handleClick">Click me</button>

    <!-- 双向数据绑定 -->
    <input v-model="message" placeholder="Enter a message">
    <p>Message: {{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: true,
      items: [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ],
      imageSrc: 'https://example.com/image.jpg',
      imageAlt: 'Example image',
      message: ''
    }
  },
  methods: {
    handleClick() {
      console.log('Button clicked')
    }
  }
}
</script>

6. 计算属性

计算属性是基于响应式依赖进行缓存的计算值,适合复杂的逻辑计算。

<template>
  <div>
    <p>Original message: {{ message }}</p>
    <p>Reversed message: {{ reversedMessage }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue!'
    }
  },
  computed: {
    reversedMessage() {
      return this.message.split('').reverse().join('')
    }
  }
}
</script>

7. 监听器

监听器用于监听数据变化并执行相应的操作,适合异步或开销较大的操作。

<template>
  <div>
    <input v-model="question" placeholder="Ask a question">
    <p>{{ answer }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      answer: 'I cannot give you an answer until you ask a question!'
    }
  },
  watch: {
    // 每当 question 变化时,执行此函数
    question(newQuestion, oldQuestion) {
      this.answer = 'Thinking...'
      this.getAnswer()
    }
  },
  methods: {
    getAnswer() {
      // 模拟异步请求
      setTimeout(() => {
        this.answer = 'Here is an answer for: ' + this.question
      }, 1000)
    }
  }
}
</script>

8. 生命周期钩子

生命周期钩子是在组件不同阶段执行的函数,用于执行特定的逻辑。

<template>
  <div>{{ message }}</div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue!'
    }
  },
  beforeCreate() {
    console.log('Before create')
  },
  created() {
    console.log('Created')
  },
  beforeMount() {
    console.log('Before mount')
  },
  mounted() {
    console.log('Mounted')
  },
  beforeUpdate() {
    console.log('Before update')
  },
  updated() {
    console.log('Updated')
  },
  beforeUnmount() {
    console.log('Before unmount')
  },
  unmounted() {
    console.log('Unmounted')
  }
}
</script>

高级功能

1. 组合式 API

组合式 API 是 Vue 3 引入的新特性,允许开发者按功能组织代码,提高代码的可维护性。

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

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

// 响应式数据
const count = ref(0)

// 计算属性
const doubleCount = computed(() => count.value * 2)

// 方法
const increment = () => {
  count.value++
}
</script>

2. 自定义指令

自定义指令允许开发者创建自己的指令,扩展 Vue 的功能。

<template>
  <div>
    <input v-focus placeholder="This input will be focused">
  </div>
</template>

<script>
export default {
  directives: {
    focus: {
      // 当被绑定的元素挂载到 DOM 中时
      mounted(el) {
        el.focus()
      }
    }
  }
}
</script>

3. 插件

插件是 Vue 生态系统的重要组成部分,用于扩展 Vue 的功能。

创建插件

// plugins/myPlugin.js
export default {
  install(app, options) {
    // 添加全局方法
    app.config.globalProperties.$myMethod = (value) => {
      console.log('My method called with:', value)
    }

    // 添加全局属性
    app.provide('myPlugin', {
      version: '1.0.0',
      options
    })
  }
}

使用插件

import { createApp } from 'vue'
import App from './App.vue'
import MyPlugin from './plugins/myPlugin'

const app = createApp(App)
app.use(MyPlugin, { someOption: true })
app.mount('#app')

4. 过渡动画

Vue 提供了内置的过渡系统,用于在元素插入、更新或移除时添加动画效果。

<template>
  <div>
    <button @click="show = !show">Toggle</button>
    <transition name="fade">
      <p v-if="show">Hello Vue!</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  }
}
</script>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

5. 混入

混入是一种重用组件选项的方式,允许多个组件共享相同的逻辑。

// mixins/logger.js
export default {
  methods: {
    log(message) {
      console.log(`${this.$options.name}: ${message}`)
    }
  }
}
<template>
  <button @click="log('Button clicked')">Click me</button>
</template>

<script>
import loggerMixin from './mixins/logger'

export default {
  name: 'MyComponent',
  mixins: [loggerMixin]
}
</script>

实际应用场景

1. 表单处理

<template>
  <form @submit.prevent="handleSubmit">
    <div>
      <label for="name">Name:</label>
      <input 
        type="text" 
        id="name" 
        v-model="form.name" 
        required
      >
    </div>
    <div>
      <label for="email">Email:</label>
      <input 
        type="email" 
        id="email" 
        v-model="form.email" 
        required
      >
    </div>
    <div>
      <label for="password">Password:</label>
      <input 
        type="password" 
        id="password" 
        v-model="form.password" 
        required
      >
    </div>
    <button type="submit">Submit</button>
  </form>
</template>

<script>
export default {
  data() {
    return {
      form: {
        name: '',
        email: '',
        password: ''
      }
    }
  },
  methods: {
    handleSubmit() {
      console.log('Form submitted:', this.form)
      // 这里可以添加表单提交逻辑
    }
  }
}
</script>

2. 数据获取

<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else>
      <h1>{{ post.title }}</h1>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      post: {},
      loading: true,
      error: null
    }
  },
  mounted() {
    this.fetchPost()
  },
  methods: {
    async fetchPost() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1')
        if (!response.ok) {
          throw new Error('Failed to fetch post')
        }
        this.post = await response.json()
      } catch (err) {
        this.error = err.message
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

3. 路由管理

使用 Vue Router 进行路由管理。

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

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')
<!-- App.vue -->
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </nav>
    <router-view />
  </div>
</template>

4. 状态管理

使用 Pinia(Vue 3 官方推荐的状态管理库)进行状态管理。

npm install pinia
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    }
  }
})
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')
<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double count: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">Increment</button>
    <button @click="counter.decrement">Decrement</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../stores/counter'

const counter = useCounterStore()
</script>

性能优化

1. 响应式数据优化

  • 使用 shallowRefshallowReactive 处理不需要深度响应的数据
  • 使用 readonly 创建只读数据,避免意外修改
  • 使用 markRaw 标记不需要响应式的数据
import { ref, shallowRef, shallowReactive, readonly, markRaw } from 'vue'

// 深度响应(默认)
const deepRef = ref({ nested: { value: 1 } })

// 浅层响应
const shallow = shallowRef({ nested: { value: 1 } })

// 浅层响应式对象
const shallowObj = shallowReactive({ nested: { value: 1 } })

// 只读数据
const readOnlyData = readonly({ value: 1 })

// 非响应式数据
const nonReactive = markRaw({ value: 1 })

2. 组件优化

  • 使用 defineAsyncComponent 懒加载组件
  • 使用 v-memo 指令缓存组件渲染结果
  • 合理使用 v-ifv-show
  • 避免在模板中使用复杂表达式
import { defineAsyncComponent } from 'vue'

// 懒加载组件
const LazyComponent = defineAsyncComponent(() => import('./LazyComponent.vue'))
<template>
  <!-- 缓存渲染结果 -->
  <div v-memo="[value1, value2]">
    {{ heavyComputation(value1, value2) }}
  </div>

  <!-- 频繁切换使用 v-show -->
  <div v-show="isVisible">Frequently toggled content</div>

  <!-- 不频繁切换使用 v-if -->
  <div v-if="isVisible">Rarely toggled content</div>
</template>

3. 构建优化

  • 使用 Vite 进行开发和构建
  • 配置合理的分包策略
  • 压缩和混淆代码
  • 移除未使用的代码
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router'],
          common: ['axios', 'lodash']
        }
      }
    }
  }
})

4. 运行时优化

  • 使用虚拟滚动处理长列表
  • 优化事件监听器,避免内存泄漏
  • 使用 requestAnimationFrame 进行动画
  • 合理使用 keep-alive 缓存组件状态
<template>
  <div>
    <!-- 缓存组件状态 -->
    <keep-alive>
      <component :is="currentComponent" />
    </keep-alive>
  </div>
</template>

最佳实践

1. 组件设计

  • 保持组件小而专注
  • 遵循单一职责原则
  • 使用命名约定保持一致性
  • 合理使用 props 和 emit

2. 代码组织

  • 按功能模块组织文件
  • 使用文件夹结构管理组件
  • 分离关注点(UI、逻辑、数据)
  • 使用注释说明复杂逻辑

3. 状态管理

  • 对于简单应用,使用组件内置状态
  • 对于复杂应用,使用 Pinia 进行状态管理
  • 合理划分状态模块
  • 避免过度使用全局状态

4. 测试

  • 使用 Vitest 进行单元测试
  • 使用 Cypress 进行端到端测试
  • 测试组件的渲染和交互
  • 编写覆盖关键功能的测试用例

5. 性能

  • 优化首屏加载速度
  • 减少不必要的渲染
  • 合理使用缓存策略
  • 监控和分析性能瓶颈

常见问题与解决方案

1. 响应式数据不更新

问题:修改对象或数组的属性后,视图没有更新。

解决方案:使用 Vue 的响应式 API 或遵循响应式规则。

// 错误做法
this.user.name = 'New Name' // 直接修改对象属性
this.items[0] = 'New Item' // 直接修改数组元素

// 正确做法
this.$set(this.user, 'name', 'New Name') // 使用 $set
this.items.splice(0, 1, 'New Item') // 使用数组方法
this.items = [...this.items] // 创建新数组

2. 组件通信问题

问题:组件之间的通信困难。

解决方案:根据组件关系选择合适的通信方式。

  • 父子组件:使用 props 和 emit
  • 兄弟组件:使用事件总线或状态管理
  • 跨层级组件:使用 provide/inject 或状态管理

3. 路由导航问题

问题:路由导航后页面没有更新或数据没有加载。

解决方案:使用路由守卫或监听路由变化。

// 监听路由变化
watch(
  () => this.$route.params.id,
  (newId) => {
    this.fetchData(newId)
  }
)

// 或使用组件内导航守卫
beforeRouteUpdate(to, from, next) {
  this.fetchData(to.params.id)
  next()
}

4. 内存泄漏

问题:组件卸载后,事件监听器或定时器没有清理。

解决方案:在组件卸载前清理资源。

<template>
  <div>{{ time }}</div>
</template>

<script>
export default {
  data() {
    return {
      time: new Date().toLocaleTimeString(),
      timer: null
    }
  },
  mounted() {
    this.timer = setInterval(() => {
      this.time = new Date().toLocaleTimeString()
    }, 1000)
  },
  beforeUnmount() {
    // 清理定时器
    if (this.timer) {
      clearInterval(this.timer)
    }
  }
}
</script>

参考资源

总结

Vue.js 是一款功能强大、易于上手的前端框架,通过响应式数据绑定、组件化开发、指令系统等特性,为开发者提供了构建现代化 Web 应用的便捷工具。本教程介绍了 Vue.js 的核心概念、使用方法和最佳实践,希望能帮助你快速上手 Vue.js 开发。

Vue.js 的生态系统非常丰富,包括 Vue Router、Pinia、Vue Test Utils 等官方库,以及众多第三方插件和工具。建议你持续关注 Vue.js 的官方文档和社区动态,以便及时了解最新的开发技巧和趋势。

« 上一篇 React 中文教程 下一篇 » Vue.js 中文教程