Appearance
Vue.js 响应式系统
Vue.js 的响应式系统是其核心特性之一,它使得数据变化能够自动更新到视图层。理解响应式系统的工作原理对于深入使用Vue.js至关重要。
响应式原理
Vue 2 的响应式实现
在 Vue 2 中,响应式系统基于 Object.defineProperty() 实现:
javascript
// Vue 2 响应式原理示例
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
observe(val);
const dep = new Dep(); // 依赖收集器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
// 依赖收集
if (Dep.target) {
dep.depend();
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 通知更新
dep.notify();
}
});
}
function observe(value) {
if (!value || typeof value !== 'object') {
return;
}
// 遍历对象的所有属性
Object.keys(value).forEach(key => {
defineReactive(value, key, value[key]);
});
}
Vue 3 的响应式实现
Vue 3 使用 Proxy 和 Reflect 实现了更强大的响应式系统:
javascript
// Vue 3 响应式原理示例
function reactive(target) {
if (!isObject(target)) {
return target;
}
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 只有值真正改变时才触发更新
if (hasChanged(value, oldValue)) {
// 触发更新
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
trigger(target, key);
return result;
}
});
}
响应式 API
reactive()
reactive() 函数将一个对象转换为响应式对象:
vue
<template>
<div>
<p>计数器: {{ state.count }}</p>
<p>双倍: {{ state.double }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
name: 'Vue 3'
});
// 使用计算属性
const double = computed(() => state.count * 2);
// 添加到响应式对象中
state.double = double;
const increment = () => {
state.count++;
};
return {
state,
increment
};
}
}
</script>
ref()
ref() 函数创建一个响应式引用:
vue
<template>
<div>
<p>计数器: {{ count }}</p>
<p>双倍: {{ doubleCount }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
return {
count,
doubleCount,
increment
};
}
}
</script>
toRefs()
toRefs() 函数将响应式对象转换为普通对象,其中每个属性都是指向原始对象相应属性的 ref:
vue
<template>
<div>
<p>姓名: {{ name }}</p>
<p>年龄: {{ age }}</p>
<button @click="updateUser">更新用户</button>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
const user = reactive({
name: 'John',
age: 30
});
const updateUser = () => {
user.name = 'Jane';
user.age = 25;
};
// 将响应式对象转换为ref
return {
...toRefs(user),
updateUser
};
}
}
</script>
深层响应式 vs 浅层响应式
深层响应式 (deep reactive)
vue
<template>
<div>
<p>用户信息: {{ user.name }}, {{ user.profile.age }}</p>
<button @click="updateProfile">更新资料</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const user = reactive({
name: 'John',
profile: {
age: 30,
address: {
city: 'New York'
}
}
});
const updateProfile = () => {
// 嵌套对象的属性变化也会触发更新
user.profile.age = 31;
user.profile.address.city = 'Boston';
};
return {
user,
updateProfile
};
}
}
</script>
浅层响应式 (shallow reactive)
vue
<template>
<div>
<p>用户信息: {{ user.name }}</p>
<p>资料年龄: {{ user.profile.age }}</p>
<button @click="updateUser">更新用户</button>
<button @click="updateProfile">更新资料</button>
</div>
</template>
<script>
import { shallowReactive, reactive } from 'vue'
export default {
setup() {
const user = shallowReactive({
name: 'John',
profile: {
age: 30
}
});
const updateUser = () => {
// 这会触发更新(直接属性)
user.name = 'Jane';
};
const updateProfile = () => {
// 这不会触发更新(嵌套对象属性)
// 需要使用 reactive 包装 profile
user.profile.age = 31;
};
return {
user,
updateUser,
updateProfile
};
}
}
</script>
计算属性和侦听器
computed()
计算属性具有缓存特性,只有依赖变化时才会重新计算:
vue
<template>
<div>
<p>原始列表: {{ list.join(', ') }}</p>
<p>过滤列表: {{ filteredList.join(', ') }}</p>
<p>计算属性执行次数: {{ computedCount }}</p>
<button @click="addItem">添加项目</button>
<button @click="filterOdd">过滤奇数</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const shouldFilterOdd = ref(true);
const computedCount = ref(0);
const filteredList = computed(() => {
computedCount.value++;
console.log('计算属性执行');
if (shouldFilterOdd.value) {
return list.value.filter(item => item % 2 === 0);
}
return list.value;
});
const addItem = () => {
list.value.push(list.value.length + 1);
};
const filterOdd = () => {
shouldFilterOdd.value = !shouldFilterOdd.value;
};
return {
list,
filteredList,
computedCount,
addItem,
filterOdd
};
}
}
</script>
watch() 和 watchEffect()
vue
<template>
<div>
<input v-model="query" placeholder="输入查询">
<p>查询: {{ query }}</p>
<p>延迟查询: {{ debouncedQuery }}</p>
<p>搜索结果: {{ results }}</p>
</div>
</template>
<script>
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const query = ref('');
const debouncedQuery = ref('');
const results = ref([]);
// watchEffect - 自动追踪依赖
watchEffect(async () => {
if (debouncedQuery.value) {
// 模拟API调用
results.value = await searchAPI(debouncedQuery.value);
} else {
results.value = [];
}
});
// watch - 显式指定监听源
let timeoutId;
watch(query, (newQuery) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
debouncedQuery.value = newQuery;
}, 300);
});
// 清理副作用
onUnmounted(() => {
clearTimeout(timeoutId);
});
return {
query,
debouncedQuery,
results
};
}
}
// 模拟API调用
function searchAPI(query) {
return new Promise(resolve => {
setTimeout(() => {
resolve([`结果1 for ${query}`, `结果2 for ${query}`, `结果3 for ${query}`]);
}, 100);
});
}
</script>
响应式系统最佳实践
避免的问题
vue
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }} - {{ item.completed ? '完成' : '未完成' }}
<button @click="toggleItem(item)">切换</button>
</li>
</ul>
<button @click="addItem">添加项目</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const items = ref([
{ id: 1, name: '项目1', completed: false },
{ id: 2, name: '项目2', completed: true }
]);
// 正确的数组更新方法
const toggleItem = (item) => {
// 方法1: 使用索引更新
const index = items.value.findIndex(i => i.id === item.id);
if (index !== -1) {
items.value[index].completed = !items.value[index].completed;
}
// 方法2: 使用解构创建新对象
// items.value = items.value.map(i =>
// i.id === item.id ? { ...i, completed: !i.completed } : i
// );
};
const addItem = () => {
const newItem = {
id: Date.now(), // 确保唯一ID
name: `项目${items.value.length + 1}`,
completed: false
};
items.value.push(newItem);
};
return {
items,
toggleItem,
addItem
};
}
}
</script>
性能优化
vue
<template>
<div>
<input v-model="searchTerm" placeholder="搜索">
<div v-for="item in displayedItems" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue'
export default {
setup() {
const allItems = ref(Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `项目${i}`,
category: i % 3 === 0 ? 'A' : i % 3 === 1 ? 'B' : 'C'
})));
const searchTerm = ref('');
// 使用计算属性进行过滤,利用缓存
const filteredItems = computed(() => {
if (!searchTerm.value) return allItems.value;
return allItems.value.filter(item =>
item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
);
});
// 分页显示,避免一次性渲染大量DOM
const currentPage = ref(0);
const pageSize = 50;
const displayedItems = computed(() => {
const start = currentPage.value * pageSize;
return filteredItems.value.slice(start, start + pageSize);
});
// 监听搜索词变化,重置页面
watch(searchTerm, () => {
currentPage.value = 0;
});
return {
searchTerm,
displayedItems,
filteredItems
};
}
}
</script>
响应式系统高级用法
自定义 ref
javascript
// 自定义防抖 ref
import { customRef } from 'vue'
export function useDebounce(value, delay = 200) {
let timeout;
return customRef((track, trigger) => {
return {
get() {
track(); // 追踪依赖
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger(); // 触发更新
}, delay);
}
};
});
}
// 在组件中使用
<template>
<input v-model="debouncedValue" placeholder="防抖输入">
<p>实际值: {{ debouncedValue }}</p>
</template>
<script>
import { ref } from 'vue'
import { useDebounce } from './composables/useDebounce'
export default {
setup() {
const value = ref('');
const debouncedValue = useDebounce(value, 500);
return {
debouncedValue
};
}
}
</script>
Vue.js 的响应式系统是其强大功能的核心,通过深入理解其工作原理和最佳实践,开发者可以构建出高性能、可维护的应用程序。