全栈学习 —— 前端(三)Vue框架

本文章已经生成可运行项目,

目录

一、Vue 框架核心思想与优势

1. 核心设计理念

2. 与其他框架的对比

3. 为什么选择 Vue?

二、Vue 基础环境搭建

1. 快速入门:直接引入 Vue

2. 工程化开发:Vue CLI

(1)安装 Vue CLI

(2)创建项目

(3)运行项目

三、Vue 核心概念与基础语法

1. Vue 实例与数据绑定

2. 指令系统

(1)v-bind:绑定属性

(2)v-model:双向数据绑定

(3)v-for:列表渲染

(4)v-if与v-show:条件渲染

(5)v-on:事件绑定

3. 计算属性与侦听器

(1)计算属性(computed)

(2)侦听器(watch)

四、Vue 组件化开发

1. 组件的定义与注册

(1)全局组件

(2)局部组件

2. 组件通信

(1)父组件向子组件传值(props)

(2)子组件向父组件传值($emit)

3. 插槽(Slot)

五、Vue 底层原理

1. 响应式系统原理

2. 虚拟 DOM 与 Diff 算法

六、Vue Router 路由管理

1. 安装与配置

2. 路由使用

3. 动态路由与参数传递

七、实战项目:待办事项应用(Todo App)

1. 项目功能设计

2. 项目结构

3. 核心代码实现

(1)状态管理(stores/todoStore.js)

(2)输入组件(components/TodoInput.vue)

(3)列表项组件(components/TodoItem.vue)

(4)列表组件(components/TodoList.vue)

(5)筛选组件(components/TodoFilter.vue)

(6)主组件(App.vue)

4. 项目运行与测试


Vue.js(简称 Vue)是一套用于构建用户界面的渐进式 JavaScript 框架。自 2014 年由尤雨溪发布以来,凭借其简洁的 API、优秀的性能和灵活的集成能力,成为前端开发的主流框架之一。本文将从基础概念到实战开发,全面解析 Vue 的核心原理与应用。

一、Vue 框架核心思想与优势

1. 核心设计理念

Vue 的核心思想可以概括为两点:数据驱动组件化

  • 数据驱动(Data-Driven)
    传统 DOM 操作需要手动将数据同步到视图,而 Vue 通过数据绑定机制,使视图自动响应数据变化。当数据更新时,视图会自动重新渲染,无需开发者编写 DOM 操作代码。

  • 组件化(Component-Based)
    将页面拆分为独立、可复用的组件(Component),每个组件包含自身的结构(HTML)、样式(CSS)和逻辑(JavaScript)。组件化让代码更易维护、复用性更高。

2. 与其他框架的对比

框架特点适用场景
Vue渐进式框架,API 简洁,学习曲线平缓中小型应用、快速开发、与现有项目集成
React基于 JSX,生态庞大,灵活性高大型复杂应用、需要高度定制化的场景
Angular全功能框架,TypeScript 友好,规范严格企业级大型应用、团队协作开发

3. 为什么选择 Vue?

  • 易学易用:HTML、CSS、JavaScript 基础开发者可快速上手
  • 性能出色:采用虚拟 DOM 和响应式系统,渲染效率高
  • 灵活性强:可作为库引入现有项目,也可构建完整单页应用
  • 完善生态:配套工具(Vue CLI、Vue Router、Pinia)和社区支持丰富

二、Vue 基础环境搭建

1. 快速入门:直接引入 Vue

适合小型项目或学习测试:

<!-- 引入Vue 3 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<div id="app">
  {{ message }}
</div>

<script>
  // 创建Vue实例
  const { createApp } = Vue
  createApp({
    data() {
      return {
        message: 'Hello Vue!'
      }
    }
  }).mount('#app')
</script>

2. 工程化开发:Vue CLI

适合中大型项目,提供完整的开发环境配置:

(1)安装 Vue CLI

# 全局安装Vue

CLI npm install -g @vue/cli

# 检查版本

vue --version

(2)创建项目
# 创建项目
vue create my-vue-app

# 选择预设(推荐手动选择特性)
# 选择Vue 3、Babel、Router、Vuex等必要组件
(3)运行项目
cd my-vue-app
npm run serve

访问http://localhost:8080即可看到默认页面。

三、Vue 核心概念与基础语法

1. Vue 实例与数据绑定

Vue 应用的入口是通过createApp创建的应用实例,所有功能都基于这个实例展开。

<div id="app">
  <h1>{{ title }}</h1>
  <p>{{ user.name }} - {{ user.age }}岁</p>
  <p>{{ isActive ? '在线' : '离线' }}</p>
  <p>{{ message.split('').reverse().join('') }}</p>
