粉粉蕉的笔记本粉粉蕉的笔记本
  • JAVA

    • 代码笔记
    • Java8实战
    • 分布式事务实战(Seata)
    • 模板引擎(FreeMarker)
    • SpringSecurity
    • Maven
  • PYTHON

    • 概述
    • python3
    • python3(菜鸟教程)
    • pandas
    • numpy
    • matplotlib
  • 中间件

    • Kafka
    • RocketMQ
    • Redis
    • MongoDB
    • Elastic Search
  • 数据库

    • Mysql
  • 前端

    • HTML
    • CSS
    • Javascript
    • Vue2学习笔记
    • Vue3学习笔记
  • 设计模式
  • 大数据

    • 概览
    • Hadoop
    • Hive
  • 机器学习

    • 机器学习概览
  • openclaw实战
  • claudecode实战
  • RAG
  • 拟人类Agent
  • linux命令速查
  • windows命令速查
  • Docker笔记
  • kubernetes学习笔记
  • kubernetes实操笔记
  • 运维工具大全
  • git操作宝典
  • 概率论
  • 线性代数
  • 统计学
  • 金融知识学习
  • 聚宽
  • 因子分析
  • 后端

    • JAVA基础
    • JAVA多线程
    • JVM
    • 分布式相关
    • 数据库
  • 前端

    • HTML
    • CSS
    • JAVASCRIPT
    • VUE3
    • 网络
    • 前端工程化
    • nodejs
  • AI

    • RAG
  • 健身

    • 笔记
    • 训练计划
  • 读书笔记

    • 《深度学习》
  • 其他

    • RSS
    • 资源导航
    • 医保
    • 装修攻略
我也想搭建这样的博客!
🚋开往
  • JAVA

    • 代码笔记
    • Java8实战
    • 分布式事务实战(Seata)
    • 模板引擎(FreeMarker)
    • SpringSecurity
    • Maven
  • PYTHON

    • 概述
    • python3
    • python3(菜鸟教程)
    • pandas
    • numpy
    • matplotlib
  • 中间件

    • Kafka
    • RocketMQ
    • Redis
    • MongoDB
    • Elastic Search
  • 数据库

    • Mysql
  • 前端

    • HTML
    • CSS
    • Javascript
    • Vue2学习笔记
    • Vue3学习笔记
  • 设计模式
  • 大数据

    • 概览
    • Hadoop
    • Hive
  • 机器学习

    • 机器学习概览
  • openclaw实战
  • claudecode实战
  • RAG
  • 拟人类Agent
  • linux命令速查
  • windows命令速查
  • Docker笔记
  • kubernetes学习笔记
  • kubernetes实操笔记
  • 运维工具大全
  • git操作宝典
  • 概率论
  • 线性代数
  • 统计学
  • 金融知识学习
  • 聚宽
  • 因子分析
  • 后端

    • JAVA基础
    • JAVA多线程
    • JVM
    • 分布式相关
    • 数据库
  • 前端

    • HTML
    • CSS
    • JAVASCRIPT
    • VUE3
    • 网络
    • 前端工程化
    • nodejs
  • AI

    • RAG
  • 健身

    • 笔记
    • 训练计划
  • 读书笔记

    • 《深度学习》
  • 其他

    • RSS
    • 资源导航
    • 医保
    • 装修攻略
我也想搭建这样的博客!
🚋开往
  • VUE2

    • 概述
    • Vue2 学习笔记
    • VueRouter
    • Vuex
    • Webpack

Vue2 学习笔记

项目目录结构

my-vue2-project/
├── node_modules/         # 依赖包
├── public/
│   └── index.html        # 宿主 HTML,Vue 挂载到 #app
├── src/
│   ├── api/              # 接口请求封装
│   ├── assets/           # 静态资源
│   ├── components/       # 可复用组件
│   ├── router/           # 路由配置(vue-router 3)
│   ├── store/            # 状态管理(Vuex)
│   ├── views/            # 页面级组件
│   ├── App.vue           # 根组件
│   └── main.js           # 入口文件
├── .env.development      # 开发环境变量
├── .env.production       # 生产环境变量
├── babel.config.js       # Babel 配置
├── vue.config.js         # Vue CLI / Webpack 配置
└── package.json

创建项目

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

# 创建项目
vue create my-vue2-app

# 选择 Vue 2 preset,或手动选择特性
cd my-vue2-app
npm run serve   # 启动开发服务器(默认 8080 端口)
npm run build   # 构建生产包

Vue 实例

// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

new Vue({
  el: '#app',       // 挂载点(也可以用 vm.$mount('#app'))
  router,
  store,
  render: h => h(App)
})

模板语法

