Vue(发音 /vjuː/,类似 view)是一款用于构建用户界面的渐进式 JavaScript 框架。由尤雨溪(Evan You)创建,以其易上手、灵活、高性能著称。
- 🎯 渐进式 - 从库到框架,按需引入功能
- 📦 组件化 - 单文件组件(SFC)封装模板、脚本、样式
- ⚡ 响应式 - 基于 Proxy(Vue3)/ defineProperty(Vue2)的响应式系统
- 🧩 生态系统 - Vue Router、Pinia/Vuex、Nuxt 等官方生态
- 🪶 轻量级 - 核心库约 33KB(gzip)
| 特性 | Vue 2 | Vue 3 |
|---|
| 响应式 | Object.defineProperty | Proxy |
| API | Options API | Composition API + Options API |
| TypeScript | 有限支持 | 原生支持 |
| 状态管理 | Vuex 4 | Pinia(推荐) |
| 构建工具 | Vue CLI | Vite(推荐) |
| 状态 | 已停止更新 | 当前最新版本 |
推荐:新项目直接使用 Vue 3。
npm create vite@latest my-vue-app -- --template vue-ts
cd my-vue-app
npm install
npm run dev
npx nuxi@latest init my-app
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const count = ref(0);
return { count };
},
template: `<button @click="count++">{{ count }}</button>`
}).mount('#app');
</script>
<script setup lang="ts">
import { ref, computed } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
</script>
<template>
<button @click="increment">
Count: {{ count }} (doubled: {{ doubled }})
</button>
</template>
<style scoped>
button {
background: #42b883;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
</style>
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted } from 'vue';
const count = ref(0);
const state = reactive({
name: 'Vue',
version: 3,
});
const message = computed(() => `Hello ${state.name} v${state.version}`);
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`);
});
onMounted(() => {
console.log('Component mounted');
});
</script>
<template>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="count++">+1</button>
</template>
<script>
export default {
data() {
return {
count: 0,
message: 'Hello Vue',
};
},
computed: {
doubled() {
return this.count * 2;
},
},
watch: {
count(newVal) {
console.log('Count:', newVal);
},
},
methods: {
increment() {
this.count++;
},
},
mounted() {
console.log('Component mounted');
},
};
</script>
<template>
<p>{{ message }}</p>
<p>Count: {{ count }} (doubled: {{ doubled }})</p>
<button @click="increment">+1</button>
</template>
<template>
<p>{{ message }}</p>
<a :href="url">Link</a>
<img :src="imageUrl" :alt="altText" />
<button @click="handleClick">Click</button>
<form @submit.prevent="onSubmit">Submit</form>
<input v-model="username" />
<textarea v-model="bio" />
<input type="checkbox" v-model="agree" />
<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Other</div>
<div v-show="visible">Visible</div>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}. {{ item.name }}
</li>
<div :class="{ active: isActive, 'text-danger': hasError }">Dynamic class</div>
<div :class="[isActive ? 'active' : '', 'base-class']">Array class</div>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">Dynamic style</div>
</template>
<script setup lang="ts">
interface Props {
label: string;
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
disabled: false,
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
function handleClick(event: MouseEvent) {
emit('click', event);
}
</script>
<template>
<button
:class="[`btn-${variant}`, `btn-${size}`]"
:disabled="disabled"
@click="handleClick"
>
{{ label }}
</button>
</template>
<script setup lang="ts">
defineProps<{ title: string }>();
</script>
<template>
<div class="card">
<div class="card-header">
<h2>{{ title }}</h2>
</div>
<div class="card-body">
<slot />
</div>
<div class="card-footer">
<slot name="footer">
<p class="default-footer">Default footer</p>
</slot>
</div>
</div>
</template>
<Card title="My Card">
<p>This is the main content</p>
<template #footer>
<button>Read More</button>
</template>
</Card>
<script setup lang="ts">
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUnmount } from 'vue';
onBeforeMount(() => console.log('Before mount'));
onMounted(() => console.log('Mounted'));
onUpdated(() => console.log('Updated'));
onBeforeUnmount(() => console.log('Before unmount'));
onUnmounted(() => console.log('Unmounted'));
</script>
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubled, increment };
});
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter';
const store = useCounterStore();
</script>
<template>
<p>Count: {{ store.count }} (doubled: {{ store.doubled }})</p>
<button @click="store.increment">+1</button>
</template>
import { createStore } from 'vuex';
export default createStore({
state: { count: 0 },
getters: { doubled: state => state.count * 2 },
mutations: { increment(state) { state.count++ } },
actions: { asyncIncrement({ commit }) { setTimeout(() => commit('increment'), 1000) } },
});
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: () => import('@/views/About.vue') },
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue'),
children: [
{ path: 'profile', component: () => import('@/views/UserProfile.vue') },
{ path: 'posts', component: () => import('@/views/UserPosts.vue') },
],
},
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/NotFound.vue') },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
router.beforeEach((to, from) => {
const isAuthenticated = localStorage.getItem('token');
if (to.meta.requiresAuth && !isAuthenticated) {
return { name: 'Login', query: { redirect: to.fullPath } };
}
});
export default router;
import { ref, onMounted, onUnmounted } from 'vue';
export function useMouse() {
const x = ref(0);
const y = ref(0);
function update(event: MouseEvent) {
x.value = event.clientX;
y.value = event.clientY;
}
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
return { x, y };
}
<script setup lang="ts">
import { useMouse } from '@/composables/useMouse';
const { x, y } = useMouse();
</script>
<template>
<p>Mouse position: {{ x }}, {{ y }}</p>
</template>
| 文档 | 说明 |
|---|
| UI 组件库 | Vue/React 组件库推荐对比表 |
| 图标方案 | 图标网站、Iconfont、SVG 图标方案汇总 |
| 代码高亮 | highlight.js / Prism.js 使用 |
| 富文本编辑器 | 富文本编辑器推荐 |