VUE

分类: 前端

Vue教程

目录

  1. Vue简介
  2. 环境搭建
  3. 模板语法
  4. 响应式系统
  5. 计算属性与监听器
  6. 条件渲染与列表渲染
  7. 事件处理
  8. 表单处理
  9. 组件基础
  10. 组件通信
  11. Vue Router路由
  12. Pinia状态管理
  13. 组合式API
  14. 生命周期
  15. 动画与过渡
  16. 最佳实践

一、Vue简介

1.1 什么是Vue

Vue.js是一个渐进式JavaScript框架,用于构建用户界面。Vue的核心库专注于视图层,不仅易于上手,还便于与第三方库或既有项目整合。Vue采用自底向上增量开发的设计,其核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

Vue由尤雨溪于2014年创建,随后迅速成为最受欢迎的前端框架之一。Vue的设计理念是"渐进式",意味着您可以根据需求逐步采用Vue的功能,从简单的组件替换到完整的单页应用都可以实现。Vue的中文文档非常完善,对中文开发者非常友好。

Vue的主要特点包括:响应式数据绑定、组件系统、指令系统、虚拟DOM、单文件组件等。这些特性使得Vue具有学习曲线平缓、文档友好、生态系统丰富等优势。

1.2 Vue核心概念

概念 说明
响应式数据 数据变化时自动更新视图
组件 可复用的Vue实例
指令 带有v-前缀的特殊属性
虚拟DOM 内存中的DOM表示
单文件组件 .vue文件包含模板、脚本、样式

1.3 Vue版本对比

版本 发布年份 主要特性
Vue 2 2016 响应式系统、组件、Vue CLI
Vue 3 2020 组合式API、Teleport、Fragments、性能提升

二、环境搭建

2.1 使用Vue CLI

Vue CLI是Vue.js官方提供的脚手架工具,用于快速搭建Vue项目。

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

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

# 进入目录
cd my-vue-app

# 启动开发服务器
npm run serve

Vue CLI提供了交互式的项目创建界面,可以选择预设配置或手动配置项目选项,包括TypeScript支持、CSS预处理器、路由、状态管理等功能。

2.2 使用Vite

Vite是新一代构建工具,为Vue提供了极快的开发体验。

# 使用Vite创建Vue项目
npm create vite@latest my-vue-app -- --template vue

# 进入目录
cd my-vue-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

Vite利用浏览器原生ES模块支持,实现了即时的服务器启动和热模块替换。相比Vue CLI,Vite在大型项目中具有明显的速度优势。

2.3 CDN引入

对于简单的项目,可以直接使用CDN引入Vue。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Vue CDN示例</title>
</head>
<body>
    <div id="app">
        <h1>{{ message }}</h1>
    </div>

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

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

三、模板语法

3.1 插值

Vue模板使用双花括号进行数据绑定,称为"Mustache"语法。

<!-- 文本插值 -->
<p>{{ message }}</p>

<!-- 原始HTML -->
<p v-html="rawHtml"></p>

<!-- 属性绑定 -->
<div v-bind:id="dynamicId"></div>
<!-- 简写 -->
<div :id="dynamicId"></div>