</div>

<script>
  const app = Vue.createApp({
    // 数据对象,返回应用所需的响应式数据
    data() {
      return {
        title: 'Vue基础示例',
        user: {
          name: '张三',
          age: 25
        },
        isActive: true,
        message: 'Hello'
      }
    }
  })
  
  // 将应用挂载到DOM元素上
  app.mount('#app')
</script>
  • {{ }}:插值表达式,用于将数据渲染到视图中
  • 支持 JavaScript 表达式(如三元运算、字符串方法)
  • 数据是响应式的:当数据变化时,视图会自动更新

2. 指令系统

Vue 提供了一系列特殊属性(指令),用于在模板中实现逻辑控制。

(1)v-bind:绑定属性
<!-- 完整语法 -->
<img v-bind:src="imageUrl" v-bind:alt="imageAlt">

<!-- 简写形式(推荐) -->
<img :src="imageUrl" :alt="imageAlt">
data() {
  return {
    imageUrl: 'logo.png',
    imageAlt: '网站logo'
  }
}
(2)v-model:双向数据绑定

主要用于表单元素,实现数据与视图的双向同步:

<div id="app">
  <input type="text" v-model="username">
  <p>你输入的是:{{ username }}</p>
  
  <textarea v-model="content"></textarea>
  
  <select v-model="selectedCity">
    <option value="">请选择</option>
    <option value="beijing">北京</option>
    <option value="shanghai">上海</option>
  </select>
</div>

<script>
  Vue.createApp({
    data() {
      return {
        username: '',
        content: '',
        selectedCity: ''
      }
    }
  }).mount('#app')
</script>
(3)v-for:列表渲染

用于循环渲染数组或对象:

<ul>
  <!-- 遍历数组 -->
  <li v-for="(item, index) in fruits" :key="index">
    {{ index + 1 }}. {{ item }}
  </li>
</ul>

<table border="1">
  <tr>
    <th>姓名</th>
    <th>年龄</th>
  </tr>
  <!-- 遍历对象数组 -->
  <tr v-for="user in users" :key="user.id">
    <td>{{ user.name }}</td>
    <td>{{ user.age }}</td>
  </tr>
</table>

data() {
  return {
    fruits: ['苹果', '香蕉', '橙子'],
    users: [
      { id: 1, name: '张三', age: 20 },
      { id: 2, name: '李四', age: 22 }
    ]
  }
}
  • :key:提高渲染性能,确保每个节点的唯一性
(4)v-ifv-show:条件渲染

<!-- v-if:条件不满足时元素会从DOM中移除 -->
<div v-if="isLogin">欢迎回来,{{ username }}!</div>
<div v-else>请先登录</div>

<!-- 多条件判断 -->
<div v-if="score >= 90">优秀</div>
<div v-else-if="score >= 60">及格</div>
<div v-else>不及格</div>

<!-- v-show:条件不满足时元素隐藏(通过display: none) -->
<div v-show="isActive">这是可以显示/隐藏的内容</div>

data() {
  return {
    isLogin: true,
    username: '张三',
    score: 85,
    isActive: false
  }
}
  • 区别v-if有更高的切换开销,v-show有更高的初始渲染开销
  • 频繁切换用v-show,条件很少改变用v-if
(5)v-on:事件绑定
<!-- 完整语法 -->
<button v-on:click="handleClick">点击我</button>

<!-- 简写形式(推荐) -->
<button @click="handleClick">点击我</button>

<!-- 传递参数 -->
<button @click="sayHello('Vue')">打招呼</button>

<!-- 事件修饰符 -->
<button @click.stop="handleClick">阻止冒泡</button>
<input @keyup.enter="handleSubmit">
data() {
  return {
    count: 0
  }
},
methods: {
  handleClick() {
    this.count++
  },
  sayHello(name) {
    alert(`Hello ${name}!`)
  },
  handleSubmit() {
    console.log('提交表单')
  }
}
  • 事件处理函数定义在methods选项中
  • this指向当前 Vue 实例
  • 事件修饰符:.stop(阻止冒泡)、.prevent(阻止默认行为)、.enter(按键修饰符)等

3. 计算属性与侦听器

(1)计算属性(computed)

用于处理复杂逻辑计算,具有缓存特性:

<div id="app">
  <p>原始价格:{{ price }}</p>
  <p>折扣价:{{ discountedPrice }}</p>
  <p>总价:{{ totalPrice }}</p>
</div>

