Vue 这份笔记是 2024 年前端学习笔记的最后一篇,从 Vue2 的选项式一路记到 Vue3 的组合式。
这也是第一次接触框架:从「操作 DOM」换到「操作数据」,一开始很不适应,写顺之后确实回不去了。
创建 Vue 实例#
步骤:
- 准备容器;
- 引入 Vue(官网的开发版本 / 生产版本);
new Vue()创建实例;- 指定配置项,渲染数据。
<!-- 准备容器 -->
<div id="app">
<h1>{{ data1 }}</h1>
<p>{{ data2 }}</p>
</div>
<!-- 引入包 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
data1: "genshin",
data2: "abccba",
},
methods: {
fn() {},
},
});
</script>el:指定容器;data:提供数据,里面的数据最终会被添加到实例上,比如app.data1;methods:提供处理逻辑的函数。
基础使用#
插值表达式 {{}}#
- 作用:利用表达式进行插值,渲染到页面中。表达式是可以被求值的代码,JS 引擎会计算出一个结果。
- 语法:
{{ 表达式 }}。 - 注意:使用的数据要存在(在 data 里);支持的是表达式而不是语句(
if、for不行);不能在标签属性里使用。
Vue 指令#
v-html
- 作用:动态设置元素的 innerHTML;
- 语法:
v-html = "表达式"。
v-show
- 作用:控制元素显示隐藏;
- 语法:
v-show = "表达式",值为 true 显示、false 隐藏; - 原理:切换
display: none; - 场景:频繁切换显示隐藏。
v-if
- 作用:控制元素显示隐藏(条件渲染);
- 语法:
v-if = "表达式"; - 原理:基于条件判断,创建或移除元素节点;
- 场景:要么显示要么隐藏、不频繁切换。
v-else 和 v-else-if
- 作用:辅助 v-if 进行判断渲染;
- 语法:
v-else、v-else-if = "表达式"; - 注意:需要紧挨着 v-if 一起使用。
v-on
- 作用:注册事件;
- 语法:
v-on:事件名 = "内联语句"或v-on:事件名 = "methods 中的函数名"; - 简写:
@事件名; - 注意:methods 函数内的 this 指向 Vue 实例。
v-bind
- 作用:动态设置 HTML 的标签属性;
- 语法:
v-bind:属性名="表达式"; - 简写:
:属性名="表达式"。
v-for
- 作用:基于数据循环,多次渲染整个元素;
- 语法:
v-for = "(item, index) in 数组",简写v-for = "item in 数组"。
v-model
- 作用:给表单元素使用,实现双向数据绑定——数据变化视图自动更新,视图变化数据自动更新;
- 语法:
v-model = "变量"。
指令修饰符#
- 按键修饰符:
@keyup.enter监听键盘回车。 - v-model 修饰符:
v-model.trim去除首尾空格、v-model.number转数字。 - 事件修饰符:
@事件名.stop阻止冒泡、@事件名.prevent阻止默认行为。
v-bind 对样式的增强#
:class 可以绑定对象或数组:
- 对象:键是类名,值是布尔值,为 true 就有这个类。
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>- 数组:数组里所有的类都会添加到元素上,本质是一个 class 列表。
<div class="box" :class="[类名1, 类名2]"></div>典型用法是 tab 导航高亮。
:style 绑定样式对象,适合动态设置某个具体属性:
<div class="box" :style="{ CSS属性名1: 值, CSS属性名2: 值 }"></div>v-model 应用于其他表单元素#
常见的表单元素都可以用 v-model 绑定,快速获取或设置值:
- 输入框
input:text→ value; - 文本域
textarea→ value; - 复选框
input:checkbox→ checked; - 单选框
input:radio→ checked; - 下拉菜单
select→ value。
computed 计算属性#
概念:基于现有数据计算出来的新属性,依赖的数据变化时会自动重新计算。声明在 computed 配置项中,一个计算属性对应一个函数,使用起来和普通属性一样({{ 计算属性名 }})。
简写形式只能读取,不能修改:
computed: {
计算属性名() {
return 结果;
}
}需要修改操作时,要写完整写法(get / set):
computed: {
计算属性名: {
get() {
return 结果;
},
set(修改的值) {
// 修改逻辑
}
}
}computed 与 methods 的区别:
- computed:封装一段对数据的处理、求得一个结果,作为属性直接使用(
this.计算属性、{{ 计算属性 }}); - methods:给实例提供一个方法,作为方法需要调用(
this.方法名()、{{ 方法名() }}、@事件名="方法名")。 - computed 有缓存特性:计算出来的结果会被缓存,再次使用直接读缓存;依赖项变化时才重新计算并再次缓存。
watch 侦听器#
作用:监视数据变化,执行一些业务逻辑或异步操作。
相关配置:
deep: true:对复杂类型深度监视;immediate: true:初始化时立刻执行一次 handler;handler:执行核心代码的函数。
watch: {
数据属性名: {
deep: true,
immediate: true,
handler(newValue, oldValue) {
console.log(newValue, oldValue);
}
},
"对象.数据属性名": {}
}不需要额外配置时可以直接写函数:
watch: {
数据属性名(newValue, oldValue) {
// 核心代码
}
}生命周期#
定义:一个 Vue 实例从创建到销毁的整个过程,分为四个阶段:
- 创建:响应式数据等,阶段末可以发送初始化渲染请求;
- 挂载:渲染模板,阶段末可以操作 DOM;
- 更新:用户修改数据、更新视图,这是一个循环过程;
- 销毁:销毁实例,也就是关掉网页。
Vue 在生命周期过程中会自动运行一些函数,叫生命周期钩子,让开发者可以在特定阶段运行自己的代码。八个钩子是:
beforeCreate 和 created、beforeMount 和 mounted、beforeUpdate 和 updated、beforeDestroy 和 destroyed。
一般用 created 发送初始化渲染请求,用 mounted 操作 DOM。
beforeCreate() {
console.log("beforeCreate 响应式数据准备好之前", this.count);
},
created() {
console.log("created 响应式数据准备好之后", this.count);
// 可以开始发送初始化渲染的请求了
},
beforeMount() {
console.log("beforeMount 模板渲染之前", document.querySelector("h3").innerHTML);
},
mounted() {
console.log("mounted 模板渲染之后", document.querySelector("h3").innerHTML);
// 可以开始操作 DOM 了
},
beforeUpdate() {
console.log("beforeUpdate 数据修改了,视图还没更新");
},
updated() {
console.log("updated 数据修改了,视图已经更新");
},
beforeDestroy() {
// 清除掉一些 Vue 以外的资源占用,定时器、延时器……
},
destroyed() {
console.log("destroyed,卸载后");
}Vue 脚手架与工程化#
安装使用#
Vue CLI 是 Vue 官方提供的全局命令工具,可以快速创建一个开发 Vue 项目的标准化基础架子(集成了 webpack 配置)。
npm i @vue/cli -g # 全局安装
vue create project-name # 创建项目架子组件式开发#
组件化:一个页面可以拆分成一个个组件,每个组件有自己独立的结构、样式、行为,便于维护和复用。
index.html:模板文件;main.js:入口文件,引入 vue、App.vue 以及其他全局组件;App.vue:根组件,整个应用最上层的组件,包裹所有普通小组件;- 其他普通组件(
xxx.vue)放在components文件夹里,还可以继续细分。
注意组件命名需要大驼峰。
局部注册(只在注册的组件内使用):
import HmHeader from "./components/HmHeader";
export default {
components: {
HmHeader,
},
};全局注册(所有组件内都能使用),写在 main.js:
import HmButton from "./components/HmButton";
Vue.component("HmButton", HmButton);组件的三大组成部分#
结构 template:Vue2 中只能有一个根元素。
样式 style:
- 默认组件中的样式会作用到全局,容易造成多个组件之间的样式冲突;
- 给组件加上
scoped属性,可以让样式只作用于当前组件。 - 技巧:公共样式在 main.js 里注册,
import './style/index.css'。
逻辑 script:
el是根实例独有的;- 组件中的
data必须是一个函数,保证每个组件实例维护独立的一份数据对象。
data() {
return {};
}组件通信#
组件的数据是独立的,无法直接访问其他组件的数据,需要专门的方案。组件关系分两类,对应的解决方式也不同:
- 父子关系 → props & $emit;
- 非父子关系 → provide & inject 或 event bus;
- 通用方案 → Vuex。
父子关系#
父传子 props:
- 父组件给子组件添加属性传值(用 v-bind);
- 子组件用 props 接收;
- 子组件使用。

