Vue3 的 reactive 赋值问题
2023/7/10大约 3 分钟
问题描述
在 Vue3 开发中,遇到一个问题:直接对使用 reactive
创建的对象本身进行赋值时,不会触发页面更新。
原因分析
响应式原理
当我们直接替换整个 reactive
对象时,实际上是让变量指向了一个新的普通对象,这个新对象没有 Proxy
代理,因此不再具有响应性。
实际案例
案例一:筛选表单重置功能
// 创建响应式表单对象
const filterForm = reactive({
createDate: null,
q: null,
});
// 错误做法:直接替换整个对象
function reset() {
filterForm = {
createDate: null,
q: null,
};
// 这行代码执行后,filterForm 不再是响应式的
}
案例二:弹窗数据回显
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
data: {
type: Object,
default: {},
},
});
const visible = ref(false);
const modalData = reactive({});
// 错误做法:直接替换整个 reactive 对象
watch(
() => props.visible,
(newVal) => {
if (newVal) {
modalData = { ...props.data };
// Vue DevTools 中显示 modalData 仍然是空对象
}
visible.value = newVal;
}
);
解决方案
方案一:单个属性赋值
对于简单对象,直接修改其属性而不是替换整个对象:
const userInfo = reactive({
name: "John",
age: 30,
});
// 正确:修改属性
userInfo.name = "Mike";
userInfo.age = 25;
// 表单重置示例
function reset() {
filterForm.createDate = null;
filterForm.q = null;
}
方案二:使用嵌套对象结构
对于需要整体替换的数据,可以在外层包装一个对象:
const state = reactive({
userData: {},
config: {},
});
// 可以整体替换嵌套对象
state.userData = { name: "Alice", role: "admin" };
state.config = { theme: "dark", language: "en" };
// 弹窗数据示例
const modalData = reactive({
data: {}
});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
// 保持 reactive 对象引用不变,只修改其内部属性
modalData.data = { ...props.data };
}
visible.value = newVal;
}
);
方案三:使用 ref 替代 reactive
对于可能需要整体替换的数据,使用 ref
可能更加合适:
const userData = ref({});
// 可以直接修改 ref 的 value
userData.value = { name: "Bob", age: 28 };
// 弹窗数据示例
const modalData = ref({});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
// 对于 ref 对象,可以直接修改其 value 属性
modalData.value = { ...props.data };
}
visible.value = newVal;
}
);
特殊情况:数组操作
reactive 数组也有类似的问题,需要特别注意:
const items = reactive([]);
// 错误做法:直接替换数组
items = [1, 2, 3]; // 失去响应性
// 正确做法:使用数组方法或修改数组属性
items.push(1, 2, 3); // 正确
items.length = 0; // 清空数组
items[0] = 100; // 修改特定元素
优化建议
- 提取初始值,便于维护
// 优化表单重置功能
const initialForm = {
createDate: null,
q: null,
};
const filterForm = reactive({ ...initialForm });
function reset() {
// 使用 Object.assign 批量重置
Object.assign(filterForm, initialForm);
}
- 使用组合式函数封装逻辑
// 封装弹窗数据处理逻辑
function useModalData(initialData = {}) {
const modalData = ref({ ...initialData });
const setModalData = (data) => {
modalData.value = { ...data };
};
const resetModalData = () => {
modalData.value = { ...initialData };
};
return { modalData, setModalData, resetModalData };
}
// 在组件中使用
const { modalData, setModalData, resetModalData } = useModalData();
watch(
() => props.visible,
(newVal) => {
if (newVal) {
setModalData(props.data);
} else {
resetModalData();
}
visible.value = newVal;
}
);
最佳实践
明确选择 reactive 和 ref:
- 对于复杂对象,优先使用
reactive
- 对于简单类型或可能需要整体替换的对象,使用
ref
- 对于复杂对象,优先使用
保持引用稳定:
- 避免直接替换
reactive
创建的对象 - 优先修改对象的属性而不是整个对象
- 避免直接替换
使用 toRefs 解构:
- 当需要解构
reactive
对象时,使用toRefs
保持响应性
- 当需要解构
import { reactive, toRefs } from "vue";
const user = reactive({
name: "Tom",
age: 25,
});
// 使用 toRefs 解构,保持响应性
const { name, age } = toRefs(user);
- 使用 Object.assign:
- 当需要合并对象时,使用
Object.assign
而不是替换整个对象
- 当需要合并对象时,使用
const state = reactive({ a: 1, b: 2 });
const newData = { b: 3, c: 4 };
// 正确:合并对象
Object.assign(state, newData);
更新日志
2025/9/28 09:03
查看所有更新日志
38e56
-于