
目录
对于数据比较多的列表(比如1000+),建议使用非响应式数据可显著提升性能
在Vue开发中,合理管理数据的响应性对于性能优化和代码可维护性至关重要。下面我们将讲下如何根据需求让数据变成响应式或非响应式。
一、为什么要控制数据响应性?
Vue的响应式系统会自动追踪依赖关系,在数据变化时更新视图。但并非所有数据都需要响应式:
-
性能考虑:响应式数据需要额外开销(创建getter/setter、依赖追踪)
-
安全需求:防止意外修改重要配置数据
-
特殊场景:大型数据集合、第三方库集成、常量数据
二、如何创建非响应式数据?
1. 在组件选项外定义数据
// 非响应式数据 - 在组件外部定义
const staticConfig = {
appName: "Vue Admin",
version: "1.0.0"
};
export default {
data() {
return {
// 响应式数据
user: { name: "Alice", role: "admin" }
}
},
created() {
console.log(staticConfig.appName); // 访问非响应式数据
}
}
2. 使用Object.freeze()冻结对象
| 主要用于全局的配置 | Object.freeze | 防止意外修改,提高安全性 |
export default {
data() {
return {
// 冻结重要配置对象
appConfig: Object.freeze({
apiBaseUrl: "https://api.example.com",
maxRetries: 3,
timeout: 5000
}),
// 注意:嵌套对象不会被自动冻结
nestedData: Object.freeze({
settings: {
theme: "dark" // 这个嵌套对象仍可修改!
}
})
}
},
methods: {
updateConfig() {
// 尝试修改冻结对象 - 在严格模式下会抛出错误
this.appConfig.apiBaseUrl = "https://new-api.example.com"; // 无效
// 嵌套对象仍可修改
this.nestedData.settings.theme = "light"; // 成功修改!
}
}
}
3. 深度冻结解决方案
其实就是对传入的对象递归使用了Object.freeze(),封装成了一个函数。
// 深度冻结函数
function deepFreeze(obj) {
Object.freeze(obj);
for (const key in obj) {
if (obj.hasOwnProperty(key) &&
typeof obj[key] === 'object' &&
obj[key] !== null) {
deepFreeze(obj[key]);
}
}
return obj;
}
export default {
data() {
return {
// 深度冻结的配置
secureConfig: deepFreeze({
apiKeys: {
public: "pk_123456",
private: "sk_abcdef" // 现在这个也无法修改了
},
permissions: ["read", "write"]
})
}
}
}
函数优化:
/**
* 深度冻结对象,使其所有层级的属性都不可修改
* @param {object} object - 需要冻结的目标对象
* @returns {object} 深度冻结后的对象
*/
function deepFreeze(object) {
// 如果对象已经被冻结,直接返回(避免重复操作)
if (Object.isFrozen(object)) {
return object;
}
// 获取对象自身所有属性名(包括不可枚举属性)
const propNames = Object.getOwnPropertyNames(object);
// 递归冻结所有属性
for (const name of propNames) {
const value = object[name];
// 递归冻结对象或数组(排除函数和null)
if (value && (typeof value === 'object' || typeof value === 'function')) {
// 特殊处理:避免冻结内置对象原型(如Array.prototype)
if (Object.getPrototypeOf(value) === Object.prototype ||
Array.isArray(value)) {
deepFreeze(value);
}
}
}
// 使用Object.freeze冻结对象本身
return Object.freeze(object);
}
三、如何创建响应式数据?
Vue 2 响应式实现
定义在data里面即可,或者使用监听、计算属性(后面两者vue3的也同理,只是写法上有点不一样)
export default {
// 1. data选项 - 自动响应式
data() {
return {
count: 0,
user: {
name: "Bob",
preferences: {
theme: "light",
notifications: true
}
}
}
},
// 2. computed计算属性 - 基于依赖自动更新
computed: {
formattedUser() {
return `${this.user.name} (${this.user.preferences.theme} theme)`;
},
isAdmin() {
return this.user.role === "admin";
}
},
// 3. watch侦听器 - 响应数据变化
watch: {
count(newVal, oldVal) {
console.log(`Count changed from ${oldVal} to ${newVal}`);
},
// 深度监听对象变化
user: {
handler(newUser) {
console.log("User data updated:", newUser);
},
deep: true
}
},
methods: {
increment() {
this.count++; // 响应式更新
},
toggleTheme() {
// 嵌套属性变更也会触发响应式更新
this.user.preferences.theme =
this.user.preferences.theme === "light" ? "dark" : "light";
}
}
}
Vue 3 响应式实现
这里特别要注意的是解构后,数据会丢失响应性,需要使用 toRef 或者 toRefs 。
import { ref, reactive, computed, watch, toRefs } from 'vue';
export default {
setup() {
// 1. ref - 用于基本类型
const count = ref(0);
// 2. reactive - 用于对象
const user = reactive({
name: "Eva",
role: "editor",
preferences: {
theme: "dark",
fontSize: 16
}
});
// 3. computed - 计算属性
const userInfo = computed(() => {
return `${user.name} [${user.role}]`;
});
// 4. watch - 侦听器
watch(count, (newVal, oldVal) => {
console.log(`Count updated: ${oldVal} → ${newVal}`);
});
// 深度侦听对象
watch(
() => user.preferences,
(newPrefs) => {
console.log("Preferences changed:", newPrefs);
},
{ deep: true }
);
// 5. 解构问题 - 丢失响应性
// 错误方式 - 解构会丢失响应性
const { name, role } = user;
// 正确方式 - 使用toRefs保持响应性
const { name: userName, role: userRole } = toRefs(user);
// 处理单个属性
const theme = toRef(user.preferences, 'theme');
return {
count,
user,
userInfo,
userName, // 保持响应性
userRole, // 保持响应性
theme
};
}
}
四、响应式数据使用场景对比
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| UI状态管理 | ref/reactive (Vue3)data (Vue2) | 需要响应式更新视图 |
| 全局配置 | Object.freeze | 防止意外修改,提高安全性 |
| 大型数据集合 | 非响应式数据 + 手动更新 | 避免响应式系统开销 |
| 表单数据 | reactive (Vue3)data (Vue2) | 需要双向绑定和验证 |
| API响应数据 | ref + Object.freeze | 初始渲染后通常不需要变更 |
| 常量/枚举 | 模块级常量 | 不需要响应式,减少开销 |
五、部分场景及注意事项
-
对于数据比较多的列表(比如1000+),建议使用非响应式数据可显著提升性能
-
Vue 3解构技巧:
// 从reactive对象中提取单个属性 import { reactive, toRef } from 'vue'; const state = reactive({ loading: false, error: null }); // 保持响应性 const loading = toRef(state, 'loading'); const error = toRef(state, 'error'); -
响应式数据转换:
// 将普通JavaScript对象转换为响应式对象 const plainObject = { id: 1, name: "Object" }; const reactiveObject = reactive(plainObject); // 使用readonly创建只读响应式对象 import { readonly } from 'vue'; const readOnlyData = readonly(reactiveObject); -
数组处理注意事项:
//Vue 2z中需要使用$set()或者修改数组的7种方法 this.$set(this.items, index, newValue); this.items.splice(index, 1, newValue); // Vue 3中直接操作即可,解决了vue2无法获取数组下标的问题 reactiveArray[index] = newValue; -
响应式原理差异:
-
Vue 2使用
Object.defineProperty,无法检测属性添加/删除 -
Vue 3使用
Proxy,可以检测各种变化类型
-
(后面有时间再搞一个关于上面讲到的vue2和vue3的响应式的区别,vue3解决了一些vue2原本存在的一些问题,就是为啥推荐用vue3的原因之一。)
通过合理运用,你可以优化Vue应用性能,提高代码可维护性,并避免常见的响应式陷阱。
记住,没有"一刀切"的方案,最佳选择取决于你的具体使用场景和需求。
----------到底啦----------
5308

被折叠的 条评论
为什么被折叠?



