name: vue-reactivity-system user-invocable: false description: 当使用Vue响应性系统,包括refs、reactive、computed和watchers时使用。当管理Vue应用程序中的复杂状态时使用。 allowed-tools:
- Bash
- Read
Vue响应性系统
掌握Vue的响应性系统,以构建响应式、高性能的应用程序,实现最优状态管理和计算属性。
响应性基础(基于Proxy)
Vue 3使用JavaScript Proxies实现响应性:
import { ref, reactive, isRef, isReactive, isProxy } from 'vue';
// ref创建响应式包装器
const count = ref(0);
console.log(isRef(count)); // true
console.log(isProxy(count)); // false(ref本身不是proxy)
console.log(isProxy(count.value)); // false对于原始值
// reactive创建proxy
const state = reactive({ count: 0 });
console.log(isReactive(state)); // true
console.log(isProxy(state)); // true
// Proxies跟踪访问和突变
state.count++; // 触发响应性
count.value++; // 触发响应性
Ref - 响应式原始值和对象
基本Ref使用
import { ref } from 'vue';
// 原始值
const count = ref(0);
const name = ref('John');
const isActive = ref(true);
// 通过.value访问
console.log(count.value); // 0
count.value++; // 更新触发响应性
// 对象(包装在proxy中)
const user = ref({
name: 'John',
age: 30
});
// 嵌套属性是响应式的
user.value.age++; // 触发响应性
// 可以替换整个对象
user.value = { name: 'Jane', age: 25 }; // 有效!
浅层Ref
import { shallowRef, triggerRef } from 'vue';
// 仅.value是响应式的,嵌套属性不是
const state = shallowRef({
count: 0,
nested: { value: 0 }
});
// 这触发响应性
state.value = { count: 1, nested: { value: 1 } };
// 这不触发响应性
state.value.count++; // 无更新!
// 手动触发
state.value.count++;
triggerRef(state); // 强制更新
自定义Ref
import { customRef } from 'vue';
// 防抖ref
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: ReturnType<typeof setTimeout>;
return customRef((track, trigger) => ({
get() {
track(); // 告诉Vue跟踪这个
return value;
},
set(newValue: T) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger(); // 告诉Vue重新渲染
}, delay);
}
}));
}
// 使用
const searchQuery = useDebouncedRef('', 300);
// 更新是防抖的
searchQuery.value = 'a'; // 不立即触发
searchQuery.value = 'ab'; // 仍在等待
searchQuery.value = 'abc'; // 300毫秒后触发
Reactive - 深层响应式对象
基本Reactive使用
import { reactive } from 'vue';
// 创建深层响应式对象
const state = reactive({
user: {
name: 'John',
profile: {
email: 'john@example.com',
settings: {
theme: 'dark'
}
}
},
posts: []
});
// 所有嵌套属性都是响应式的
state.user.profile.settings.theme = 'light'; // 触发响应性
state.posts.push({ id: 1, title: 'Post' }); // 触发响应性
浅层Reactive
import { shallowReactive } from 'vue';
// 仅根级属性是响应式的
const state = shallowReactive({
count: 0,
nested: { value: 0 }
});
// 这触发响应性
state.count++; // 有效
// 这不触发响应性
state.nested.value++; // 无更新!
// 但替换有效
state.nested = { value: 1 }; // 触发响应性
响应式数组
import { reactive } from 'vue';
const list = reactive<number[]>([]);
// 突变方法触发响应性
list.push(1); // 响应式
list.pop(); // 响应式
list.splice(0, 1); // 响应式
list.sort(); // 响应式
list.reverse(); // 响应式
// 替换触发响应性
const newList = reactive([1, 2, 3]);
响应式集合
import { reactive } from 'vue';
// Map
const map = reactive(new Map<string, number>());
map.set('count', 0); // 响应式
map.delete('count'); // 响应式
// Set
const set = reactive(new Set<number>());
set.add(1); // 响应式
set.delete(1); // 响应式
// WeakMap和WeakSet
const weakMap = reactive(new WeakMap());
const weakSet = reactive(new WeakSet());
Readonly - 防止突变
import { reactive, readonly, isReadonly } from 'vue';
const state = reactive({ count: 0 });
const readonlyState = readonly(state);
console.log(isReadonly(readonlyState)); // true
// 不能突变
readonlyState.count++; // 开发模式下警告
// 原始对象仍可变
state.count++; // 有效,同时更新只读视图
// 深层只读
const deepState = reactive({
nested: { value: 0 }
});
const deepReadonly = readonly(deepState);
deepReadonly.nested.value++; // 警告!深层只读
ToRef和ToRefs - 保持响应性
ToRefs - 将Reactive转换为Refs
import { reactive, toRefs } from 'vue';
const state = reactive({
count: 0,
name: 'John'
});
// 解构会失去响应性
const { count, name } = state; // 非响应式!
// 使用toRefs保持响应性
const { count: countRef, name: nameRef } = toRefs(state);
// 现在响应式
countRef.value++; // 更新state.count
console.log(state.count); // 1
ToRef - 从属性创建Ref
import { reactive, toRef } from 'vue';
const state = reactive({
count: 0
});
// 创建特定属性的ref
const countRef = toRef(state, 'count');
countRef.value++; // 更新state.count
console.log(state.count); // 1
// 不存在的属性
const missingRef = toRef(state, 'missing');
missingRef.value = 'now exists'; // 添加到state!
Unref和IsRef - Ref工具
import { ref, unref, isRef } from 'vue';
const count = ref(0);
const plain = 0;
// unref:如果是ref则解包,否则返回值
console.log(unref(count)); // 0
console.log(unref(plain)); // 0
// 用于处理ref或值
function double(value: number | Ref<number>): number {
return unref(value) * 2;
}
double(count); // 0
double(5); // 10
// isRef:检查是否为ref
if (isRef(count)) {
console.log(count.value);
} else {
console.log(count);
}
Computed - 派生状态
基本Computed
import { ref, computed } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
console.log(doubled.value); // 0
count.value = 5;
console.log(doubled.value); // 10
// Computed是缓存的
const expensive = computed(() => {
console.log('Computing...');
return count.value * 2;
});
console.log(expensive.value); // Computing... 0
console.log(expensive.value); // 0(缓存,无日志)
count.value = 1;
console.log(expensive.value); // Computing... 2
可写Computed
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(value) {
[firstName.value, lastName.value] = value.split(' ');
}
});
console.log(fullName.value); // John Doe
fullName.value = 'Jane Smith';
console.log(firstName.value); // Jane
console.log(lastName.value); // Smith
Computed调试
import { ref, computed } from 'vue';
const count = ref(0);
const doubled = computed(
() => count.value * 2,
{
onTrack(e) {
console.log('Tracked:', e);
},
onTrigger(e) {
console.log('Triggered:', e);
}
}
);
Watch - 响应变化
监视单个源
import { ref, watch } from 'vue';
const count = ref(0);
// 基本watch
watch(count, (newValue, oldValue) => {
console.log(`Count: ${oldValue} -> ${newValue}`);
});
// 带选项
watch(
count,
(newValue, oldValue) => {
console.log('Count changed');
},
{
immediate: true, // 立即运行
flush: 'post', // 时机:'pre' | 'post' | 'sync'
onTrack(e) { console.log('Tracked:', e); },
onTrigger(e) { console.log('Triggered:', e); }
}
);
监视多个源
import { ref, watch } from 'vue';
const x = ref(0);
const y = ref(0);
// 监视源数组
watch(
[x, y],
([newX, newY], [oldX, oldY]) => {
console.log(`x: ${oldX} -> ${newX}`);
console.log(`y: ${oldY} -> ${newY}`);
}
);
// 任意变化时触发
x.value++; // 日志
y.value++; // 日志
监视响应式对象
import { reactive, watch } from 'vue';
const state = reactive({
count: 0,
user: { name: 'John' }
});
// 监视getter
watch(
() => state.count,
(newValue, oldValue) => {
console.log('Count changed');
}
);
// 深度监视整个对象
watch(
state,
(newValue, oldValue) => {
console.log('State changed');
},
{ deep: true }
);
// 监视特定嵌套属性
watch(
() => state.user.name,
(newValue, oldValue) => {
console.log('Name changed');
}
);
停止监视
import { ref, watch } from 'vue';
const count = ref(0);
const stop = watch(count, (value) => {
console.log(`Count: ${value}`);
// 当计数达到5时停止监视
if (value >= 5) {
stop();
}
});
// 或外部停止
stop();
WatchEffect - 自动依赖跟踪
import { ref, watchEffect } from 'vue';
const count = ref(0);
const name = ref('John');
// 自动跟踪依赖
watchEffect(() => {
console.log(`${name.value}: ${count.value}`);
});
// 日志:John: 0
count.value++; // 日志:John: 1
name.value = 'Jane'; // 日志:Jane: 1
// 清理
const stop = watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log(count.value);
}, 1000);
// 注册清理
onCleanup(() => {
clearTimeout(timer);
});
});
// 停止监视
stop();
WatchEffect时机
import { ref, watchEffect, watchPostEffect, watchSyncEffect } from 'vue';
const count = ref(0);
// 默认:'pre' - 组件更新前
watchEffect(() => {
console.log('Pre:', count.value);
}, { flush: 'pre' });
// 'post' - 组件更新后(访问更新后的DOM)
watchPostEffect(() => {
console.log('Post:', count.value);
// 可以访问更新后的DOM
});
// 'sync' - 同步(谨慎使用!)
watchSyncEffect(() => {
console.log('Sync:', count.value);
});
Effect Scope - 分组效果
import { effectScope, ref, watch } from 'vue';
const scope = effectScope();
scope.run(() => {
const count = ref(0);
watch(count, () => {
console.log('Count changed');
});
watchEffect(() => {
console.log('Effect');
});
});
// 停止范围内的所有效果
scope.stop();
// 嵌套范围
const parent = effectScope();
parent.run(() => {
const child = effectScope();
child.run(() => {
// 子效果
});
// 仅停止子范围
child.stop();
});
// 停止父范围(及所有子范围)
parent.stop();
响应性工具
Trigger和Scheduler
import { ref, triggerRef } from 'vue';
const count = ref(0);
// 手动触发更新
count.value = 1;
triggerRef(count); // 即使值未改变也强制更新
响应式解包
import { reactive, ref } from 'vue';
const count = ref(0);
const state = reactive({
// Refs在reactive中自动解包
count
});
// 不需要.value
console.log(state.count); // 0(不是state.count.value)
state.count++; // 有效
// 但在数组中,不解包
const list = reactive([ref(0)]);
console.log(list[0].value); // 必须使用.value
何时使用此技能
使用vue-reactivity-system当构建现代、生产就绪的应用程序,需要:
- 复杂状态管理模式
- 细粒度响应性控制
- 通过计算属性优化性能
- 高级监视和效果模式
- 理解Vue的响应性内部机制
- 调试响应性问题
- 构建响应式可组合
- 大规模应用程序,具有复杂数据流
响应性最佳实践
- 对原始值使用
ref- 始终将原始值包装在ref中 - 对对象使用
reactive- 深层响应性用于复杂状态 - 对派生状态使用
computed- 缓存和响应式 - 对副作用使用
watch- API调用、localStorage等 - 对简单效果使用
watchEffect- 自动跟踪依赖 - 不解构响应式对象 - 使用
toRefs保持响应性 - 使用
readonly防止突变 - 保护共享状态 - 正确清理效果 - 返回清理函数或使用
onCleanup - 避免深度监视所有内容 - 性能影响
- 对大数据使用
shallowRef/shallowReactive- 更好的性能
常见响应性陷阱
- 解构响应式对象 - 不使用
toRefs会失去响应性 - 忘记ref的
.value- 常见的bug来源 - 替换响应式对象 - 破坏响应性,使用
ref代替 - 深度监视性能 - 对于大型对象可能缓慢
- 不清理监视器 - 内存泄漏
- 在初始化前访问refs - 可能未定义
- 突变props - props是只读的
- 不必要的computed - 如果不是派生状态,使用常规refs
- 同步效果 - 通常应为异步
- 不理解proxy限制 - 某些操作不跟踪
高级模式
响应式状态模式
import { reactive, readonly, computed } from 'vue';
interface State {
count: number;
items: string[];
}
function createStore() {
const state = reactive<State>({
count: 0,
items: []
});
// 计算属性
const doubled = computed(() => state.count * 2);
// 操作
function increment() {
state.count++;
}
function addItem(item: string) {
state.items.push(item);
}
// 暴露只读状态
return {
state: readonly(state),
doubled,
increment,
addItem
};
}
const store = createStore();
响应式表单状态
import { reactive, computed, watch } from 'vue';
interface FormData {
email: string;
password: string;
}
interface FormErrors {
email?: string;
password?: string;
}
function useForm() {
const data = reactive<FormData>({
email: '',
password: ''
});
const errors = reactive<FormErrors>({});
const isValid = computed(() =>
!errors.email && !errors.password &&
data.email && data.password
);
// 变化时验证
watch(
() => data.email,
(email) => {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errors.email = '无效邮箱';
} else {
delete errors.email;
}
}
);
watch(
() => data.password,
(password) => {
if (password.length < 8) {
errors.password = '必须8+字符';
} else {
delete errors.password;
}
}
);
return {
data,
errors,
isValid
};
}
异步响应式状态
import { ref, watchEffect } from 'vue';
interface User {
id: number;
name: string;
}
function useAsyncData<T>(
fetcher: () => Promise<T>
) {
const data = ref<T | null>(null);
const error = ref<Error | null>(null);
const loading = ref(false);
async function execute() {
loading.value = true;
error.value = null;
try {
data.value = await fetcher();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
}
watchEffect((onCleanup) => {
let cancelled = false;
execute().then(() => {
if (cancelled) {
data.value = null;
}
});
onCleanup(() => {
cancelled = true;
});
});
return { data, error, loading, refetch: execute };
}
响应性注意事项和限制
属性添加/删除
import { reactive } from 'vue';
const state = reactive<{ count?: number }>({});
// 添加新属性是响应式的
state.count = 1; // 响应式
// 但TypeScript除非有正确类型,否则不知道
// 解决方案:预先定义所有属性或使用正确类型
模板中的Ref解包
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(0);
</script>
<template>
<!-- 在模板中自动解包 -->
<p>{{ count }}</p> <!-- 不是count.value -->
<!-- 但在JavaScript表达式中不 -->
<p>{{ count + 1 }}</p> <!-- 不会工作! -->
<p>{{ count.value + 1 }}</p> <!-- 正确 -->
</template>
非响应式值
import { reactive } from 'vue';
// 原始值在reactive中仍是响应式的
const state = reactive({
count: 0 // 响应式
});
// 但提取会失去响应性
let count = state.count; // 非响应式
count++; // 不更新state