Vue 3 中使用 ref
创建的响应式对象数据转换为普通(非响应式)的数据,有以下几种方法:
1. 访问 .value
属性:
这是最直接、最常见的方法。 由于 ref
对象的值存储在其 .value
属性中,直接访问该属性即可获得普通数据。
<script setup>
import { ref } from 'vue';
const myRef = ref({ name: 'John', age: 30 });
// 获取普通对象
const plainObject = myRef.value;
console.log(plainObject); // { name: 'John', age: 30 }
console.log(typeof plainObject); // object
// 修改 plainObject 不会影响 myRef
plainObject.name = 'Jane';
console.log(myRef.value.name); // "John" (myRef 仍然是响应式的,没有被修改)
console.log(plainObject.name); // "Jane"
</script>
- 优点: 简单直接,易于理解。
-
缺点: 如果你需要的是深拷贝,此方法只进行浅拷贝,修改
plainObject
的嵌套对象属性仍然会影响myRef
。
2. 使用展开运算符 (...
) 进行浅拷贝:
如果你需要的是一个新的对象,并且避免修改原始的 ref
对象,可以使用展开运算符创建一个浅拷贝。
<script setup>
import { ref } from 'vue';
const myRef = ref({ name: 'John', age: 30, address: { city: 'New York' } });
// 使用展开运算符进行浅拷贝
const plainObject = { ...myRef.value };
console.log(plainObject); // { name: 'John', age: 30, address: { city: 'New York' } }
// 修改浅拷贝对象的属性
plainObject.name = 'Jane';
console.log(myRef.value.name); // "John"
console.log(plainObject.name); // "Jane"
// 修改嵌套对象属性会影响 myRef,因为是浅拷贝
plainObject.address.city = 'Los Angeles';
console.log(myRef.value.address.city); // "Los Angeles" (仍然会影响 ref 对象)
console.log(plainObject.address.city); // "Los Angeles"
</script>
-
优点: 创建了一个新的对象,修改拷贝后的对象不会直接影响原始的
ref
对象。 -
缺点: 仍然是浅拷贝。如果
ref
对象包含嵌套的对象或数组,修改拷贝后的对象的嵌套属性仍然会影响原始的ref
对象。
3. 使用 JSON.parse(JSON.stringify(obj))
进行深拷贝 (不推荐用于包含函数或 Symbol 的对象):
这是一种常用的创建深拷贝的方法,但不适用于包含函数、Symbol、循环引用的对象。
<script setup>
import { ref } from 'vue';
const myRef = ref({ name: 'John', age: 30, address: { city: 'New York' } });
// 使用 JSON.parse(JSON.stringify()) 进行深拷贝
const plainObject = JSON.parse(JSON.stringify(myRef.value));
console.log(plainObject); // { name: 'John', age: 30, address: { city: 'New York' } }
// 修改深拷贝对象的属性
plainObject.name = 'Jane';
plainObject.address.city = 'Los Angeles';
console.log(myRef.value.name); // "John"
console.log(plainObject.name); // "Jane"
console.log(myRef.value.address.city); // "New York"
console.log(plainObject.address.city); // "Los Angeles"
</script>
-
优点: 可以创建深拷贝,完全隔离原始
ref
对象和拷贝对象。 -
缺点:
- 效率相对较低。
-
无法复制函数、Symbol、
undefined
和循环引用的对象。 函数会被忽略,Symbol 会丢失,undefined 会变成 null, 循环引用会报错。 - Date对象会被转换成字符串。
4. 使用 lodash
的 _.cloneDeep()
函数 (推荐):
lodash
是一个流行的 JavaScript 工具库,提供了很多实用的函数,包括深拷贝。
<script setup>
import { ref } from 'vue';
import _ from 'lodash'; // 需要安装 lodash: npm install lodash
const myRef = ref({ name: 'John', age: 30, address: { city: 'New York' }, func: () => {} });
// 使用 lodash 的 _.cloneDeep() 函数进行深拷贝
const plainObject = _.cloneDeep(myRef.value);
console.log(plainObject); // { name: 'John', age: 30, address: { city: 'New York' }, func: () => {} }
// 修改深拷贝对象的属性
plainObject.name = 'Jane';
plainObject.address.city = 'Los Angeles';
plainObject.func = () => { console.log('new func') }; // 修改函数
console.log(myRef.value.name); // "John"
console.log(plainObject.name); // "Jane"
console.log(myRef.value.address.city); // "New York"
console.log(plainObject.address.city); // "Los Angeles"
</script>
-
优点:
- 可以创建真正的深拷贝,包括函数和 Symbol (取决于 lodash 版本)。
- 能够处理循环引用。
- 经过优化,性能通常比
JSON.parse(JSON.stringify())
更好。
-
缺点: 需要安装额外的库 (
lodash
)。
5. 使用 structuredClone()
(现代浏览器):
structuredClone()
是一个现代浏览器提供的全局函数,用于创建深拷贝。
<script setup>
import { ref } from 'vue';
const myRef = ref({ name: 'John', age: 30, address: { city: 'New York' }, func: () => {} });
// 使用 structuredClone() 进行深拷贝
const plainObject = structuredClone(myRef.value);
console.log(plainObject);
// 修改深拷贝对象的属性
plainObject.name = 'Jane';
plainObject.address.city = 'Los Angeles';
console.log(myRef.value.name); // "John"
console.log(plainObject.name); // "Jane"
console.log(myRef.value.address.city); // "New York"
console.log(plainObject.address.city); // "Los Angeles"
</script>
-
优点:
- 内置于浏览器,无需安装额外的库。
- 可以处理循环引用。
- 支持多种数据类型,包括
Date
、RegExp
、Map
、Set
等。
-
缺点:
- 浏览器兼容性:不是所有浏览器都支持
structuredClone()
(旧版本的浏览器可能不支持). 需要 polyfill 。 - 性能可能不如
lodash
的_.cloneDeep()
,具体取决于数据结构和浏览器实现。 -
无法拷贝函数。 如果对象包含函数,函数属性将会被设置为
null
。
- 浏览器兼容性:不是所有浏览器都支持
总结:
- 如果只需要访问
ref
对象的值,直接使用.value
即可。 - 如果需要创建一个新的对象,并且不需要深拷贝,可以使用展开运算符 (
...
)。 - 如果需要深拷贝,且对象不包含函数、Symbol、循环引用,可以使用
JSON.parse(JSON.stringify())
(但通常不推荐)。 -
最推荐使用
lodash
的_.cloneDeep()
函数进行深拷贝。 它功能强大,能够处理多种情况,性能也不错。 - 如果你的目标环境都是现代浏览器,并且不需要拷贝函数,可以考虑使用
structuredClone()
。
选择哪种方法取决于你的具体需求:
- 是否需要创建一个新的对象?
- 是否需要深拷贝?
- 对象是否包含函数、Symbol、循环引用?
- 性能要求如何?
- 是否可以引入额外的库?
- 目标浏览器环境是什么?
根据这些因素,选择最适合你的方法。 在大多数情况下,使用 lodash
的 _.cloneDeep()
是一个安全可靠的选择。