<template>
  <div>
    <!-- 插值 -->
    <p>{{ message }}</p>
    <p>{{ message.toUpperCase() }}</p>

    <!-- 绑定属性(v-bind 简写 :) -->
    <img :src="imgUrl" :alt="title" />
    <button :disabled="isLoading">提交</button>

    <!-- 事件(v-on 简写 @) -->
    <button @click="handleClick">点击</button>
    <button @click="handleClick($event, 'arg1')">传参</button>
    <form @submit.prevent="handleSubmit">...</form>

    <!-- 条件渲染 -->
    <p v-if="score >= 90">优秀</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>

    <!-- v-show:切换 display,元素始终渲染 -->
    <p v-show="isVisible">可见内容</p>

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

    <!-- 双向绑定 -->
    <input v-model="inputValue" />
    <input v-model.trim="name" />      <!-- 去除首尾空格 -->
    <input v-model.number="age" />     <!-- 转数字 -->
    <input v-model.lazy="text" />      <!-- blur 时更新 -->
  </div>
</template>

Options API

<script>
export default {
  name: 'MyComponent',

  // 组件数据(必须是函数,保证每个实例独立)
  data() {
    return {
      message: 'Hello Vue2',
      count: 0,
      list: [],
      form: { name: '', age: '' },
    }
  },

  // 计算属性(有缓存,依赖不变不重新计算)
  computed: {
    fullName() {
      return `${this.form.name} (${this.form.age})`
    },
    // 可读可写的计算属性
    reversedMessage: {
      get() { return this.message.split('').reverse().join('') },
      set(val) { this.message = val.split('').reverse().join('') }
    }
  },

  // 侦听器(适合异步操作、副作用)
  watch: {
    // 简单侦听
    count(newVal, oldVal) {
      console.log(`count: ${oldVal} → ${newVal}`)
    },
    // 深度侦听对象
    form: {
      handler(newVal) { console.log('form changed', newVal) },
      deep: true,
      immediate: true  // 组件创建时立即执行一次
    },
    // 侦听对象的某个属性
    'form.name'(newVal) {
      console.log('name changed:', newVal)
    }
  },

  // 方法
  methods: {
    handleClick() {
      this.count++
    },
    async fetchData() {
      const res = await this.$axios.get('/api/list')
      this.list = res.data
    }
  },
}
</script>

生命周期

export default {
  // 创建阶段(此时还没有 DOM)
  beforeCreate() {
    // data、methods 还未初始化,很少使用
  },
  created() {
    // data、methods 已就绪,常用于发起数据请求
    this.fetchData()
  },

  // 挂载阶段
  beforeMount() {
    // DOM 即将生成
  },
  mounted() {
    // DOM 已生成,可操作 DOM、初始化第三方库(如 ECharts)
    this.$refs.chart.init()
  },

  // 更新阶段
  beforeUpdate() {
    // 数据已变,DOM 还未更新
  },
  updated() {
    // DOM 已更新,避免在此修改数据(死循环)
  },

  // 销毁阶段
  beforeDestroy() {
    // 组件即将销毁,清理定时器、取消事件监听
    clearInterval(this.timer)
    window.removeEventListener('resize', this.handleResize)
  },
  destroyed() {
    // 组件已销毁
  },

  // keep-alive 专用
  activated() { /* 从缓存中激活 */ },
  deactivated() { /* 进入缓存 */ },
}

组件

注册与使用

// 全局注册(在 main.js 中)
import MyButton from '@/components/MyButton.vue'
Vue.component('MyButton', MyButton)

// 局部注册(在单文件组件中)
export default {
  components: { MyButton }
}

Props(父 → 子)

<!-- 子组件 UserCard.vue -->
<script>
export default {
  props: {
    name: {
      type: String,
      required: true,
    },
    age: {
      type: Number,
      default: 0,
    },
    tags: {
      type: Array,
      default: () => [],  // 引用类型必须用工厂函数
    },
    config: {
      type: Object,
      default: () => ({ size: 'medium' }),
    },
    // 多种类型
    value: [String, Number],
    // 自定义校验
    status: {
      validator(val) {
        return ['active', 'inactive', 'pending'].includes(val)
      }
    }
  }
}
</script>

<!-- 父组件使用 -->
<template>
  <UserCard name="Tom" :age="25" :tags="['前端', 'Vue']" />
</template>

$emit(子 → 父)

<!-- 子组件 -->
<template>
  <button @click="handleClick">点击</button>
</template>

<script>
export default {
  methods: {
    handleClick() {
      this.$emit('click-btn', { count: 1 })  // 触发事件,传递数据
    }
  }
}
</script>

<!-- 父组件 -->
<template>
  <ChildComp @click-btn="handleChildClick" />
</template>

<script>
export default {
  methods: {
    handleChildClick(data) {
      console.log('子组件数据:', data)
    }
  }
}
</script>

v-model(父子双向绑定)

<!-- 父组件 -->
<MyInput v-model="searchText" />
<!-- 等价于 -->
<MyInput :value="searchText" @input="searchText = $event" />