<!-- JavaScript表达式 -->
<p>{{ number + 1 }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<p>{{ ok ? 'Yes' : 'No' }}</p>

3.2 指令

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

指令 说明 示例
v-bind 绑定属性 <img :src="../media/imageUrl">
v-on 绑定事件 <button @click="handleClick">
v-if 条件渲染 <div v-if="show">
v-else 条件else <div v-else>
v-for 列表渲染 <li v-for="item in items">
v-model 双向绑定 <input v-model="text">
v-show 显示隐藏 <div v-show="show">
v-once 一次性渲染 <span v-once>{{ msg }}</span>
<!-- 条件渲染 -->
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>其他</div>

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

<!-- 对象遍历 -->
<div v-for="(value, key, index) in object">
    {{ index }}. {{ key }}: {{ value }}
</div>

四、响应式系统

4.1 data选项

data选项用于声明组件的响应式状态。

const app = {
    data() {
        return {
            message: "Hello Vue!",
            user: {
                name: "张三",
                age: 25
            },
            items: ["a", "b", "c"]
        };
    }
};

Vue会递归遍历data中的所有属性,将其转换为响应式的。当这些属性变化时,视图会自动更新。

4.2 响应式原理

Vue 3使用Proxy实现响应式系统,相比Vue 2的Object.defineProperty有更好的性能和支持。

import { reactive, ref } from "vue";

// reactive用于对象
const state = reactive({
    count: 0,
    user: { name: "张三" }
});

// ref用于基本类型
const count = ref(0);

// 访问和修改
console.log(state.count);
state.count++;

console.log(count.value);
count.value++;

4.3 响应式API

API 用途
reactive 创建响应式对象
ref 创建响应式引用
computed 创建计算属性
watch 监听响应式数据变化
toRefs 将响应式对象转换为ref
import { reactive, ref, computed, watch, toRefs } from "vue";

export default {
    setup() {
        // reactive对象
        const state = reactive({
            name: "张三",
            age: 25
        });

        // ref引用
        const count = ref(0);

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

        // watch监听
        watch(count, (newVal, oldVal) => {
            console.log(`count从${oldVal}变为${newVal}`);
        });

        // toRefs转换
        const { name, age } = toRefs(state);

        return {
            state,
            count,
            doubleCount,
            name,
            age
        };
    }
};

五、计算属性与监听器

5.1 计算属性

计算属性是基于现有响应式数据计算得出的新数据,具有缓存特性。

export default {
    data() {
        return {
            firstName: "张",
            lastName: "三",
            items: [
                { name: "苹果", price: 10 },
                { name: "香蕉", price: 5 },
                { name: "橙子", price: 8 }
            ]
        };
    },
    computed: {
        // 简单计算属性
        fullName() {
            return this.firstName + this.lastName;
        },

        // getter和setter
        fullName: {
            get() {
                return this.firstName + " " + this.lastName;
            },
            set(newValue) {
                const names = newValue.split(" ");
                this.firstName = names[0];
                this.lastName = names[1];
            }
        },

        // 基于列表计算
        totalPrice() {
            return this.items.reduce((sum, item) => sum + item.price, 0);
        }
    }
};

计算属性会自动缓存结果,只有当依赖的响应式数据变化时才会重新计算。这避免了不必要的计算,提高性能。

5.2 监听器

watch选项用于监听数据变化并执行相应的操作。

export default {
    data() {
        return {
            question: "",
            answer: ""
        };
    },
    watch: {
        // 监听单个数据
        question(newQuestion, oldQuestion) {
            if (newQuestion.includes("?")) {
                this.fetchAnswer();
            }
        },

        // 监听对象
        "user.name"(newName, oldName) {
            console.log(`用户名从${oldName}改为${newName}`);
        },

        // 深度监听
        options: {
            handler(newOptions) {
                console.log("选项变化:", newOptions);
            },
            deep: true
        },

        // 立即执行
        name: {
            handler(newName) {
                console.log("立即执行:", newName);
            },
            immediate: true
        }
    },
    methods: {
        fetchAnswer() {
            this.answer = "加载中...";
            // 模拟API调用
            setTimeout(() => {
                this.answer = "这是答案";
            }, 500);
        }
    }
};

六、条件渲染与列表渲染

6.1 条件渲染

v-if和v-show都用于条件渲染,但实现方式不同。

<!-- v-if:条件性地渲染元素,条件为false时不渲染 -->
<div v-if="show">内容1</div>
<div v-else-if="show2">内容2</div>
<div v-else>内容3</div>

<!-- v-show:始终渲染,通过display控制显示隐藏 -->
<div v-show="show">内容</div>

<!-- v-if vs v-show -->
<!-- v-if: 切换时销毁/重建元素,开销大,初始条件false不渲染 -->
<!-- v-show: 始终渲染,切换时只改display,开销小 -->
export default {
    data() {
        return {
            type: "A",
            show: true
        };
    },
    methods: {
        toggle() {
            this.show = !this.show;
        }
    }
};

6.2 列表渲染

v-for用于渲染列表,支持遍历数组、对象、整数等。

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

<!-- 遍历对象 -->
<div v-for="(value, key, index) in object" :key="key">
    {{ index }}. {{ key }}: {{ value }}
</div>

<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }}</span>

<!-- v-for with template -->
<template v-for="item in items">
    <li>{{ item.name }}</li>
    <li class="divider" role="presentation"></li>
</template>

七、事件处理

7.1 事件绑定

<!-- 方法处理器 -->
<button @click="greet">打招呼</button>

<!-- 访问原生事件对象 -->
<button @click="handleClick">点击</button>
<button @click="handleClick($event)">点击并传参</button>

<!-- 多个事件 -->
<button @click="one($event), two($event)">多事件</button>