<script>
  Vue.createApp({
    data() {
      return {
        price: 100,
        quantity: 3,
        discount: 0.8
      }
    },
    computed: {
      // 计算折扣价
      discountedPrice() {
        return this.price * this.discount
      },
      // 计算总价
      totalPrice() {
        return this.discountedPrice * this.quantity
      }
    }
  }).mount('#app')
</script>

  • 计算属性会基于依赖的数据自动更新
  • 相比方法(methods),计算属性有缓存,多次访问不会重复计算
(2)侦听器(watch)

用于监听数据变化并执行副作用操作:

<div id="app">
  <input v-model="username">
  <p>{{ message }}</p>
</div>

<script>
  Vue.createApp({
    data() {
      return {
        username: '',
        message: ''
      }
    },
    watch: {
      // 监听username变化
      username(newVal, oldVal) {
        if (newVal.length < 3) {
          this.message = '用户名长度不能少于3个字符'
        } else {
          this.message = '用户名可用'
        }
      }
    }
  }).mount('#app')
</script>
  • 适合处理异步操作或复杂逻辑(如数据请求、定时器等)

四、Vue 组件化开发

组件是 Vue 的核心概念,将页面拆分为独立可复用的模块。

1. 组件的定义与注册

(1)全局组件

在整个应用中都可使用:

// 定义全局组件
const app = Vue.createApp({})

app.component('my-component', {
  template: `
    <div class="my-component">
      <h3>{{ title }}</h3>
      <p>{{ content }}</p>
    </div>
  `,
  data() {
    return {
      title: '我是全局组件',
      content: '这是全局组件的内容'
    }
  }
})

app.mount('#app')

使用组件:

<div id="app">
  <my-component></my-component>
</div>
(2)局部组件

仅在注册它的父组件中可用:

// 定义局部组件
const MyComponent = {
  template: `
    <div>
      <p>{{ message }}</p>
    </div>
  `,
  data() {
    return {
      message: '我是局部组件'
    }
  }
}

// 在父组件中注册
const app = Vue.createApp({
  components: {
    'my-component': MyComponent
  }
})

app.mount('#app')

2. 组件通信

(1)父组件向子组件传值(props)

javascript

// 子组件
const ChildComponent = {
  // 声明接收的props
  props: ['title', 'userInfo', 'isActive'],
  template: `
    <div>
      <h3>{{ title }}</h3>
      <p>{{ userInfo.name }} - {{ userInfo.age }}岁</p>
      <p v-if="isActive">激活状态</p>
    </div>
  `
}

// 父组件
const app = Vue.createApp({
  components: {
    'child-component': ChildComponent
  },
  data() {
    return {
      parentTitle: '来自父组件的标题',
      user: {
        name: '张三',
        age: 25
      },
      active: true
    }
  }
})

使用时传递数据:

<child-component 
  :title="parentTitle" 
  :user-info="user" 
  :is-active="active"
></child-component>
  • props 命名:在组件中使用驼峰式(camelCase),在模板中使用短横线式(kebab-case)
  • 可以指定 props 类型和验证:
props: {
  // 基础类型检查
  age: Number,
  // 多个可能的类型
  id: [String, Number],
  // 必传且是字符串
  name: {
    type: String,
    required: true
  },
  // 带默认值的数字
  count: {
    type: Number,
    default: 0
  }
}
(2)子组件向父组件传值($emit)

// 子组件
const ChildComponent = {
  template: `
    <button @click="handleClick">点击发送消息</button>
  `,
  methods: {
    handleClick() {
      // 触发自定义事件,并传递数据
      this.$emit('send-message', '来自子组件的消息', 123)
    }
  }
}

// 父组件
const app = Vue.createApp({
  components: {
    'child-component': ChildComponent
  },
  methods: {
    // 接收子组件传递的消息
    receiveMessage(msg, num) {
      console.log('收到消息:', msg, num)
    }
  }
})

父组件监听事件:

<child-component @send-message="receiveMessage"></child-component>

3. 插槽(Slot)

用于组件内容分发,使组件更灵活:

// 子组件(MyButton)
const MyButton = {
  template: `
    <button class="my-button">
      <!-- 插槽:父组件传递的内容会放在这里 -->
      <slot></slot>
    </button>
  `
}

// 父组件使用
const app = Vue.createApp({
  components: {
    'my-button': MyButton
  }
})

使用带插槽的组件:

<my-button>
  <span>点击我</span>
</my-button>

<my-button>
  <i class="icon"></i> 提交
</my-button>

具名插槽(多个插槽区分):

// 子组件
const Card = {
  template: `
    <div class="card">
      <div class="card-header">
        <slot name="header"></slot>
      </div>
      <div class="card-body">
        <slot></slot> <!-- 默认插槽 -->
      </div>
      <div class="card-footer">
        <slot name="footer"></slot>
      </div>
    </div>
  `
}

