Vue响应性系统Skill vue-reactivity-system

Vue响应性系统是Vue.js框架的核心部分,基于JavaScript Proxies实现数据响应式更新,包括ref、reactive、computed、watch等API,用于前端开发中的状态管理、性能优化和复杂数据流处理,关键词包括Vue、响应性、ref、reactive、computed、watch、状态管理、前端开发。

前端开发 0 次安装 0 次浏览 更新于 3/25/2026

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的响应性内部机制
  • 调试响应性问题
  • 构建响应式可组合
  • 大规模应用程序,具有复杂数据流

响应性最佳实践

  1. 对原始值使用ref - 始终将原始值包装在ref中
  2. 对对象使用reactive - 深层响应性用于复杂状态
  3. 对派生状态使用computed - 缓存和响应式
  4. 对副作用使用watch - API调用、localStorage等
  5. 对简单效果使用watchEffect - 自动跟踪依赖
  6. 不解构响应式对象 - 使用toRefs保持响应性
  7. 使用readonly防止突变 - 保护共享状态
  8. 正确清理效果 - 返回清理函数或使用onCleanup
  9. 避免深度监视所有内容 - 性能影响
  10. 对大数据使用shallowRef/shallowReactive - 更好的性能

常见响应性陷阱

  1. 解构响应式对象 - 不使用toRefs会失去响应性
  2. 忘记ref的.value - 常见的bug来源
  3. 替换响应式对象 - 破坏响应性,使用ref代替
  4. 深度监视性能 - 对于大型对象可能缓慢
  5. 不清理监视器 - 内存泄漏
  6. 在初始化前访问refs - 可能未定义
  7. 突变props - props是只读的
  8. 不必要的computed - 如果不是派生状态,使用常规refs
  9. 同步效果 - 通常应为异步
  10. 不理解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

资源