<!-- 事件修饰符 -->
<button @click.stop="handleClick">阻止冒泡</button>
<button @click.prevent="handleSubmit">阻止默认</button>
<button @click.stop.prevent="handleBoth">阻止冒泡和默认</button>
<button @click.once="handleOnce">只触发一次</button>
<button @click.self="handleSelf">仅自身触发</button>

<!-- 键盘事件 -->
<input @keyup.enter="submit" />
<input @keyup.enter.exact="submitExact" />

7.2 事件修饰符详解

修饰符 说明
.stop 阻止事件冒泡
.prevent 阻止默认行为
.capture 使用事件捕获模式
.self 只有event.target是自身时才触发
.once 事件只触发一次
.passive 监听器使用被动模式
export default {
    methods: {
        greet(event) {
            console.log("Hello!", event);
        },
        handleClick(event) {
            console.log("点击了", event.target);
        },
        handleSubmit(event) {
            console.log("表单提交");
        },
        submit(event) {
            if (event.key === "Enter") {
                console.log("提交表单");
            }
        }
    }
};

八、表单处理

8.1 v-model双向绑定

v-model用于创建表单元素与数据的双向绑定。

<!-- 文本输入 -->
<input v-model="message">
<p>输入的内容:{{ message }}</p>

<!-- 多行文本 -->
<textarea v-model="message"></textarea>

<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked ? "已勾选" : "未勾选" }}">

<!-- 多个复选框 -->
<input type="checkbox" value="苹果" v-model="fruits">
<input type="checkbox" value="香蕉" v-model="fruits">
<input type="checkbox" value="橙子" v-model="fruits">
<p>选择的水果:{{ fruits }}</p>

<!-- 单选按钮 -->
<input type="radio" value="A" v-model="choice">
<input type="radio" value="B" v-model="choice">
<p>选择:{{ choice }}</p>

<!-- 下拉选择 -->
<select v-model="selected">
    <option value="">请选择</option>
    <option value="a">选项A</option>
    <option value="b">选项B</option>
</select>

8.2 v-model修饰符

修饰符 说明
.lazy 改为change事件同步
.number 自动转换为数字
.trim 自动去除首尾空格
<!-- lazy: 失去焦点时同步 -->
<input v-model.lazy="message">

<!-- number: 自动转数字 -->
<input v-model.number="age" type="number">

<!-- trim: 去除空格 -->
<input v-model.trim="username">
export default {
    data() {
        return {
            message: "",
            age: 0,
            username: "",
            checked: false,
            fruits: [],
            choice: "A",
            selected: ""
        };
    }
};

九、组件基础

9.1 组件注册

组件是可复用的Vue实例。

// 全局注册
import { createApp } from "vue";
import MyComponent from "./MyComponent.vue";

const app = createApp({});
app.component("MyComponent", MyComponent);

// 局部注册(在另一个组件中)
import MyComponent from "./MyComponent.vue";

export default {
    components: {
        MyComponent
    }
};

9.2 单文件组件

单文件组件(.vue文件)将模板、脚本和样式封装在一个文件中。

<!-- MyComponent.vue -->
<template>
    <div class="my-component">
        <h2>{{ title }}</h2>
        <p>{{ message }}</p>
        <button @click="handleClick">点击</button>
    </div>
</template>

<script>
export default {
    name: "MyComponent",
    props: {
        title: {
            type: String,
            default: "默认标题"
        }
    },
    data() {
        return {
            message: "Hello Vue!"
        };
    },
    methods: {
        handleClick() {
            this.$emit("click", "数据");
        }
    }
};
</script>

<style scoped>
.my-component {
    padding: 20px;
    background: #f0f0f0;
}

h2 {
    color: #333;
}
</style>

9.3 组件使用

<template>
    <div>
        <MyComponent
            title="自定义标题"
            @click="handleData"
        />
    </div>
</template>

<script>
import MyComponent from "./MyComponent.vue";

export default {
    components: {
        MyComponent
    },
    methods: {
        handleData(data) {
            console.log("收到子组件数据:", data);
        }
    }
};
</script>

十、组件通信

10.1 Props与Emit

父子组件通信的基本方式。

// 父组件
<template>
    <ChildComponent
        :message="parentMessage"
        :count="parentCount"
        @update="handleUpdate"
    />
</template>

<script>
export default {
    data() {
        return {
            parentMessage: "来自父组件",
            parentCount: 0
        };
    },
    methods: {
        handleUpdate(newValue) {
            this.parentCount = newValue;
        }
    }
};
</script>

