Skip to content

Vue 基础

前几篇用原生 JavaScript 操作 DOM——查元素、改文本、绑事件、遍历数据生成列表。页面逻辑少的时候没问题,状态一多就乱了:手动维护"列表数据""选中状态""加载中标志"这些变量,每次数据变了都要手动找到对应 DOM 更新,忘了一处页面就跟数据对不上。

Vue 把这个过程自动化了——数据变了页面自动更新,不用手动操作 DOM。这就是"数据驱动":你只管改数据,Vue 负责把页面刷新到对应状态。

一、创建项目

Vue 官方推荐用 Vite 创建工程:

bash
npm create vite@latest article-app -- --template vue
cd article-app
npm install
npm run dev

启动后浏览器打开 http://localhost:5173 就能看到默认页面。开发时改代码页面自动刷新,不用手动 Ctrl+R。

目录结构:

text
article-app/
├── index.html       # 入口 HTML,Vite 自动注入 JS
├── package.json     # 依赖和脚本
├── src/
│   ├── App.vue      # 根组件
│   ├── main.js      # 入口 JS,挂载 Vue 到页面
│   └── components/  # 组件目录(自己建)
└── vite.config.js

二、组件长什么样

Vue 的基本单位是组件,一个 .vue 文件包含三块:template(HTML 结构)、script(JS 逻辑)、style(CSS)。

vue
<!-- src/components/ArticleCard.vue -->
<template>
    <div class="card">
        <h3>{{ title }}</h3>
        <p>{{ summary }}</p>
    </div>
</template>

<script setup>
// 组件的数据和逻辑放这
const title = "Python 入门";
const summary = "从变量到面向对象的完整基础";
</script>

<style scoped>
.card {
    border: 1px solid #ddd;
    padding: 16px;
    border-radius: 8px;
}
</style>

三个块的分工:

  • <template> 写 HTML,但里面的 是 Vue 的插值语法,能显示 script 里的变量
  • <script setup> 写 JS 逻辑,组件的数据和函数都在这
  • <style scoped> 写 CSS,scoped 表示样式只作用于当前组件,不污染别的组件

三、数据绑定

{{ }} 把变量显示在页面上,这是最基本的数据绑定。数据一变,页面自动更新:

vue
<template>
    <p>当前计数:{{ count }}</p>
    <p>文章标题:{{ title }}</p>
</template>

<script setup>
import { ref } from "vue";

// ref() 把普通值变成响应式的——值变了页面自动更新
const count = ref(0);
const title = ref("Python 入门");

// 2 秒后改值,页面上的数字会自动变
setTimeout(() => {
    count.value = 10;
}, 2000);
</script>

ref() 是 Vue 的核心函数,把一个值包装成"响应式的"。在 template 里直接写变量名(count),在 script 里要加 .valuecount.value)。

响应式怎么理解?普通变量改了值,页面不会动;ref 变量改了值,Vue 自动重新渲染用到它的地方。这就是 Vue 跟原生 JS 操作 DOM 最大的区别——不用手动更新页面。

四、属性绑定

HTML 标签的属性(hrefsrcclassdisabled)要绑定到变量,用 v-bind: 或简写 :

vue
<template>
    <a :href="articleUrl">阅读全文</a>
    <img :src="coverImage" :alt="title" />
    <button :disabled="isSubmitting">提交</button>
</template>

<script setup>
import { ref } from "vue";

const articleUrl = ref("https://example.com/articles/1");
const coverImage = ref("/images/cover.png");
const title = ref("Python 入门");
const isSubmitting = ref(false);
</script>

:href="articleUrl"articleUrl 的值绑到 href 属性上。articleUrl 变了,链接自动更新。

五、条件渲染

根据条件决定要不要显示某个元素,用 v-ifv-else

vue
<template>
    <div v-if="isLoading">加载中...</div>
    <div v-else-if="articles.length === 0">暂无文章</div>
    <div v-else>
        <p v-for="article in articles" :key="article.id">
            {{ article.title }}
        </p>
    </div>
</template>

<script setup>
import { ref } from "vue";

const isLoading = ref(true);
const articles = ref([]);

setTimeout(() => {
    isLoading.value = false;
    articles.value = [
        { id: 1, title: "Python 入门" },
        { id: 2, title: "Go 并发" },
    ];
}, 1000);
</script>