使用具名插槽:

<card>
  <template v-slot:header>
    <h3>卡片标题</h3>
  </template>
  
  <p>这是卡片内容...</p>
  
  <template v-slot:footer>
    <button>确定</button>
  </template>
</card>

五、Vue 底层原理

1. 响应式系统原理

Vue 的响应式系统基于数据劫持依赖收集实现:

  1. 初始化阶段

    • Vue 会遍历 data 中的所有属性,使用Object.defineProperty(Vue 2)或Proxy(Vue 3)进行劫持
    • 为每个属性创建对应的Dep(依赖管理器),用于收集依赖
  2. 依赖收集

    • 当组件渲染时,会触发属性的 getter 方法
    • getter 方法会将当前组件的Watcher(观察者)添加到Dep
  3. 数据更新

    • 当属性被修改时,会触发 setter 方法
    • setter 方法会通知Dep中的所有Watcher
    • Watcher触发组件重新渲染

Vue 3 使用 Proxy 替代 Object.defineProperty 的优势:

  • 支持监听数组变化
  • 支持监听新增 / 删除属性
  • 性能更好,劫持整个对象而非单个属性

2. 虚拟 DOM 与 Diff 算法

Vue 通过虚拟 DOM 提高渲染性能:

  1. 虚拟 DOM

    • 用 JavaScript 对象模拟真实 DOM 结构
    • 例如:{ tag: 'div', props: { id: 'app' }, children: [] }
  2. Diff 算法

    • 当数据变化时,Vue 会生成新的虚拟 DOM
    • 通过 Diff 算法对比新旧虚拟 DOM 的差异
    • 只更新差异部分对应的真实 DOM,减少 DOM 操作
  3. key 的作用

    • 在列表渲染中,key 帮助 Vue 识别元素的唯一性
    • 提高 Diff 算法的效率,避免不必要的元素重绘

六、Vue Router 路由管理

Vue Router 是 Vue 官方的路由管理器,用于构建单页应用(SPA)。

1. 安装与配置

# 安装Vue Router(Vue 3对应版本)
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(), // HTML5 history模式
  routes
})

export default router

在 main.js 中引入:

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

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

2. 路由使用

在 App.vue 中添加路由出口和导航:

<template>
  <div id="app">
    <!-- 路由导航 -->
    <nav>
      <router-link to="/">首页</router-link> |
      <router-link to="/about">关于</router-link>
    </nav>
    
    <!-- 路由出口:匹配的组件将在这里渲染 -->
    <router-view></router-view>
  </div>
</template>

3. 动态路由与参数传递

// 路由配置
{
  path: '/user/:id',
  name: 'User',
  component: User
}

在组件中获取参数:

// User.vue
export default {
  mounted() {
    // 通过$route获取路由参数
    console.log(this.$route.params.id)
  }
}

编程式导航:

// 导航到用户页面
this.$router.push({
  name: 'User',
  params: { id: 123 }
})

// 后退
this.$router.go(-1)

七、实战项目:待办事项应用(Todo App)

1. 项目功能设计

  • 添加待办事项
  • 标记待办事项为已完成
  • 删除待办事项
  • 筛选待办事项(全部 / 已完成 / 未完成)
  • 统计待办事项数量

2. 项目结构

todo-app/
├── public/
├── src/
│   ├── components/
│   │   ├── TodoInput.vue      // 输入组件
│   │   ├── TodoList.vue       // 列表组件
│   │   ├── TodoItem.vue       // 列表项组件
│   │   └── TodoFilter.vue     // 筛选组件
│   ├── stores/
│   │   └── todoStore.js       // 状态管理
│   ├── App.vue
│   └── main.js
└── package.json

3. 核心代码实现

(1)状态管理(stores/todoStore.js)
import { defineStore } from 'pinia'

