Appearance
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 里要加 .value(count.value)。
响应式怎么理解?普通变量改了值,页面不会动;ref 变量改了值,Vue 自动重新渲染用到它的地方。这就是 Vue 跟原生 JS 操作 DOM 最大的区别——不用手动更新页面。
四、属性绑定
HTML 标签的属性(href、src、class、disabled)要绑定到变量,用 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-if 和 v-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-if 为 true 时渲染元素,为 false 时元素根本不存在于 DOM 中。v-else-if 和 v-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>@click 是 v-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 声明"我接收 title 和 status 两个字符串"。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 往上报事件。