v-iftrue 时渲染元素,为 false 时元素根本不存在于 DOM 中。v-else-ifv-else 跟普通 if/else 逻辑一样。

六、列表渲染

遍历数组生成一组元素,用 v-for每个元素必须加 :key 绑定唯一标识(通常是数据的 id):

vue
<template>
    <ul>
        <li v-for="article in articles" :key="article.id">
            <span>{{ article.title }}</span>
            <span class="status">{{ article.status }}</span>
        </li>
    </ul>
</template>

<script setup>
import { ref } from "vue";

const articles = ref([
    { id: 1, title: "Python 入门", status: "已发布" },
    { id: 2, title: "Go 并发", status: "草稿" },
    { id: 3, title: "Rust 安全", status: "已发布" },
]);
</script>

:key 的作用是让 Vue 追踪每个列表项的身份——数据增删改时,Vue 靠 key 判断哪些元素变了、哪些没变,精准更新而不是整个列表重渲染。key 用唯一 id,不要用数组下标 index——下标会在增删时错位,导致渲染混乱。

七、事件处理

@ 监听 DOM 事件(点击、输入、提交等),绑定到 script 里的函数:

vue
<template>
    <button @click="count++">点击 +1</button>
    <p>当前:{{ count }}</p>

    <button @click="addArticle">添加文章</button>
</template>

<script setup>
import { ref } from "vue";

const count = ref(0);

const articles = ref([]);

function addArticle() {
    articles.value.push({
        id: Date.now(),
        title: `新文章 ${articles.value.length + 1}`,
    });
    console.log("当前文章数:", articles.value.length);
}
</script>

@clickv-on:click 的简写。可以直接写简单表达式(count++),也可以绑定到函数(addArticle)。

事件对象用 $event 传,或者在函数参数里接:

vue
<template>
    <input @input="onInput" placeholder="输入文字" />
    <p>你输入了:{{ text }}</p>
</template>

<script setup>
import { ref } from "vue";

const text = ref("");

function onInput(event) {
    text.value = event.target.value;  // event.target 是触发事件的 DOM 元素
}
</script>

event.target.value 是输入框当前的值。原生 JS 里操作 DOM 的那套,在 Vue 里变成:事件触发 → 改数据 → 页面自动更新

八、组件组合

实际页面不会只有一个组件。多个组件组合,通过 props 传数据、emit 触发事件。

父组件传数据给子组件用 props

vue
<!-- 父组件 App.vue -->
<template>
    <div>
        <ArticleCard
            v-for="article in articles"
            :key="article.id"
            :title="article.title"
            :status="article.status"
        />
    </div>
</template>

<script setup>
import { ref } from "vue";
import ArticleCard from "./components/ArticleCard.vue";

const articles = ref([
    { id: 1, title: "Python 入门", status: "已发布" },
    { id: 2, title: "Go 并发", status: "草稿" },
]);
</script>
vue
<!-- 子组件 ArticleCard.vue -->
<template>
    <div class="card">
        <h3>{{ title }}</h3>
        <span class="badge">{{ status }}</span>
    </div>
</template>

<script setup>
// 用 defineProps 声明接收哪些数据
defineProps({
    title: String,
    status: String,
});
</script>

父组件通过 :title="article.title" 把数据传下去,子组件用 defineProps 声明"我接收 titlestatus 两个字符串"。props 是单向的——父传子可以,子不能直接改父的数据

子组件要通知父组件,用 emit 触发事件:

vue
<!-- 子组件,点击时通知父 -->
<template>
    <div class="card" @click="handleClick">
        <h3>{{ title }}</h3>
    </div>
</template>

<script setup>
const emit = defineEmits(["select"]);

defineProps({ title: String });

function handleClick() {
    emit("select", title);  // 触发 select 事件,传 title 给父
}
</script>
vue
<!-- 父组件,监听 select 事件 -->
<template>
    <ArticleCard :title="article.title" @select="onSelect" />
</template>

<script setup>
function onSelect(title) {
    console.log("选中了:", title);
}
</script>

子组件 emit("select", title),父组件用 @select="onSelect" 监听。这就是 Vue 组件间通信的基础模式:props 往下传数据,emit 往上报事件。