props 也可以写成带校验的对象形式:
props: {
校验的属性名: {
type: Number, // Number String Boolean ...
required: true, // 是否必填
default: 默认值,
validator(value) {
// 自定义校验逻辑,返回 Boolean
return true;
}
}
}子传父 $emit:
- 子组件
$emit发送消息; - 父组件给子组件添加消息监听;
- 父组件实现处理函数。

methods: {
add() {
this.$emit("add", 传入值);
}
}props 与 data 的区别:data 的数据是自己的,可以随便改;props 的数据是外部的,不能直接改,要遵循单向数据流。
非父子关系#
event bus(事件总线):用于非父子组件之间进行简易的消息传递。
- 创建一个都能访问到的事件总线(空 Vue 实例),放在
utils/EventBus.js:
import Vue from "vue";
const Bus = new Vue();
export default Bus;- A 组件(接收方)监听 Bus 实例的事件:
created() {
Bus.$on("sendMsg", (msg) => {
this.msg = msg;
});
}- B 组件(发送方)触发事件:
Bus.$emit("sendMsg", "这是一个消息");provide & inject:用于跨层级共享数据。
父组件 provide 提供数据:
export default {
provide() {
return {
color: this.color, // 普通类型:非响应式
userInfo: this.userInfo, // 复杂类型:响应式
};
},
};子 / 孙组件 inject 取值:
export default {
inject: ["color", "userInfo"],
created() {
console.log(this.color, this.userInfo);
},
};进阶语法#
v-model 原理#
v-model 本质上是一个语法糖:用在输入框上,就是 value 属性和 input 事件的合写。
<input v-model="msg" type="text" />
<input :value="msg" @input="msg = $event.target.value" type="text" />$event 用于在模板中获取事件的形参。
表单类组件封装与 v-model 简化#
封装表单类组件实现父子数据双向绑定:
- 父传子:数据通过 props 传给子组件,拆解 v-model 绑定的数据;
- 子传父:监听输入,子组件把值传给父组件修改。
父组件用 v-model 简化(子组件 props 通过 value 接收,事件触发 input):
<BaseSelect :cityId="selectId" @事件名="selectId = $event" />
<BaseSelect v-model="selectId"></BaseSelect>props: {
cityId: String;
// 父组件用 v-model 时,这里要用 value 接收
},
methods: {
handleChange(e) {
this.$emit("事件名", e.target.value);
}
}.sync 修饰符#
- 作用:实现子组件与父组件数据的双向绑定,简化代码;
- 特点:prop 属性名可以自定义,不固定为 value;
- 场景:封装弹框类组件,
visible为 true 显示、false 隐藏; - 本质:
:属性名和@update:属性名的合写。
<!-- 父组件 -->
<BaseDialog :visible.sync="isShow" />
<!-- 等价于 -->
<BaseDialog :visible="isShow" @update:visible="isShow = $event" />// 子组件
props: { visible: Boolean },
this.$emit("update:visible", false)ref 和 $refs#
作用:获取 DOM 元素或组件实例。查找范围是当前组件内,比 querySelector 更精确稳定。
获取 DOM:
<div ref="chartRef">我是渲染图表的容器</div>mounted() {
console.log(this.$refs.chartRef);
}获取组件:给组件加 ref,之后就能调用组件对象里的方法。
<BaseForm ref="baseForm"></BaseForm>this.$refs.baseForm.组件方法();异步更新与 $nextTick#
$nextTick:等 DOM 更新后才会触发执行方法里的函数体。
this.$nextTick(() => {
this.$refs.inp.focus();
});自定义#
自定义指令#
作用:封装一些 DOM 操作,扩展额外功能。
相关参数:
inserted:元素被插入后触发的钩子函数;el:本标签;binding.value:指令的值。
局部注册:
directives: {
指令名: {
inserted(el, binding) {
el.focus();
el.style.color = binding.value;
},
update(el, binding) {
el.style.color = binding.value;
}
}
}全局注册(main.js):
Vue.directive("指令名", {
inserted(el, binding) {
el.focus();
},
});使用:<input v-指令名="指令值" type="text" />。
插槽#
作用:让组件内部的一些结构支持自定义。典型需求是把需要多次显示的对话框封装成组件,但内容部分不想写死。
组件里用 <slot> 占位,里面的内容就是默认值:
<template>
<div class="dialog-header">
<slot name="head">默认标题</slot>
</div>
<div class="dialog-content">
<slot name="content">默认内容</slot>
</div>
<div class="dialog-footer">
<slot name="footer">默认尾部</slot>
</div>
</template>使用时用 template 配合 v-slot:名字 分发内容:
<组件名字 :list="list">
<template v-slot:head>我是标题</template>
<template v-slot:content>我是内容</template>
<template v-slot:footer>
<button>我是尾部按钮</button>
</template>
</组件名字>作用域插槽(子给父传值):
- 以添加属性的方式传值:
<slot :id="item.id" msg="测试文本"></slot>; - 所有添加的属性都会被收集到一个对象中;
- 在 template 中通过
#插槽名="obj"接收,默认插槽名是default。
VueRouter 路由#
版本对应关系:Vue2 用 VueRouter3 + Vuex3,Vue3 用 VueRouter4 + Vuex4。
认识路由#
Vue 中的路由是「路径和组件的映射关系」,作用是修改地址栏路径时切换显示匹配的组件,它是 Vue 官方的路由插件(第三方包)。
5 + 2 个基础步骤#
在 main.js 里操作的前五步:
- 下载:
npm install vue-router@3.6.5; - 引入:
import VueRouter from 'vue-router'; - 安装注册:
Vue.use(VueRouter); - 创建路由对象:
const router = new VueRouter(); - 注入到 Vue 实例:
new Vue({
render: (h) => h(App),
router,
}).$mount("#app");两个核心步骤:
- 创建组件(views 目录)并配置路由规则;
- 配置导航和路由出口(路径匹配的组件显示的位置)。
import Find from "./views/Find";
// 路由懒加载
const Login = () => import("@/views/login");
const router = new VueRouter({
routes: [
{ path: "/find", component: Find },
{
path: "/friend",
component: Friend,
children: [{ path: "/路径", component: xxx }], // 二级路由
},
],
});<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
</div>
<div class="top">
<!-- 路由出口:匹配的组件展示在这里 -->
<router-view></router-view>
</div>路由配置堆在 main.js 不太合适,可以抽离到 src/router/index.js,main.js 再引入。@ 指代 src 目录,可以快速引入组件。
声明式导航#
router-link:vue-router 提供的全局组件,用来取代 a 标签。
- 能跳转:用
to属性指定路径(必须),本质还是 a 标签,to不需要写#; - 能高亮:默认提供高亮类名,直接写样式就能高亮。
<a href="#/friend">朋友</a>
<router-link to="/路径值"></router-link>高亮类名:router-link 会自动添加两个类名。
router-link-active:模糊匹配,to="/my"可以匹配/my、/my/a、/my/b……用得更多;router-link-exact-active:精确匹配,to="/my"只能匹配/my。
类名太长可以在创建路由对象时定制:
const router = new VueRouter({
routes: [],
linkActiveClass: "类名1",
linkExactActiveClass: "类名2",
});参数传递:
- 查询参数:
to="/path?参数名1=值1&参数名2=值2",接收用$route.query.参数名; - 动态路由:
path: "/path/:参数名"表示必须传参(否则页面空白),path: "/path/:参数名?"里的?表示可选;跳转用to="/path/值",接收用$route.params.参数名。
查询参数适合传多个参数,动态路由传参更优雅简洁,适合单个参数。
重定向、404 与模式#
重定向:打开网页时 url 默认是 /,未匹配到组件会空白,可以强制跳转。
{ path: "/", redirect: "/home" }404 页面:配在路由最后,任意路径都不匹配时命中。
{ path: "*", component: NotFind }模式切换:hash 路由(默认)形如 http://localhost:8080/#/home;history 路由形如 http://localhost:8080/home,路径更自然,但上线需要服务端支持。
const router = new VueRouter({
mode: "history",
routes: [],
});编程式导航#
用 JS 代码跳转:
// path 路径跳转
this.$router.push("/路由路径");
this.$router.push({ path: "/路由路径" });
// name 命名路由跳转(适合路径很长的场景)
this.$router.push({ name: "路由名" });命名路由需要在配置里写 name。另外 this.$router.replace 和 push 功能差不多,区别是 push 会记录历史,replace 直接替换。
跳转时传参:
// query 传参
this.$router.push("/路径?参数名1=参数值1");
this.$router.push({ path: "/路径", query: { 参数名1: "参数值1" } });
// 动态路由传参
this.$router.push("/路径/参数值");
this.$router.push({ path: "/路径/参数值" });
// 命名路由 + params
this.$router.push({ name: "路由名字", params: { 参数名: "参数值" } });组件缓存 keep-alive#
场景:从列表点到详情页,再点返回,数据重新加载了——希望回到原来的位置。原因是路由跳转后组件被销毁,返回时又重新创建,所以数据重新加载。
keep-alive 是 Vue 的内置组件,包裹动态组件时会缓存不活动的组件实例,而不是销毁它们。它是一个抽象组件:自身不会渲染成 DOM 元素,也不会出现在父组件链中。
优点:组件切换时把切出去的组件保留在内存中,防止重复渲染 DOM,减少加载时间和性能消耗。
三个属性:
include:只有匹配的组件会被缓存;exclude:匹配的组件都不会被缓存;max:最多缓存多少个组件实例。
<keep-alive :include="['LayoutPage']">
<router-view></router-view>
</keep-alive>钩子函数:
activated:组件被激活(进入页面)时触发;deactivated:组件不被使用(离开页面)时触发。
组件被缓存后,就不会再执行 created、mounted、destroyed 这些钩子了。
Vuex#
Vuex 是 Vue 的状态管理工具,状态就是数据,可以帮我们管理 Vue 通用的数据。
优势:共同维护一份数据、数据集中化管理、响应式变化、操作简洁(提供了辅助函数)。
创建空仓库#
npm install vuex@3;- 新建
store/index.js专门存放 vuex; - 载入
Vue.use(Vuex),创建仓库new Vuex.Store(); - 在 main.js 中导入并挂载到 Vue 实例。
state 状态#
State 提供唯一的公共数据源,所有共享数据都统一放到 Store 的 State 中。
const store = new Vuex.Store({
state: {
count: 101,
},
});和组件 data 的区别是:data 是组件自己的数据,state 是所有组件共享的数据。
使用数据:
// 模板中
{{ $store.state.xxx }}
// 组件逻辑中
this.$store.state.xxx
// JS 模块中
store.state.xxx也可以用辅助函数 mapState 把数据映射到组件的计算属性:
import { mapState } from "vuex";
computed: {
...mapState(["count"])
// 相当于
// count() { return this.$store.state.count }
}mutations 修改数据#
Vuex 遵循单向数据流,组件中不能直接修改仓库的数据,需要用 mutations。
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
// 第一个参数是 state,n 是传入的参数
addCount(state, n) {
state.count += n;
},
},
});mapMutations 可以把 mutations 中的方法映射到组件的 methods:
import { mapMutations } from "vuex";
methods: {
...mapMutations(["subCount"])
// 相当于
// subCount(n) { this.$store.commit("subCount", n) }
}actions 处理异步操作#
mutations 必须是同步的(便于监测数据变化、记录),异步操作放在 actions 里,再通过 commit 触发 mutations。
actions: {
// context 是上下文对象,可以调用 commit、dispatch,也能拿到 state、getters
setAsyncCount(context, num) {
setTimeout(() => {
context.commit("changeCount", num);
}, 1000);
}
}mapActions 把 actions 中的方法映射到组件的 methods:
import { mapActions } from "vuex";
methods: {
...mapActions(["setAsyncCount"])
// 相当于
// changeCountAction(n) { this.$store.dispatch("setAsyncCount", n) }
}getters#
从 state 中派生出一些状态、并且依赖 state 时,用 getters。比如 state 里有一个 1 到 10 的数组,组件里只想显示大于 5 的数据:
getters: {
filterList(state) {
return state.list.filter((item) => item > 5);
},
}注意:getters 函数的第一个参数是 state,并且必须有返回值。使用方法和 state 一样,也可以用 mapGetters 映射。
module 模块语法#
Vuex 使用单一状态树,应用变得复杂时 store 会相当臃肿,所以可以把不同种类的状态分模块存放。
// store/modules/user.js
const state = { userInfo: { name: "zs", age: 18 } };
const mutations = {};
const actions = {};
const getters = {};
export default {
namespaced: true, // 开启命名空间
state,
mutations,
actions,
getters,
};// store/index.js
import user from "./modules/user";
const store = new Vuex.Store({
modules: { user },
});模块中的访问语法:
- state:
$store.state.模块名.xxx,映射用mapState("模块名", ["xxx"])(需要开启命名空间); - getters:
$store.getters["模块名/xxx"],映射用mapGetters("模块名", ["xxx"]); - mutation:
$store.commit("模块名/xxx", 额外参数),映射用mapMutations("模块名", ["xxx"]); - action:
$store.dispatch("模块名/xxx", 额外参数),映射用mapActions("模块名", ["xxx"])。
用 json-server 准备后端接口#
- 全局安装:
npm i json-server -g; - 在代码根目录新建
db目录; - 把
index.json放进db目录; - 进入 db 目录执行
json-server index.json; - 访问
http://localhost:3000/cart测试接口。
推荐用 json-server --watch index.json,可以实时监听 JSON 文件的修改。
Vue3 组合式开发#
Vue3 相比 Vue2:
- 更容易维护:组合式 API、更好的 TypeScript 支持;
- 更快的速度:重写 diff 算法、模板编译优化、更高效的组件初始化;
- 更小的体积:良好的 TreeShaking、按需引入;
- 更优的数据响应式:基于 Proxy。
创建项目 create-vue#
前提是 Node.js 16.0 以上。
npm init vue@latest
pnpm create vue关键文件:
vite.config.js:项目配置文件,基于 vite;package.json:核心依赖变成 Vue3 和 vite;main.js:入口文件,用createApp创建应用实例;app.vue:根组件(SFC 单文件组件,script / template / style)。变化有三处:script 和 template 的顺序调整了;template 不再要求唯一根元素;script 上添加setup标识以支持组合式 API;index.html:单页入口,提供 id 为 app 的挂载点。
代码检查与提交钩子#
.eslintrc.cjs 里可以配一套 Prettier 规则:单引号、不加分号、每行至多 80 字符、对象和数组末尾不加逗号、换行符不做限制;另外让 vue/multi-word-component-names 忽略 index、关掉 props 解构的校验、开启 no-undef 未定义变量提示。
husky(提交前自动检查):
git init初始化仓库;pnpm dlx husky-init && pnpm install初始化 husky 配置;- 修改
.husky/pre-commit,写入pnpm lint。
问题是默认走全量检查,耗时长,而且历史遗留问题也会一起报出来。
lint-staged(只检查暂存区文件):
- 安装:
pnpm i lint-staged -D; - 在
package.json里配置"lint-staged": { "*.{js,ts,vue}": ["eslint --fix"] },并加上"lint-staged": "lint-staged"脚本; - 把
.husky/pre-commit改成pnpm lint-staged。
组合式 API setup#
setup() 是组件中使用组合式 API 的入口,返回的对象会暴露给模板和组件实例。
- 在模板中访问 setup 返回的 ref 时会自动浅层解包,不需要写
.value,通过 this 访问时也一样; - 在
setup()中访问 this 会是undefined。
export default {
setup() {
const count = 0;
const logMessage = () => {
console.log("hello world");
};
return { count, logMessage };
},
};<script setup> 语法糖:
<script setup>
const count = 0;
const logMessage = () => {
console.log("hello world");
};
</script>响应式核心#
reactive():接收对象类型数据,返回一个响应式对象。
import { reactive } from "vue";
const state = reactive(对象);ref():接收简单类型或对象类型,返回响应式对象,内部实现依赖 reactive。
import { ref } from "vue";
const count = ref(0);
console.log(count.value); // 0<!-- 模板中不需要 .value -->
<button @click="count++">{{ count }}</button>computed:思想和 Vue2 一致,只是写法变了。计算属性默认只读,需要写入时加上 get 和 set。
import { computed } from "vue";
const computedState = computed(() => {
return 计算后的值;
});watch:侦听一个或多个数据的变化,变化时执行回调。两个额外参数:immediate(立即执行)和 deep(深度侦听)。
import { ref, watch } from "vue";
const count = ref(0);
const name = ref("a");
watch(
[count, name],
([newCount, newName], [oldCount, oldName]) => {
console.log("发生变化", newCount, newName, oldCount, oldName);
},
{ deep: true, immediate: true },
);生命周期#
选项式与组合式的对应关系:
beforeCreate/created→setup;beforeMount→onBeforeMount,mounted→onMounted;beforeUpdate→onBeforeUpdate,updated→onUpdated;beforeUnmount→onBeforeUnmount,unmounted→onUnmounted。
import { onMounted } from "vue";
onMounted(() => {
// 自定义逻辑
});父子通信#
父传子:父组件绑定属性,子组件用编译器宏 defineProps 接收。
<sonCom message="this is message"></sonCom>// 子组件:因为写了 setup,无法直接配置 props 选项,要用编译器宏
const props = defineProps({
message: String,
});子传父:父组件用 @ 绑定事件,子组件用 defineEmits 拿到 emit 方法后触发。
<SonCom @changeMoney="changeFn"></SonCom>const emit = defineEmits(["changeMoney"]);
const buy = () => {
emit("changeMoney", 5);
};模板引用#
通过 ref 获取真实的 DOM 对象或组件实例:
import { ref } from "vue";
const h1Ref = ref(null);<h1 ref="h1Ref"></h1>defineExpose()#
默认情况下,<script setup> 中组件内部的属性和方法不开放给父组件访问,可以用 defineExpose 编译宏指定允许访问的内容。
const message = ref("hello world");
defineExpose({ message });跨层组件通信#
provide 和 inject:顶层组件向任意底层组件传递数据和方法。
// 父组件
provide("key", 数据 / ref 对象 / 方法);
// 子组件
const x = inject("key");Vue3.3 新特性#
defineOptions:直接在 <script setup> 中声明组件选项,不必再另开一个 <script> 块。主要用来定义 Options API 的选项,props、emits、expose、slots 除外(这些有对应的 defineXXX)。它是个宏,选项会被提升到模块作用域,无法访问 <script setup> 中不是字面常数的局部变量。
defineOptions({
// 选项
});defineModel:用于简化父子组件之间的双向绑定,这次笔记只记下了名字,还没有展开。
写在最后#
Vue 是这批笔记里最「成套」的一份:从模板语法、组件通信,到路由、状态管理,再到组合式 API,基本把入门要用的东西串了一遍。
现在回看,Vue2 的选项式和 Vue3 的组合式我都记了,虽然后来写项目时大多直接用 Vue3 的 <script setup>,但先学过选项式反而让组合式的对应关系更好理解。
2024 年的前端笔记到这里就整理完了——HTML、CSS、JavaScript(基础 + 进阶 6 篇)、Vue,一共 9 篇。谢谢当年的自己肯把东西记下来。