<!-- 子组件 MyInput.vue -->
<template>
  <input :value="value" @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
  props: ['value']  // v-model 默认绑定 value prop
}
</script>

$refs(父访问子实例)

<template>
  <ChildComp ref="child" />
  <button @click="callChildMethod">调用子组件方法</button>
</template>

<script>
export default {
  methods: {
    callChildMethod() {
      this.$refs.child.reset()   // 直接调用子组件的方法
      console.log(this.$refs.child.value)  // 访问子组件的数据
    }
  }
}
</script>

provide / inject(跨层级传值)

// 祖先组件
export default {
  provide() {
    return {
      theme: 'dark',
      // ⚠️ Vue2 的 provide 默认不是响应式的
      // 要响应式需传对象引用
      config: this.config,
    }
  }
}

// 后代组件
export default {
  inject: ['theme', 'config'],
  mounted() {
    console.log(this.theme)  // 'dark'
  }
}

响应式注意事项

Vue2 基于 Object.defineProperty,有以下限制:

export default {
  data() {
    return {
      obj: { name: 'Tom' },
      arr: [1, 2, 3]
    }
  },
  methods: {
    update() {
      // ❌ 直接新增属性,不是响应式
      this.obj.age = 25

      // ✅ 用 $set 新增属性(响应式)
      this.$set(this.obj, 'age', 25)
      // 或
      Vue.set(this.obj, 'age', 25)

      // ❌ 直接删除属性,不是响应式
      delete this.obj.name

      // ✅ 用 $delete 删除
      this.$delete(this.obj, 'name')

      // ❌ 通过下标修改数组,不是响应式
      this.arr[0] = 10

      // ✅ 用变异方法 或 $set
      this.arr.splice(0, 1, 10)
      this.$set(this.arr, 0, 10)

      // ✅ Vue2 响应式的数组变异方法
      // push / pop / shift / unshift / splice / sort / reverse
      this.arr.push(4)
      this.arr.splice(1, 1)
    }
  }
}

插槽(Slot)

<!-- 子组件 Card.vue -->
<template>
  <div class="card">
    <!-- 默认插槽 -->
    <slot></slot>

    <!-- 具名插槽 -->
    <slot name="header"></slot>
    <slot name="footer"></slot>

    <!-- 作用域插槽(将子组件数据暴露给父组件) -->
    <slot name="item" :row="currentRow" :index="currentIndex"></slot>
  </div>
</template>