// 子组件
<script>
export default {
    name: "ChildComponent",
    props: {
        message: String,
        count: {
            type: Number,
            required: true,
            default: 0
        }
    },
    emits: ["update"],
    methods: {
        sendToParent() {
            this.$emit("update", this.count + 1);
        }
    }
};
</script>

10.2 Provide与Inject

跨层级组件通信。

// 祖先组件
export default {
    provide() {
        return {
            message: "祖先提供的数据",
            user: reactive({ name: "张三" }),
            updateMessage: (newValue) => {
                this.message = newValue;
            }
        };
    }
};

// 后代组件
export default {
    inject: ["message", "user", "updateMessage"],
    mounted() {
        console.log(this.message);
        this.updateMessage("新消息");
    }
};

10.3 组件通信方式对比

方式 通信层级 说明
Props/Emit 父子 最常用
$refs 父访问子 直接调用子组件方法
$parent/$children 父子 不推荐
Provide/Inject 祖先与后代 跨层级
EventBus 任意 全局事件
Vuex/Pinia 任意 全局状态

十一、Vue Router路由

11.1 基本使用

# 安装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 },
    { path: "/user/:id", name: "User", component: User },
    { path: "/:pathMatch(.*)*", redirect: "/" } // 404重定向
];

const router = createRouter({
    history: createWebHistory(),
    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");

11.2 路由导航

<!-- 声明式导航 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link :to="{ name: 'User', params: { id: 123 }}">用户</router-link>

<!-- 编程式导航 -->
<button @click="$router.push('/about')">跳转</button>
<button @click="$router.replace('/about')">替换</button>
<button @click="$router.go(-1)">后退</button>
<button @click="$router.forward()">前进</button>

11.3 路由守卫

const router = createRouter({ ... });

// 全局前置守卫
router.beforeEach((to, from, next) => {
    // to: 目标路由
    // from: 来源路由
    // next: 放行函数
    if (to.meta.requiresAuth && !isAuthenticated) {
        next("/login");
    } else {
        next();
    }
});

// 全局后置钩子
router.afterEach((to, from) => {
    console.log("导航完成");
});

// 路由独享守卫
{
    path: "/admin",
    component: Admin,
    beforeEnter: (to, from, next) => {
        next();
    }
}

// 组件内守卫
export default {
    beforeRouteEnter(to, from, next) {
        // 不能访问this
        next(vm => {
            console.log(vm);
        });
    },
    beforeRouteUpdate(to, from, next) {
        // 可以访问this
        next();
    },
    beforeRouteLeave(to, from, next) {
        next();
    }
};

十二、Pinia状态管理

12.1 Pinia基础

Pinia是Vue 3推荐的状态管理库,API设计直观。

# 安装Pinia
npm install pinia
// stores/counter.js
import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", {
    state: () => ({
        count: 0,
        name: "张三"
    }),
    getters: {
        doubleCount: (state) => state.count * 2,
        formattedName: (state) => `Hello, ${state.name}!`
    },
    actions: {
        increment() {
            this.count++;
        },
        incrementAsync() {
            setTimeout(() => {
                this.count++;
            }, 1000);
        }
    }
});
// main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const pinia = createPinia();
const app = createApp(App);

app.use(pinia);
app.mount("#app");

12.2 组件中使用Pinia

import { useCounterStore } from "@/stores/counter";

export default {
    setup() {
        const counterStore = useCounterStore();

        // 访问state
        console.log(counterStore.count);

        // 访问getters
        console.log(counterStore.doubleCount);

        // 调用actions
        const increment = () => {
            counterStore.increment();
        };

        return {
            count: counterStore.count,
            doubleCount: counterStore.doubleCount,
            increment
        };
    }
};

十三、组合式API

13.1 setup函数

setup是Vue 3新增的组件选项,是组合式API的入口。

export default {
    setup() {
        // 定义响应式状态
        const count = ref(0);
        const user = reactive({
            name: "张三",
            age: 25
        });

        // 定义方法
        const increment = () => {
            count.value++;
        };

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

        // 生命周期钩子
        onMounted(() => {
            console.log("组件挂载");
        });

        // 返回模板中使用的变量和方法
        return {
            count,
            user,
            increment,
            doubleCount
        };
    }
};

13.2 常用组合式API

API 说明
ref 创建响应式引用
reactive 创建响应式对象
computed 创建计算属性
watch 监听数据变化
watchEffect 立即执行的监听
onMounted 挂载完成钩子
onUpdated 更新完成钩子
onUnmounted 卸载完成钩子
provide/inject 依赖注入
toRefs 转换为ref
import { ref, reactive, computed, watch, watchEffect, onMounted } from "vue";