export const useTodoStore = defineStore('todo', {
  state: () => ({
    todos: [
      { id: 1, text: '学习Vue', completed: false },
      { id: 2, text: '完成项目', completed: true }
    ],
    filter: 'all' // all, active, completed
  }),
  getters: {
    // 筛选后的待办事项
    filteredTodos(state) {
      switch (state.filter) {
        case 'active':
          return state.todos.filter(todo => !todo.completed)
        case 'completed':
          return state.todos.filter(todo => todo.completed)
        default:
          return state.todos
      }
    },
    // 未完成数量
    activeCount(state) {
      return state.todos.filter(todo => !todo.completed).length
    }
  },
  actions: {
    // 添加待办
    addTodo(text) {
      if (!text.trim()) return
      this.todos.push({
        id: Date.now(),
        text,
        completed: false
      })
    },
    // 切换完成状态
    toggleTodo(id) {
      const todo = this.todos.find(todo => todo.id === id)
      if (todo) {
        todo.completed = !todo.completed
      }
    },
    // 删除待办
    deleteTodo(id) {
      this.todos = this.todos.filter(todo => todo.id !== id)
    },
    // 设置筛选条件
    setFilter(filter) {
      this.filter = filter
    }
  }
})
(2)输入组件(components/TodoInput.vue)
<template>
  <input
    type="text"
    v-model="newTodo"
    @keyup.enter="handleAdd"
    placeholder="请输入待办事项..."
  >
  <button @click="handleAdd">添加</button>
</template>

<script>
import { useTodoStore } from '../stores/todoStore'

export default {
  data() {
    return {
      newTodo: ''
    }
  },
  methods: {
    handleAdd() {
      const todoStore = useTodoStore()
      todoStore.addTodo(this.newTodo)
      this.newTodo = '' // 清空输入框
    }
  }
}
</script>
(3)列表项组件(components/TodoItem.vue)
<template>
  <li :class="{ completed: todo.completed }">
    <span @click="$emit('toggle', todo.id)">{{ todo.text }}</span>
    <button @click="$emit('delete', todo.id)">删除</button>
  </li>
</template>

<script>
export default {
  props: {
    todo: {
      type: Object,
      required: true
    }
  }
}
</script>

<style>
.completed {
  text-decoration: line-through;
  color: #999;
}
</style>
(4)列表组件(components/TodoList.vue)
<template>
  <ul>
    <todo-item
      v-for="todo in filteredTodos"
      :key="todo.id"
      :todo="todo"
      @toggle="toggleTodo"
      @delete="deleteTodo"
    ></todo-item>
  </ul>
  <p v-if="filteredTodos.length === 0">没有待办事项</p>
</template>

<script>
import TodoItem from './TodoItem.vue'
import { useTodoStore } from '../stores/todoStore'

export default {
  components: {
    TodoItem
  },
  computed: {
    filteredTodos() {
      return useTodoStore().filteredTodos
    }
  },
  methods: {
    toggleTodo(id) {
      useTodoStore().toggleTodo(id)
    },
    deleteTodo(id) {
      useTodoStore().deleteTodo(id)
    }
  }
}
</script>
(5)筛选组件(components/TodoFilter.vue)
<template>
  <div>
    <span>筛选:</span>
    <button 
      :class="{ active: currentFilter === 'all' }"
      @click="$emit('filter', 'all')"
    >
      全部
    </button>
    <button 
      :class="{ active: currentFilter === 'active' }"
      @click="$emit('filter', 'active')"
    >
      未完成
    </button>
    <button 
      :class="{ active: currentFilter === 'completed' }"
      @click="$emit('filter', 'completed')"
    >
      已完成
    </button>
    <span>剩余:{{ activeCount }} 项</span>
  </div>
</template>

<script>
import { useTodoStore } from '../stores/todoStore'

export default {
  props: {
    currentFilter: {
      type: String,
      required: true
    }
  },
  computed: {
    activeCount() {
      return useTodoStore().activeCount
    }
  }
}
</script>

<style>
.active {
  background-color: #42b983;
  color: white;
}
</style>
(6)主组件(App.vue)
<template>
  <div id="app">
    <h1>待办事项</h1>
    <todo-input></todo-input>
    <todo-list></todo-list>
    <todo-filter 
      :current-filter="filter"
      @filter="handleFilter"
    ></todo-filter>
  </div>
</template>

<script>
import TodoInput from './components/TodoInput.vue'
import TodoList from './components/TodoList.vue'
import TodoFilter from './components/TodoFilter.vue'
import { useTodoStore } from './stores/todoStore'

export default {
  components: {
    TodoInput,
    TodoList,
    TodoFilter
  },
  computed: {
    filter() {
      return useTodoStore().filter
    }
  },
  methods: {
    handleFilter(filter) {
      useTodoStore().setFilter(filter)
    }
  }
}
</script>

4. 项目运行与测试

# 安装依赖
npm install

# 运行项目
npm run serve

访问http://localhost:8080即可使用待办事项应用,测试各功能是否正常工作:

  • 输入框中输入内容,按回车或点击添加按钮
  • 点击待办事项文本切换完成状态
  • 点击删除按钮移除待办事项
  • 切换筛选按钮查看不同状态的待办事项
  • 检查剩余数量统计是否正确

本文已生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

peachcobbler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值