Vue 3 setup
函数:响应式状态与副作用管理
大家好,今天我们来深入探讨 Vue 3 的核心概念之一 —— setup
函数,以及如何利用它来有效地管理组件的响应式状态和副作用。在 Vue 3 中,setup
函数是 Composition API 的入口点,它允许我们以更灵活、更可维护的方式组织组件逻辑。
setup
函数的本质
setup
函数是一个在组件创建之前执行的函数。它接收两个参数:
props
: 组件的 props 对象,只读,在setup
函数内部不能直接修改。context
: 一个包含以下属性的上下文对象:attrs
: 组件的非 prop attribute (类似于$attrs
)slots
: 组件的插槽 (类似于$slots
)emit
: 用于触发自定义事件的函数 (替代了$emit
)
setup
函数的返回值决定了组件的模板可以访问哪些数据和方法。它可以返回一个对象,对象中的属性会被合并到组件的渲染上下文中,也可以返回一个渲染函数,直接控制组件的渲染。
响应式状态的管理
在 Vue 3 中,我们使用 reactive
和 ref
函数来创建响应式状态。
reactive
: 用于创建对象的响应式副本。任何对响应式对象的修改都会触发视图更新。ref
: 用于创建持有任何类型值的响应式 引用。通常用于基本数据类型和更细粒度的响应式控制。
示例:使用 reactive
创建响应式对象
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
message: 'Hello Vue 3!'
});
const increment = () => {
state.count++;
};
return {
state,
increment
};
},
template: `
<div>
<p>{{ state.message }}</p>
<p>Count: {{ state.count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
在这个例子中,state
是一个响应式对象,包含了 count
和 message
两个属性。当 count
的值发生改变时,模板会自动更新。
示例:使用 ref
创建响应式引用
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const message = ref('Hello Vue 3!');
const increment = () => {
count.value++; // 注意:需要通过 .value 访问 ref 的值
};
return {
count,
message,
increment
};
},
template: `
<div>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
这里,count
和 message
都是响应式引用。我们需要通过 .value
属性来访问和修改它们的值。
reactive
vs. ref
的选择
特性 | reactive |
ref |
---|---|---|
数据类型 | 对象 | 任意类型 |
访问方式 | 直接访问属性 | 通过 .value 访问属性 |
应用场景 | 管理多个相关联的状态,构建复杂的数据结构 | 管理单个状态,如计数器、开关状态等,或基本类型 |
解构 | 解构后失去响应性 (除非使用 toRefs ) |
解构后仍然保持响应性 |
toRefs
:保持解构后的响应性
当我们需要解构 reactive
对象,并且仍然保持解构后的属性的响应性时,可以使用 toRefs
函数。
import { reactive, toRefs } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
message: 'Hello Vue 3!'
});
const { count, message } = toRefs(state);
const increment = () => {
state.count++; // 修改 state 仍然会更新 count
};
return {
count,
message,
increment
};
},
template: `
<div>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
在这个例子中,count
和 message
都是响应式引用,即使它们是通过解构 state
对象得到的。toRefs
会将 state
对象的每个属性转换为 ref
,并返回一个包含这些 ref
的对象。
副作用的管理
副作用是指在组件生命周期中发生的与 UI 无关的操作,例如:
- 数据请求
- DOM 操作
- 定时器
- 事件监听
在 Vue 3 中,我们使用 watch
和 watchEffect
函数来管理副作用。
watch
: 用于监听一个或多个响应式状态,并在状态改变时执行回调函数。watchEffect
: 用于立即执行一个回调函数,并自动追踪回调函数中使用的所有响应式状态。当这些状态发生改变时,回调函数会重新执行。
示例:使用 watch
监听响应式状态
import { ref, watch } from 'vue';
export default {
setup() {
const count = ref(0);
const message = ref('');
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
message.value = `Count is now ${newValue}`;
});
const increment = () => {
count.value++;
};
return {
count,
message,
increment
};
},
template: `
<div>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
在这个例子中,watch
函数监听 count
的变化。当 count
的值发生改变时,回调函数会被执行,并将新的值和旧的值作为参数传递给回调函数。
示例:使用 watchEffect
自动追踪依赖
import { ref, watchEffect } from 'vue';
export default {
setup() {
const count = ref(0);
const message = ref('');
watchEffect(() => {
console.log(`Count is ${count.value}`);
message.value = `Count is ${count.value}`;
});
const increment = () => {
count.value++;
};
return {
count,
message,
increment
};
},
template: `
<div>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
在这个例子中,watchEffect
函数会自动追踪回调函数中使用的 count
变量。当 count
的值发生改变时,回调函数会被重新执行。
watch
vs. watchEffect
的选择
特性 | watch |
watchEffect |
---|---|---|
依赖追踪 | 需要手动指定监听的响应式状态 | 自动追踪回调函数中使用的响应式状态 |
执行时机 | 只有当监听的响应式状态发生改变时才会执行回调函数 | 立即执行一次回调函数,并在依赖的响应式状态发生改变时重新执行回调函数 |
用途 | 监听特定的状态变化,执行特定的操作,例如数据请求、复杂的 DOM 操作等 | 响应状态变化,自动执行相应的副作用,例如更新 UI、日志输出等 |
惰性执行 | 默认惰性执行,即初始时不执行回调函数,除非设置 immediate: true |
立即执行 |
获取旧值 | 可以获取旧值和新值 | 只能获取新值 |
停止监听
watch
和 watchEffect
函数都会返回一个停止监听的函数。当组件卸载时,我们需要调用这些函数来停止监听,以避免内存泄漏。
import { ref, watchEffect, onUnmounted } from 'vue';
export default {
setup() {
const count = ref(0);
const stop = watchEffect(() => {
console.log(`Count is ${count.value}`);
});
onUnmounted(() => {
stop(); // 组件卸载时停止监听
});
const increment = () => {
count.value++;
};
return {
count,
increment
};
},
template: `
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
在这个例子中,onUnmounted
函数会在组件卸载时被调用,我们可以在这个函数中调用 stop
函数来停止监听。
onMounted
、onUpdated
、onUnmounted
等生命周期钩子
在 setup
函数中,我们可以使用 onMounted
、onUpdated
、onUnmounted
等生命周期钩子来执行与组件生命周期相关的操作。这些钩子函数都接受一个回调函数作为参数,回调函数会在对应的生命周期阶段被调用。
onMounted
: 组件挂载后执行onUpdated
: 组件更新后执行onUnmounted
: 组件卸载前执行onErrorCaptured
: 当捕获到来自子组件的错误时调用此钩子。onRenderTracked
: 组件渲染跟踪依赖时触发onRenderTriggered
: 组件渲染被触发时触发
使用 computed
计算属性
computed
函数用于创建计算属性。计算属性的值是根据其他响应式状态计算得出的,并且会被缓存。只有当依赖的响应式状态发生改变时,计算属性的值才会重新计算。
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
return {
firstName,
lastName,
fullName
};
},
template: `
<div>
<p>First Name: {{ firstName }}</p>
<p>Last Name: {{ lastName }}</p>
<p>Full Name: {{ fullName }}</p>
</div>
`
};
在这个例子中,fullName
是一个计算属性,它的值是根据 firstName
和 lastName
计算得出的。当 firstName
或 lastName
的值发生改变时,fullName
的值会自动更新。
使用 readonly
创建只读的响应式对象
readonly
函数用于创建一个只读的响应式对象。对只读对象的任何修改都会导致警告。
import { reactive, readonly } from 'vue';
export default {
setup() {
const state = reactive({
count: 0
});
const readonlyState = readonly(state);
const increment = () => {
// readonlyState.count++; // 报错:Attempting to mutate a readonly value.
state.count++; // 允许修改原始的 state 对象
};
return {
readonlyState,
increment
};
},
template: `
<div>
<p>Count: {{ readonlyState.count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
在这个例子中,readonlyState
是一个只读的响应式对象。我们不能直接修改 readonlyState.count
的值,但是可以修改原始的 state.count
的值。
封装可复用的逻辑:Composables
Composables 是指包含可在多个组件中复用的状态逻辑的函数。它们可以封装响应式状态、副作用和计算属性,并在不同的组件中共享。
示例:创建一个简单的计数器 Composable
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
return {
count,
increment,
decrement
};
}
在组件中使用 Composable
import { useCounter } from './useCounter';
export default {
setup() {
const { count, increment, decrement } = useCounter(10);
return {
count,
increment,
decrement
};
},
template: `
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
`
};
通过使用 Composables,我们可以将组件的逻辑拆分成更小的、可复用的单元,从而提高代码的可维护性和可测试性。
总结状态与副作用管理
setup
函数为 Vue 3 组件提供了强大的状态管理和副作用处理能力。通过 reactive
和 ref
可以创建响应式状态,watch
和 watchEffect
用于处理副作用,而 Composables 则使得逻辑复用成为可能。掌握这些概念是构建高效、可维护的 Vue 3 应用的关键。