export default {
    setup() {
        // ref - 基本类型
        const count = ref(0);

        // reactive - 对象
        const state = reactive({
            name: "张三",
            age: 25
        });

        // computed
        const adult = computed(() => state.age >= 18);

        // watch
        watch(count, (newVal, oldVal) => {
            console.log(`count从${oldVal}变为${newVal}`);
        });

        // watchEffect - 立即执行,自动追踪依赖
        watchEffect(() => {
            console.log("name变化:", state.name);
        });

        // 生命周期
        onMounted(() => {
            console.log("挂载完成");
        });

        return {
            count,
            state,
            adult
        };
    }
};

十四、生命周期

14.1 Vue 3生命周期

钩子 说明
setup 组件创建前
onBeforeMount 挂载前
onMounted 挂载完成
onBeforeUpdate 更新前
onUpdated 更新完成
onBeforeUnmount 卸载前
onUnmounted 卸载完成
onErrorCaptured 错误捕获

14.2 对比Vue 2

Vue 2 Vue 3
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured

十五、动画与过渡

15.1 Transition组件

Vue提供了Transition组件用于动画效果。

<template>
    <div>
        <button @click="show = !show">切换</button>

        <transition name="fade">
            <div v-if="show">内容</div>
        </transition>
    </div>
</template>

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

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

15.2 列表过渡

<transition-group name="list" tag="ul">
    <li v-for="(item, index) in items" :key="item.id">
        {{ item.name }}
    </li>
</transition-group>

<style>
.list-enter-active,
.list-leave-active {
    transition: all 0.5s;
}

.list-enter-from,
.list-leave-to {
    opacity: 0;
    transform: translateX(30px);
}

.list-move {
    transition: transform 0.5s;
}
</style>

15.3 JavaScript钩子

<transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @enter-cancelled="enterCancelled"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    @leave-cancelled="leaveCancelled"
>
    <div v-if="show">内容</div>
</transition>

<script>
export default {
    methods: {
        beforeEnter(el) {
            el.style.opacity = 0;
        },
        enter(el, done) {
            el.offsetHeight; // 触发重绘
            el.style.transition = "opacity 0.5s";
            el.style.opacity = 1;
            done();
        },
        leave(el, done) {
            el.style.transition = "opacity 0.5s";
            el.style.opacity = 0;
            done();
        }
    }
};
</script>

十六、最佳实践

16.1 组件设计

原则 说明
单一职责 每个组件只负责一个功能
合理拆分 根据功能模块拆分组件
命名规范 使用有意义的命名
Props验证 声明Props类型和默认值

16.2 性能优化

// 1. 使用v-show代替v-if频繁切换的元素
<div v-show="show">频繁切换</div>

// 2. 使用computed缓存
computed: {
    filteredItems() {
        return this.items.filter(item => item.active);
    }
}

// 3. 使用v-memo优化列表
<div v-for="item in items" :key="item.id" v-memo="[item.selected]">
    {{ item.content }}
</div>

// 4. 路由懒加载
{
    path: "/admin",
    component: () => import("../views/Admin.vue")
}

// 5. 大列表使用虚拟滚动
// vue-virtual-scroller库

16.3 代码组织

// 推荐:使用组合式API组织代码
export default {
    setup() {
        // 相关状态和方法放在一起
        const searchState = reactive({
            keyword: "",
            results: []
        });

        const search = async () => {
            searchState.results = await fetchData(searchState.keyword);
        };

        // 另一个功能模块
        const userState = reactive({
            user: null,
            isLoggedIn: false
        });

        const login = async () => { /* ... */ };

        return {
            ...toRefs(searchState),
            search,
            ...toRefs(userState),
            login
        };
    }
};

总结

本教程系统介绍了Vue.js开发的各个方面,从基础概念到高级应用。主要内容包括:Vue核心概念和响应式系统、模板语法和指令系统、计算属性和监听器、条件渲染和列表渲染、事件处理和表单操作、组件开发和通信、Vue Router路由配置、Pinia状态管理、组合式API、生命周期钩子、动画与过渡效果,以及性能优化技巧。Vue采用渐进式的设计理念,学习曲线平缓,文档完善,对中文开发者非常友好。掌握这些知识后,您可以独立开发功能完整的Vue应用。


本文档为Vue教程,适合Vue初学者和有一定前端基础的开发者学习参考。