<!-- 父组件使用 -->
<template>
  <Card>
    <!-- 默认插槽 -->
    <p>卡片内容</p>

    <!-- 具名插槽(v-slot 简写 #) -->
    <template #header>
      <h2>卡片标题</h2>
    </template>

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

    <!-- 作用域插槽:接收子组件暴露的数据 -->
    <template #item="{ row, index }">
      <span>{{ index }}: {{ row.name }}</span>
    </template>
  </Card>
</template>

常见开发场景

1. 发送 HTTP 请求(axios)

// main.js 全局挂载 axios
import axios from 'axios'
Vue.prototype.$axios = axios

// 组件中使用
export default {
  data() {
    return { list: [], loading: false }
  },
  async created() {
    this.loading = true
    try {
      const res = await this.$axios.get('/api/list', {
        params: { page: 1, size: 10 }
      })
      this.list = res.data.list
    } catch (err) {
      console.error(err)
    } finally {
      this.loading = false
    }
  }
}

2. 表单处理

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="form.username" placeholder="用户名" />
    <input v-model="form.password" type="password" placeholder="密码" />
    <button type="submit" :disabled="loading">
      {{ loading ? '登录中...' : '登录' }}
    </button>
  </form>
</template>

<script>
export default {
  data() {
    return {
      loading: false,
      form: { username: '', password: '' }
    }
  },
  methods: {
    async handleSubmit() {
      this.loading = true
      try {
        const res = await this.$axios.post('/api/login', this.form)
        this.$router.push('/dashboard')
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

3. 路由跳转

// 编程式导航
this.$router.push('/home')
this.$router.push({ name: 'UserDetail', params: { id: 1 } })
this.$router.push({ path: '/search', query: { keyword: 'vue' } })
this.$router.replace('/login')
this.$router.go(-1)

// 获取路由信息
this.$route.params.id       // 动态参数
this.$route.query.keyword   // 查询参数
this.$route.path            // 当前路径

4. 查询数据 & 渲染表格

<template>
  <div>
    <input v-model="query.keyword" placeholder="关键字" />
    <button @click="handleSearch">查询</button>

    <table v-if="!loading">
      <tr v-for="row in tableData" :key="row.id">
        <td>{{ row.name }}</td>
        <td>{{ row.status === 1 ? '启用' : '禁用' }}</td>
        <td>
          <button @click="handleEdit(row)">编辑</button>
          <button @click="handleDelete(row.id)">删除</button>
        </td>
      </tr>
    </table>
    <p v-else>加载中...</p>

    <div>
      <button :disabled="page <= 1" @click="changePage(page - 1)">上一页</button>
      <span>第 {{ page }} / {{ totalPages }} 页</span>
      <button :disabled="page >= totalPages" @click="changePage(page + 1)">下一页</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      query: { keyword: '' },
      tableData: [],
      loading: false,
      page: 1,
      pageSize: 10,
      total: 0,
    }
  },
  computed: {
    totalPages() {
      return Math.ceil(this.total / this.pageSize)
    }
  },
  created() {
    this.loadData()
  },
  methods: {
    async loadData() {
      this.loading = true
      try {
        const res = await this.$axios.get('/api/list', {
          params: { ...this.query, page: this.page, pageSize: this.pageSize }
        })
        this.tableData = res.data.list
        this.total = res.data.total
      } finally {
        this.loading = false
      }
    },
    handleSearch() {
      this.page = 1
      this.loadData()
    },
    changePage(page) {
      this.page = page
      this.loadData()
    },
    handleEdit(row) {
      // 打开弹窗
    },
    async handleDelete(id) {
      if (!confirm('确认删除?')) return
      await this.$axios.delete(`/api/item/${id}`)
      this.loadData()
    }
  }
}
</script>

5. 弹窗 & 获取返回参数

<!-- 父组件 -->
<template>
  <div>
    <button @click="openEdit(row)">编辑</button>
    <EditDialog
      v-if="dialogVisible"
      :visible.sync="dialogVisible"
      :init-data="dialogData"
      @confirm="handleConfirm"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      dialogVisible: false,
      dialogData: null,
    }
  },
  methods: {
    openEdit(row) {
      this.dialogData = { ...row }
      this.dialogVisible = true
    },
    async handleConfirm(formData) {
      await this.$axios.put(`/api/item/${formData.id}`, formData)
      this.dialogVisible = false
      this.loadData()
    }
  }
}
</script>
<!-- 子组件 EditDialog.vue -->
<template>
  <div class="dialog" v-if="visible">
    <input v-model="form.name" />
    <button @click="handleConfirm">确认</button>
    <!-- .sync 修饰符:子组件可通过 $emit('update:visible', false) 关闭弹窗 -->
    <button @click="$emit('update:visible', false)">取消</button>
  </div>
</template>

<script>
export default {
  props: {
    visible: Boolean,
    initData: Object,
  },
  data() {
    return {
      form: { name: '', id: null }
    }
  },
  watch: {
    visible(val) {
      if (val && this.initData) {
        this.form = { ...this.initData }
      }
    }
  },
  methods: {
    handleConfirm() {
      this.$emit('confirm', { ...this.form })
    }
  }
}
</script>

自定义指令

// 全局注册
Vue.directive('focus', {
  inserted(el) {        // 元素插入 DOM 后
    el.focus()
  }
})

// 局部注册
export default {
  directives: {
    color: {
      bind(el, binding) {           // 指令绑定到元素时
        el.style.color = binding.value
      },
      update(el, binding) {         // 组件更新时
        el.style.color = binding.value
      }
    }
  }
}
<input v-focus />
<p v-color="'red'">红色文字</p>

过滤器(Vue2 特有,Vue3 已移除)

// 全局过滤器
Vue.filter('formatDate', (val) => {
  if (!val) return ''
  return new Date(val).toLocaleDateString()
})

Vue.filter('currency', (val, symbol = '¥') => {
  return `${symbol}${Number(val).toFixed(2)}`
})
<!-- 模板中使用管道符 -->
<p>{{ createTime | formatDate }}</p>
<p>{{ price | currency }}</p>
<p>{{ price | currency('$') }}</p>

混入(Mixin)

将可复用逻辑提取到 mixin,在多个组件中共享(Vue3 中用 composable 替代)。

// mixins/tableMixin.js
export const tableMixin = {
  data() {
    return {
      tableData: [],
      loading: false,
      page: 1,
      pageSize: 10,
      total: 0,
    }
  },
  computed: {
    totalPages() {
      return Math.ceil(this.total / this.pageSize)
    }
  },
  methods: {
    changePage(page) {
      this.page = page
      this.loadData()
    }
  }
}

// 组件中使用
import { tableMixin } from '@/mixins/tableMixin'
export default {
  mixins: [tableMixin],
  methods: {
    async loadData() {
      // 组件自己实现具体的请求逻辑
    }
  }
}

Mixin 的缺点:命名冲突、来源不清晰。Vue3 推荐用 Composition API 的 composable 替代。

Last Updated: 6/25/26, 9:43 AM
Contributors: dongyz8
Prev
概述
Next
VueRouter