一、组件基础
组件是可复用的界面单元,通常包含自己的模板、数据、方法、样式和生命周期。把页面拆成组件,可以降低单个文件的复杂度,也便于多人协作和后续维护。
组件的 data 必须是一个返回对象的函数。这样每次创建组件实例时都会得到独立的数据,避免多个实例共享同一个对象。
二、全局组件与局部组件
1. 全局组件
全局组件通过 Vue.component() 注册,注册后当前页面中的所有 Vue 实例和子组件都可以使用。全局组件适合按钮、图标等通用程度很高的组件,但数量过多会增加命名冲突和维护成本。
html
<div id="app">
<child-card></child-card>
</div>
<script src="./vue2/vue.js"></script>
<script>
Vue.component('child-card', {
template: `
<section>
<h2>{{ name }}</h2>
<button @click="showMessage">显示提示</button>
</section>
`,
data() {
return { name: '子组件' }
},
methods: {
showMessage() {
alert('组件按钮被点击了')
}
}
})
new Vue({ el: '#app' })
</script>
2. 局部组件
局部组件只在注册它的父组件模板中可用,更适合业务页面中的专用组件。推荐把组件对象单独放在 .vue 文件中,再通过 import 引入。
html
<div id="app">
<photo-card></photo-card>
</div>
<script src="./vue2/vue.js"></script>
<script>
const PhotoCard = {
template: `
<div>
<img :src="image" alt="示例图片" width="200">
<button @click="changeImage">切换图片</button>
</div>
`,
data() {
return { image: './img/1.png' }
},
methods: {
changeImage() {
this.image = './img/2.png'
}
}
}
new Vue({
el: '#app',
components: { PhotoCard }
})
</script>
三、动态组件与 keep-alive
动态组件使用 <component :is="..."> 根据变量切换组件,适合选项卡、不同商品分类和多步骤表单。相比堆叠多个 v-if,动态组件能让切换逻辑更集中。
html
<div id="app">
<button @click="current = 'fresh-panel'">生鲜</button>
<button @click="current = 'seafood-panel'">水产</button>
<button @click="current = 'meat-panel'">肉类</button>
<component :is="current"></component>
</div>
<script>
Vue.component('fresh-panel', { template: '<h3>生鲜商品</h3>' })
Vue.component('seafood-panel', { template: '<h3>水产商品</h3>' })
Vue.component('meat-panel', { template: '<h3>肉类商品</h3>' })
new Vue({
el: '#app',
data: { current: 'fresh-panel' }
})
</script>
默认切换组件时,旧组件可能被销毁,输入框内容和局部状态也会丢失。keep-alive 会缓存动态组件实例,使组件切换回来时保留状态。
html
<keep-alive>
<component :is="current"></component>
</keep-alive>
被缓存组件可以使用 activated 和 deactivated 处理激活、停用逻辑。缓存不是越多越好,页面数量较多时要结合 include、exclude 或 max 控制缓存范围。
四、插槽 slot
插槽用于让父组件向子组件传入模板内容。子组件负责统一布局,父组件决定具体内容,因此很适合封装卡片、弹窗和页面布局。
1. 默认插槽
html
<div id="app">
<base-card>
<p>这是父组件传入的正文。</p>
</base-card>
</div>
<script>
Vue.component('base-card', {
template: `
<section class="card">
<h2>通用卡片</h2>
<slot>没有传入内容时显示的默认文本</slot>
</section>
`
})
new Vue({ el: '#app' })
</script>
父组件写在 <base-card> 标签内部的内容会渲染到子组件的 <slot> 位置。没有传入内容时,slot 标签内部的文本会作为默认内容。
2. 具名插槽
一个组件需要多个内容区域时,可以使用具名插槽。Vue 2 中父组件使用 slot="名称",子组件用 <slot name="名称"> 接收。
html
<base-layout>
<template slot="header"><h2>文章标题</h2></template>
<p>文章正文内容。</p>
<template slot="footer"><button>保存</button></template>
</base-layout>
<script>
Vue.component('base-layout', {
template: `
<div class="layout">
<header><slot name="header"></slot></header>
<main><slot></slot></main>
<footer><slot name="footer"></slot></footer>
</div>
`
})
</script>
Vue 3 使用 v-slot 或 #header 的写法。项目升级时应按照对应版本的插槽语法编写,避免混用。
五、创建 Vue 项目
Vue 2 常使用 Vue CLI 创建项目,Vue 3 新项目通常使用 Vite。两者都依赖 Node.js 环境;Node.js 提供运行时,npm 负责安装和管理依赖。
bash
# 查看 Node.js 和 npm 是否安装成功
node -v
npm -v
# 可选:配置 npm 镜像源
npm config set registry https://registry.npmmirror.com
npm config get registry
# Vue CLI(主要用于 Vue 2 或旧项目)
npm install -g @vue/cli
vue create vue-demo
# 进入项目并启动开发服务器
cd vue-demo
npm run serve
包管理器的核心职责是根据 package.json 安装依赖。不要把 node_modules 提交到 Git;把项目交给其他人时,执行 npm install 即可依据锁定文件重新安装依赖。
六、Vue 项目目录结构
典型 Vue CLI 项目结构如下:
text
vue-demo/
├─ public/
│ └─ index.html # SPA 的 HTML 入口
├─ src/
│ ├─ assets/ # 需要经过构建处理的图片、样式
│ ├─ components/ # 可复用的小组件
│ ├─ views/ # 页面级组件
│ ├─ router/ # 路由配置
│ ├─ store/ # 状态管理配置
│ ├─ App.vue # 根组件
│ └─ main.js # 应用启动入口
├─ package.json # 项目脚本和依赖声明
├─ package-lock.json # 依赖版本锁定文件
└─ vue.config.js # Vue CLI 构建配置(可选)
public/index.html 通常只保留挂载点,Vue 会把根组件渲染到 #app。业务代码主要写在 src 中。assets 中的资源会被构建工具处理,直接放在 public 的资源则按原路径提供。
七、Vue 单文件组件规范
.vue 文件通常由 <template>、<script> 和 <style> 三部分组成。scoped 会让样式只作用于当前组件生成的节点,避免普通类名污染其他组件。
vue
<template>
<header class="top-bar">
<button @click="goBack">后退</button>
<span>{{ title }}</span>
<button @click="goForward">前进</button>
</header>
</template>
<script>
export default {
name: 'TopBar',
data() {
return { title: '首页' }
},
methods: {
goBack() {
window.history.back()
},
goForward() {
window.history.forward()
}
}
}
</script>
<style scoped>
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
在其他组件中使用时,需要先导入,再注册,最后在模板中使用:
vue
<template>
<div id="app">
<TopBar />
</div>
</template>
<script>
import TopBar from '@/components/TopBar.vue'
export default {
name: 'App',
components: { TopBar }
}
</script>
@ 通常是构建工具配置的 src 别名。组件名推荐使用多单词形式,避免和原生 HTML 标签冲突。
八、路由与页面组件
使用 Vue Router 后,URL 和页面组件的对应关系由路由表决定。App.vue 一般只保留布局和 <router-view />,当前路由匹配的页面会渲染到这个位置。
js
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import AboutView from '@/views/AboutView.vue'
Vue.use(VueRouter)
export default new VueRouter({
mode: 'history',
routes: [
{ path: '/', name: 'home', component: HomeView },
{ path: '/about', name: 'about', component: AboutView }
]
})
vue
<!-- App.vue -->
<template>
<div id="app">
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view />
</div>
</template>
创建新页面时,通常在 views 中新建页面组件,再在路由配置中注册路径。组件内部的可复用部分放入 components,不要把所有代码都堆在页面组件中。
九、ES6 常用语法
1. let、const 与块级作用域
let 声明可重新赋值的块级变量,const 声明不能重新赋值的绑定。对象用 const 声明后,仍然可以修改对象内部属性;不能做的是给变量重新绑定另一个对象。
js
let count = 1
count = 2
const user = { name: 'lqz' }
user.name = 'new name' // 可以修改属性
// user = {} // 不允许重新赋值
if (true) {
let inside = '只在代码块内有效'
const fixed = 1
}
// console.log(inside) // ReferenceError
var 具有函数作用域和变量提升,容易造成意外覆盖。现代项目通常优先使用 const,确实需要重新赋值时使用 let。
2. 模板字符串
反引号字符串支持换行和插值,适合拼接提示文本或组件模板:
js
const name = 'lqz'
const message = `你好,${name}!`
console.log(message)
3. 解构赋值
解构可以从对象或数组中提取值,接口返回数据和函数参数处理中经常使用。
js
const user = { name: 'lqz', age: 19 }
const { name, age, city = '未知' } = user
const numbers = [11, 22, 33]
const [first, second] = numbers
function getUser() {
return { username: 'tom', role: 'admin' }
}
const { username, role } = getUser()
console.log(name, age, city, first, second, username, role)
缺少的对象属性会得到 undefined,可以在解构时使用 = 提供默认值。
4. 默认参数与展开运算符
js
function greet(name = '访客') {
return `你好,${name}`
}
const base = { age: 19, hobby: '阅读' }
const user = { name: 'lqz', ...base }
const first = [1, 2]
const all = [0, ...first, 3]
function collect(firstValue, ...rest) {
return { firstValue, rest }
}
console.log(greet())
console.log(user, all, collect(...[10, 20, 30]))
对象展开通常用于浅拷贝和合并配置;嵌套对象仍然共享引用,需要深拷贝时应使用专门方案。
十、ES6 模块化
模块通过 export 暴露内容,通过 import 引入内容。默认导出一个模块只能有一个,命名导出可以有多个。
js
// utils.js:默认导出
const appName = 'Vue Demo'
function add(a, b) {
return a + b
}
export default { appName, add }
js
// 使用默认导出
import utils from './utils.js'
console.log(utils.appName)
console.log(utils.add(3, 4))
js
// utils.js:命名导出
export const version = '1.0.0'
export function subtract(a, b) {
return a - b
}
js
import { version, subtract as minus } from './utils.js'
console.log(version, minus(8, 3))
如果目录下存在 index.js,导入时通常可以只写目录路径。模块化的价值在于拆分职责、避免全局变量污染,并使依赖关系在文件顶部清晰可见。
十一、总结与练习
- 全局组件使用方便但容易产生命名和维护问题,业务组件优先局部注册。
- 动态组件负责切换,
keep-alive负责缓存;插槽负责让父组件注入可变内容。 - Vue 项目通过
main.js启动,页面组件放在views,可复用组件放在components,路由配置集中在router。 let、const、解构、展开和模块化是 Vue 工程中高频使用的 ES6 语法。- 建议完成一个带选项卡、可缓存表单、具名插槽卡片和路由页面的综合练习,体会组件拆分和数据复用。