区别:
选项式API
- 在 vue2.x 项目中使用的就是 选项式API 写法
- 优点:易于学习和使用,写代码的位置已经约定好了
- 缺点:代码组织性差,相似的逻辑代码不便于复用,逻辑复杂代码多了不好阅读
组合式API
- 在 vue3 中使用的就是 组合式API 写法
- 优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护
- 缺点:需要有良好的代码组织能力和拆分逻辑能力
学习过程对比选项式API写法。
响应式和计算属性
响应式就是选项式API中的data(),也就是变量声明。
<template>
<h3>组合式API-简约写法</h3>
<p>{{ message }}</p>
<p>{{ userInfo.name }}</p>
<p>{{ reverse }}</p>
<p>{{ demo }}</p>
</template>
<script setup> //这里增加setup
// 为什么每次使用到一个功能,都需要引入?
// Vue3的优势:在打包的时候,Vue2所有内容都会打包,Vue3只把使用到的功能打包
// Vue3的打包会让程序变得更小
//ref:基本类型的响应式数据:String Number Boolean
//reactive:引用类型的响应式数据:Array Object
import { ref, reactive, computed } from "vue"
const message = ref("组合式API 简约 绑定数据")
const userInfo = reactive({
name: "iwen"
})
const reverse = computed(() => {
// 注意:message在逻辑里面读取的时候(script),必须通过.value读取到值
return message.value.split("").reverse().join("")
})
const demo = computed(() => {
return message.value + "hahah"
})
</script>
事件处理
//选项式API
<template>
<h3>选项式API</h3>
<p>{{ count }}</p>
<button @click="addCountHandle">增加</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
addCountHandle() {
this.count++
}
}
}
</script>
//组合式API
<template>
<h3>组合式API</h3>
<p>{{ count }}</p>
<button @click="addCountHandle">增加</button>
</template>
<script setup>
import { ref } from "vue"
const count = ref(0) //将count变量初始化为0
function addCountHandle(e) { //这里直接写方法,不再用写入methods中。this.count变为count.value
count.value++
console.log(e)
}
</script>
侦听器
//选项式API
<template>
<h3>选项式API</h3>
<p>{{ count }}</p>
<button @click="addCountHandle">增加</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
addCountHandle() {
this.count++
}
},
watch: {
count(newValue, oldValue) {
console.log(newValue, oldValue);
}
}
}
</script>
//组合式API
<template>
<h3>组合式API</h3>
<p>{{ count }}</p>
<button @click="addCountHandle">增加</button>
</template>
<script setup>
import { ref, watch } from "vue"
const count = ref(0);
function addCountHandle() {
// .value是读取ref数据的
count.value++
}
// 参数1: 代表侦听的数据
watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue);
})
</script>
watchEffect
只要你在 watchEffect 函数里用到了响应式变量,这个函数就会在这些变量发生变化时重新运行。
<template>
<div>
<p>当前姓名: {{ firstName }}{{ lastName }}</p>
<button @click="changeFirstNameHandle">改为firstname为纪</button>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
watchEffect(() => {
console.log(`全名是:${firstName.value}${lastName.value}`)
})
function changeFirstNameHandle() {
firstName.value = "纪"
}
</script>
控制台输出:
全名是:张三 //程序运行就会执行一次
全名是:张三 //响应式变量发生变化就会再执行一次,
//不管是 `firstName` 还是 `lastName` 变了,都会重新触发 `watchEffect`。
watchEffect 和 watch 的区别
| 对比项 | watchEffect | watch |
|---|---|---|
| 执行时机 | 立即执行一次 | 依赖变化时执行 |
| 依赖收集方式 | 自动收集(访问了哪些响应式数据) | 手动指定要监听的变量 |
| 使用场景 | 快速响应、调试、简单副作用 | 精准控制依赖、旧值新值比较 |
停止监听
watchEffect 返回一个停止函数:
const stop = watchEffect(() => {
console.log('运行中...')
})
// 停止监听
stop()
副作用清理:
目前看不懂,用到再说,看官网文档。
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(newId)
// 如果 `id` 变化,则调用 `cancel`,
// 如果之前的请求未完成,则取消该请求
onCleanup(cancel)
data.value = await response
})
什么是副作用(Side Effect)?
副作用是指:函数在运行过程中,除了返回值之外,还对外部产生了影响的行为。
这些“影响”可能包括:
- 修改了外部的变量
- 操作了 DOM
- 发起了网络请求(AJAX / fetch)
- 设置了定时器(
setTimeout/setInterval) - 打印日志(
console.log) - 本地存储(
localStorage)
🎯 举个例子
没有副作用的函数(纯函数):
function add(a, b) {
return a + b
}
这个函数:
- 不依赖外部变量
- 不改变外部状态
- 每次调用都返回相同结果 ✅ 所以它是纯函数,没有副作用。
有副作用的函数:
let total = 0
function addToTotal(num) {
total += num
}
这个函数:
- 修改了外部变量
total - 改变了“外部世界”的状态 ❌ 所以它有副作用。
在 Vue 中的副作用是?
在 Vue 中,副作用通常是那些“对界面或系统产生实际影响的操作”,比如:
watchEffect(() => {
console.log(count.value) // ✅ 副作用:打印日志
})
或者:
watchEffect(() => {
fetch(`/api/data?id=${id.value}`) // ✅ 副作用:发起请求
})
这些副作用会在响应式数据发生变化时重新执行。
💡为什么要特别处理副作用?
副作用本身不可避免,但要管理好它们,因为它们:
- 可能导致数据不一致
- 在组件销毁后还在运行(比如异步请求没取消)
- 影响性能或用户体验
Vue 3 提供 watchEffect 和 watch,就是帮我们更安全、更智能地处理副作用。
生命周期
//选项式API
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
addCountHandle() {
this.count++
}
},
created() {
console.log("创建之后");
},
mounted() {
console.log("渲染之后1");
},
// 第二个生命周期函数覆盖了第一个,只显示2。所有代码都要放在一个生命周期函数里面,会使代码太过臃肿。
mounted() {
console.log("渲染之后2");
}
}
</script>
//组合式API
<template>
<h3>组合式API</h3>
<p>{{ count }}</p>
<button @click="addCountHandle">增加</button>
</template>
<script setup>
import { ref, watch ,onMounted} from "vue" //引入onMounted
const count = ref(0);
function addCountHandle() {
// .value是读取ref数据的
count.value++
}
//每个生命周期函数方法,都可以独立的处理一个业务。
onMounted(() => {
console.log("组件渲染之后1");
});
//这里不会覆盖,1和2都会显示。
onMounted(() => {
console.log("组件渲染之后2");
});
</script>
模板引用
获取真实的DOM对象的。 当然可以!以下是图片中的文字内容:
虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的refattribute,组合式API的实现更为简洁。
//选项式API
<template>
<h3>选项式API</h3>
<p ref="message">选项式API-模板引用</p>
</template>
<script>
export default {
mounted() {
this.$refs.message.innerHTML = "选项式API-模板引用-修改"
}
}
</script>
<template>
<h3>组合式API</h3>
<p ref="message">组合式API-模板引用</p>
</template>
<script setup>
import { ref, onMounted } from "vue"
const message = ref(null) //初始化为null,必须和模板里面ref同名
// 不能放在最外层,因为在DOM还没有渲染的时候,这里的代码就已经执行
// 必须保证DOM渲染完成之后在去读取DOM,所以innerHTML要放在onMounted里面。
onMounted(() => {
message.value.innerHTML = "组合式API-模板引用-修改"
})
</script>
Props
父传子
选项式API:
<template>
<h3>选项式API-Parent</h3>
<Child title = "传递数据"/>
</template>
<script>
import Child from "./Child.vue"
export default {
components: {
Child
}
}
</script>
<template>
<h3>选项式API-Child</h3>
<p>{{ title }}</p>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ""
}
}
}
</script>
组合式API:
<template>
<h3>组合式API-Parent</h3>
<Child title = "传递数据" :age = "age"/> //组合式API引入组件不需要三步走了,现在只需要二步。
</template>
<script setup>
import Child from "./Child.vue"
import ref from "vue"
const age = ref(20)
</script>
<template>
<h3>组合式API-Child</h3>
<p>{{ title }}</p>
<p>{{ age }}</p>
</template>
<script setup>
const props = defineProps({ //使用definProps
title: {
type: String,
default: ""
},
age:{
type:Number,
default:0
})
consolo.log(props.title)
consolo.log(props.age)
</script>
事件
子传父
组件之间传递数据,除了props可以做到,还可以使用自定义事件$emit。
选项式API:
<template>
<h3>选项式API-Parent</h3>
<Child title="传递数据" @onSomeEvent="getMessageHandler"/>
<p>{{ message }}</p>
</template>
<script>
import Child from "./Child.vue"
export default {
data() {
return {
message: ""
}
},
components: {
Child
},
methods: {
getMessageHandler(data) {
this.message = data;
}
}
}
</script>
<template>
<h3>选项式API-Child</h3>
<p>{{ title }}</p>
<button @click="sendMessageHandler">发送数据</button>
</template>
<script>
export default {
data() {
return {
message: "Child数据"
}
},
methods: {
sendMessageHandler() {
this.$emit("onSomeEvent", this.message)
}
}
}
</script>
组合式API
<template>
<h3>组合式API-Parent</h3>
<Child @onSomeEvent="getMessageHandler"/>
<p>{{ message }}</p>
</template>
<script setup>
import Child from "./Child.vue"
import { ref } from "vue"
const message = ref("")
function getMessageHandler(data){
message.value = data
}
</script>
<template>
<h3>组合式API-Child</h3>
<p>{{ title }}</p>
<p>{{ age }}</p>
<button @click="sendMessageHandler">发送数据</button>
</template>
<script setup>
import { ref } from "vue"
const emit = defineEmits(["onSomeEvent"]) //需要使用defineEmits定义事件
function senMessageHandler(){
emit("onSomeEvent", message.value) //再用返回的emit发送数据给父组件
}
</script>
自定义指令基础
选项式API:
<template>
<h3>选项式API-自定义指令</h3>
<p v-author>学习前端</p> //网页显示会加上-itbaizh
<p v-author>大家好,学习前端课程</p> //网页显示会加上-itbaizh
</template>
<script>
export default {
// 自定义指令
directives: {
author: {
mounted(element) {
element.innerHTML = element.innerHTML + "-itbaizh"
}
}
}
}
</script>
组合式API
<template>
<h3>组合式API</h3>
<p v-author>学习前端</p> //网页显示会加上-itbaizh
</template>
<script setup>
const vAuthor = { //需要使用vAuthor关键字
mounted: (element) => {
element.innerHTML = element.innerHTML + "-itbaizhan"
}
}
</script>
全局和局部自定义指令
自定义指令是区分全局和局部注册,在全局注册,可以在任意组件中使用,局部注册,只在当前组件中使用。
上节里面的那种只能在当前组件中使用,就是局部注册。
全局注册(代码放在main.js中):
//main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 全局自定义指令
app.directive("iblue", {
mounted(element){
element.style.color = "blue"
}
})
app.mount('#app')
//app.vue 其它所有给件都可以这样使用v-blue自定义指令
<template>
<DirectiveScoped />
<p v-blue>大家好,我是蓝色的</p>
</template>
<script setup>
import DirectiveScoped from "./components/DirectiveScoped.vue"
</script>
自定义指令的钩子函数
自定义指令有很多钩子函数,我们可以理解为是自定义指令的生命周期函数,在不同的情况下会自动调用。
<template>
<h3>自定义指令的钩子函数</h3>
<p v-red v-if="flag">{{ message }}</p>
<button @click="updateHandler">修改内容</button>
<button @click="delHandler">删除元素</button>
</template>
<script setup>
import { ref } from "vue"
const message = ref("红色效果")
const flag = ref(true)
function updateHandler(){
message.value = "修改 红色效果"
}
function delHandler(){
flag.value = false
}
const vRed = {
//在绑定元素的attribute前
//或事件监听器应用前调用
//比生命周期函数少了一个beforeCreate()函数
created(el, binding, vnode, prevVnode) {
console.log("created");
},
//在元素被插入到DOM前调用
beforeMount(el, binding, vnode, prevVnode) {
console.log("beforeMount");
},
//在绑定元素的父组件
//及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {
console.log("mounted");
},
//绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {
console.log("beforeUpdate");
},
//在绑定元素的父组件
//及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {
console.log("updated");
},
//绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {
console.log("beforeUnmount");
},
//绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode){
console.log("unmounted")
}
}
</script>
自定义指令的钩子函数
常用的参数是el和binding的value、arg值,其它基本用不上。
el就是html元素本身。
<template>
<h3>自定义指令钩子函数参数</h3>
<p v-myShow:foo="flag">{{ message }}</p> //binding的value值就是其中flag的值,arg就是foo
<button @click="updateHandler">切换</button>
</template>
<script setup>
import { ref } from "vue"
const message = ref("模拟v-show指令")
const flag = ref(true) //根据是true还是flase来切换是否显示
function updateHandler(){
flag.value = flag.value === true ? false : true
}
// 模拟v-show的指令
const vMyShow = {
// mounted(el, binding){
// binding.value === true ? el.style.display = "block" : el.style.display = "none"
// },
updated(el, binding){ //这里要写在updated中,不然点切换按钮没反应
binding.value === true ? el.style.display = "block" : el.style.display = "none"
}
}
</script>