🌱 搭建 Vue3 项目:一篇文章带你掌握实战演练
Vue3 + Vite + 组件化 + 响应式 + 表单绑定 + 计算属性 + Watch 全面上手!
🏗️ 创建项目脚手架
使用 vite 快速构建 Vue 项目:
npm create vite # 根据提示选择 Vue 技术栈即可
cd your-project-name
npm install
npm run dev # 启动项目
启动后,你的目录结构大致如下:
├─ .vscode/ # VSCode 配置
├─ public/ # 静态资源(如 favicon)
├─ src/ # 项目源码目录
├─ .gitignore # Git 忽略配置
├─ index.html # 入口 HTML 文件
├─ package.json # 项目依赖与脚本配置(像 Maven 的 pom.xml)
├─ README.md # 项目说明文档
└─ vite.config.js # Vite 配置文件
🧱 组件化开发基础
Vue 以组件为中心:
- 组件:独立、可复用的功能单元
- 组合:多个组件组合形成完整应用
组件本质是一棵 组件树,每个界面都是组件组合的结果。
Vue 的组件文件叫做 SFC(Single File Component) ,结构如下:
<script setup>
// JS逻辑区域
</script>
<template>
<!-- HTML模板 -->
</template>
<style scoped>
/* 样式,仅作用于当前组件 */
</style>
Vue工程
创建&运行
npm create vite # 按照提示选择Vue
npm run dev #项目运行命令
运行原理
🧪 Vue 基础功能实战
🔁 数据插值(双大括号)
<script setup>
let name = "张三"
let age = 18
let car = {
brand: "奔驰",
price: 777
}
</script>
<template>
<div>
<p>姓名: {{ name }}</p>
<p>年龄: {{ age }}</p>
<div style="border: 2px dashed red">
<p>品牌:{{ car.brand }}</p>
<p>价格:{{ car.price }}</p>
</div>
</div>
</template>
📜 指令 v-text / v-html
<script setup>
let msg = "<p style='color: red'>你好</p>"
</script>
<template>
<div v-html="msg"></div> <!-- 渲染 HTML -->
<div v-text="msg"></div> <!-- 显示纯文本 -->
</template>
🎯 事件绑定 @click
<script setup>
function buy() {
alert("购买成功")
}
</script>
<template>
<button @click="buy">购买</button>
</template>
🔗 修饰符参考:事件修饰符文档
🔍 条件判断 v-if
<script setup>
let car = { brand: "奔驰", price: 777 }
</script>
<template>
<span v-if="car.price < 1000">可以买</span>
<span v-else>买不起</span>
</template>
🔁 列表渲染 v-for
<script setup>
let fruits = ["苹果", "香蕉", "橘子"]
</script>
<template>
<li v-for="(f, i) in fruits" :key="i">{{ f }} ==> {{ i }}</li>
</template>
🔗 属性绑定 :href
<script setup>
let url = "https://www.baidu.com"
</script>
<template>
<a :href="url">百度</a>
</template>
更多指令见:Vue 官方指令文档
⚡ 响应式数据管理
❌ 非响应式变量的问题
<script setup>
let url = "https://www.baidu.com"
function changeUrl() {
url = "https://www.bilibili.com"
}
</script>
<template>
<a :href="url">链接</a>
<button @click="changeUrl">修改网址</button>
</template>
点击按钮后变量变了,页面却没变?因为这不是响应式变量!
✅ 使用 ref 创建响应式变量
<script setup>
import { ref } from 'vue'
const url = ref("https://www.baidu.com")
function changeUrl() {
url.value = "https://www.bilibili.com"
}
</script>
<template>
<a :href="url">链接:{{ url }}</a>
<button @click="changeUrl">修改网址</button>
</template>
📌 注意:ref 中的值在 JS 中访问需要 .value,html中直接写变量名。
🧩 深层响应式:ref vs reactive
import { reactive } from 'vue'
const car = reactive({ brand: '小米', price: 999 })
car.price += 1000 // 直接修改即可
| 类型 | 用法 |
|---|---|
| 基本类型 | 使用 ref() |
| 对象/数组 | 使用 reactive() |
总结:想偷懒也可以全用
ref,多写.value就完事。
📝 表单绑定 v-model
<script setup>
import { reactive } from 'vue'
const data = reactive({
username: "",
agree: true,
hobby: [],
sex: "",
edu: "",
course: []
})
</script>
绑定方式如下:
- 文本框:
v-model="data.username" - 复选框:
v-model="data.agree" - 多选框:
v-model="data.hobby" - 单选框:
v-model="data.sex" - 下拉单选:
v-model="data.edu" - 多选下拉:
v-model="data.course"
使用
v-model的输入控件值会自动同步到数据对象中。
🔢 计算属性 computed
<script setup>
import { reactive, ref, computed } from 'vue'
const car = reactive({ brand: "小米", price: 999 })
const num = ref(1)
const totalPrice = computed(() => num.value * car.price)
function addPrice() { car.price += 1000 }
</script>
<template>
<div>
<p>单价:{{ car.price }}</p>
<p>数量:{{ num }}</p>
<p>总价:{{ totalPrice }}</p>
<button @click="addPrice">涨价</button>
<button @click="num++">加数量</button>
</div>
</template>
🎯 响应式监听 watch / watchEffect
🔍 精准监听(watch)
import { watch, ref } from 'vue'
const num = ref(1)
watch(num, (newVal, oldVal, onCleanup) => {
console.log("新值:", newVal, "旧值:", oldVal)
if (newVal > 3) num.value = 3
onCleanup(() => console.log("watch 清理了"))
})
👀 自动依赖收集(watchEffect)
import { watchEffect } from 'vue'
watchEffect(() => {
console.log("响应式数据变了就触发")
})
🌀 生命周期钩子(Composition API)
在 Vue3 中,每个组件实例在创建时会经历一系列初始化步骤,如:
- 设置响应式数据
- 编译模板
- 将组件挂载到 DOM
- 监听数据变化并响应更新
Vue 在这些阶段会调用 生命周期钩子函数,让我们可以在合适的时机插入自定义逻辑。
🔄 生命周期四大阶段
| 阶段 | 常用钩子函数 |
|---|---|
| 创建 | onBeforeMount → onMounted |
| 更新 | onBeforeUpdate → onUpdated |
| 卸载 | onBeforeUnmount → onUnmounted |
| 销毁 | — |
✅ 实战示例
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from 'vue';
const count = ref(0);
const btn01 = ref();
onBeforeMount(() => {
console.log('挂载之前', count.value, document.getElementById("btn01"));
});
onMounted(() => {
console.log('挂载完毕', count.value, document.getElementById("btn01"));
});
onBeforeUpdate(() => {
console.log('更新之前', count.value, btn01.value.innerHTML);
});
onUpdated(() => {
console.log('更新完毕', count.value, btn01.value.innerHTML);
});
</script>
<template>
<button ref="btn01" @click="count++">{{ count }}</button>
</template>
📦 组件传值通信
🔁 父传子(Props)
父组件通过绑定属性传递数据,子组件使用 defineProps 接收。
- 数据是单向流动的:父 → 子
- 子组件修改 props 并不会影响父组件的值
✅ 父组件传值
<Son :books="data.books" :money="data.money" />
✅ 子组件接收
const props = defineProps({
money: { //这里面是money的特殊定义,类型、是否为必须、当空时为200
type: Number,
required: true,
default: 200
},
books: Array
});
📤 子传父(Emit)
子组件通过 emit 触发事件,父组件监听事件来接收数据或触发行为。
✅ 子组件定义并触发事件
<script setup>
const sonsum = defineProps(['sonsum']);
const emits = defineEmits(['buy']);
function buy() {
emits('buy', -5);
}
</script>
<template>
<div>
<h3>Son</h3>
<p>son sum: {{ sonsum }}</p>
<button @click="buy">儿子购买了一根5块的棒棒糖</button>
</div>
</template>
✅ 父组件监听子组件事件
<script setup>
import Son from './Son.vue';
import { ref } from 'vue';
const sum = ref(100);
function buy(num) {
sum.value += num;
}
</script>
<template>
<div>
<h2>Father</h2>
<Son :sonsum="sum" @buy="buy" />
</div>
</template>
🪝 provide / inject 实现跨层通信
在祖先组件中使用 provide 提供变量或方法,在子孙组件中通过 inject 使用。
✅ 父组件 provide 示例
import { provide, ref, nextTick } from 'vue';
const hello = ref();
const reload = async () => {
isRouterAlive.value = false;
await nextTick();
isRouterAlive.value = true;
};
provide("reload", reload);
provide("hello", hello);
✅ 子组件 inject 示例
import { inject } from 'vue';
const reload = inject("reload");
const hello = inject("hello");
🚨注意:方法需要用 箭头函数 形式定义,否则注入后
this会丢失。
🧩 插槽(Slot)
父组件可以通过插槽向子组件注入内容,实现更灵活的组件布局。
✅ 子组件使用插槽
<script setup>
const sonsum = defineProps(['sonsum']);
const emits = defineEmits(['buy']);
function buy() {
emits('buy', -5);
}
</script>
<template>
<div>
<h3>Son</h3>
<p>son sum: {{ sonsum }}</p>
<h2>按钮插槽</h2>
<slot name="btn">没有按钮</slot>
<h2>按钮文字插槽</h2>
<button @click="buy">
<slot name="买东西">初始值</slot>
</button>
</div>
</template>
✅ 父组件传入插槽内容
<script setup>
import Son from './Son.vue';
import { ref } from 'vue';
const sum = ref(100);
function buy(num) {
sum.value += num;
}
</script>
<template>
<div>
<h2>Father</h2>
<Son :sonsum="sum" @buy="buy">
<!-- 命名插槽绑定 v-slot: -->
<template v-slot:btn>
<button>生成了按钮</button>
</template>
<!-- 简写语法 # -->
<template #买东西>
买棒棒糖
</template>
</Son>
</div>
</template>
🧠 小结
| 功能点 | API/关键词 | 特性说明 |
|---|---|---|
| 生命周期钩子 | onMounted 等 | 提供组件各阶段插入钩子逻辑 |
| 父传子 | defineProps | 单向数据流,父到子 |
| 子传父 | defineEmits | 子触发事件,父来处理 |
| 跨层通信 | provide/inject | 跨层级传值传方法 |
| 插槽 | slot/v-slot/# | 插入结构内容,增强组件复用性 |
🛠️ 持续练习 + 阅读官方文档,是精通 Vue 的最佳方式!
📚 官方地址:cn.vuejs.org/