Vue3中toRaw和MarkRaw
一、这两个是什么东西?
在vue3中,响应式系统是通过proxy,它可以把一个普通对象变成一个响应式对象,这样数据变化的时候视图也会跟着变化;但是可能有的时候,我们并不希望一些对象被Proxy对象包装,我们只需要原始对象,而toRaw和MarkRaw就是做这个事情的;
二、toRaw获取原始对象
toRaw接受一个ref、reactive定义的响应式对象,返回其对象原始本身,如果传入的不是响应式对象,就会返回本身;
vue
<template>
<h3>{{ person }}</h3>
<button @click="changePerson2Age">修改原始对象age</button>
</template>
<script setup>
import { arEg } from 'element-plus/es/locale/index.mjs';
import { reactive, toRaw } from 'vue';
const person = reactive({
name: '张三',
age: 18
})
const person2 = toRaw(person)
function changePerson2Age() {
console.log(person2);
person2.age += 1
}
</script>

使用场景
当你需要修改也给大对象的时候,不要频繁的更新视图,以此来减少开销,那么你就可以使用它;
这里假设我们又10000条记录的大表格,当用户点击"批量更新"的时候,你需要修改其5000条数据。如果每次修改数据都触发更新,界面就会卡顿。这时候我们可以使用toRaw直接修改原始数据,最后在整体替换
vue
<template>
<h3>{{ tableData }}</h3>
<button @click="batchUpdata">触发更新</button>
</template>
<script setup>
import { reactive, toRaw } from 'vue';
const tableData = reactive([{ id: 1, name: 'Alice', score: 50 }, { id: 2, name: 'Bob', score: 92 }])
function batchUpdata() {
// 1. 获取原始数据(注意:数组内的对象仍是响应式代理)
const rawArray = toRaw(tableData);
// 2. 深拷贝一份干净的数据(避免直接修改响应式对象)
const newData = rawArray.map(item => ({ ...item })); // 浅拷贝已足够(本例中对象只有一层)
// 3. 批量修改新数据
for (let i = 0; i < newData.length; i++) {
if (newData[i].score < 60) {
newData[i].score += 10;
}
}
// 4. 整体替换响应式数组(仅触发一次视图更新)
tableData.length = 0;
tableData.push(...newData);
}
</script>

三、markRaw------ 标记为永远不响应式
markRaw 返回对象本身,并给它添加一个内部标记,告诉 Vue 永远不要把这个对象转换成响应式对象。即使你把它放到 reactive 或 ref 中,它也不会被包装。
这个在之前已经说过了,这里再举一个现实世界示例
例如一些地图实例、图标实例,一般这些对象内部状态非常复杂,Vue完全不需要去跟踪它们
假设你在组件中使用 ECharts 创建一个图表,ECharts 实例有很多内部属性和方法,Vue 完全不需要对这些属性做响应式处理。如果意外地将它变成了响应式,不仅性能差,还可能引发错误。
vue
<template>
<div class="chart-container">
<!-- 图表容器,ref 用于获取 DOM 元素 -->
<div ref="chartRef" class="chart"></div>
<!-- 交互按钮:更新图表数据 -->
<div class="controls">
<button @click="updateChartData">更新数据(随机)</button>
<button @click="resetChartData">重置数据</button>
</div>
</div>
</template>
<script setup>
import { markRaw, onMounted, onBeforeUnmount, ref } from 'vue'
import * as echarts from 'echarts'
// 1. 获取图表容器的 DOM 引用
const chartRef = ref(null)
// 2. 存储 ECharts 实例(使用 markRaw 防止被 Vue 响应化)
const chartInstance = ref(null)
// 3. 定义初始数据(也可以作为响应式数据,但图表更新直接调用实例方法)
const defaultData = [120, 200, 150, 80]
const currentData = ref([...defaultData]) // 仅用于展示,实际更新图表时直接操作实例
// 4. 更新图表数据的方法
function updateChart(newData) {
if (!chartInstance.value) return
// 直接调用 ECharts 实例的 setOption,不会触发 Vue 的响应式系统
chartInstance.value.setOption({
series: [{ data: newData }]
})
// 同步更新响应式数据(可选,如果需要在其他地方展示当前数据)
currentData.value = [...newData]
}
// 随机生成数据
function updateChartData() {
const randomData = Array.from({ length: 4 }, () => Math.floor(Math.random() * 300))
updateChart(randomData)
}
function resetChartData() {
updateChart(defaultData)
}
// 窗口尺寸变化时,调用 echarts 的 resize 方法使图表自适应
function handleResize() {
if (chartInstance.value) {
chartInstance.value.resize()
}
}
// 5. 生命周期:挂载完成后初始化图表
onMounted(() => {
if (chartRef.value) {
// 创建 ECharts 实例
const instance = echarts.init(chartRef.value)
// 使用 markRaw 标记,避免 Vue 将其转换为响应式对象
chartInstance.value = markRaw(instance)
// 配置图表
instance.setOption({
title: {
text: '销售趋势',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['Q1', 'Q2', 'Q3', 'Q4']
},
yAxis: {
type: 'value'
},
series: [
{
name: '销售额',
type: 'line',
data: defaultData,
smooth: true,
lineStyle: {
width: 3,
color: '#42b983'
},
areaStyle: {
opacity: 0.1,
color: '#42b983'
}
}
]
})
// 添加窗口 resize 监听
window.addEventListener('resize', handleResize)
}
})
// 6. 组件卸载前销毁图表实例并移除事件监听
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance.value) {
chartInstance.value.dispose() // 释放 ECharts 实例占用的内存
chartInstance.value = null
}
})
</script>
<style scoped>
.chart-container {
width: 100%;
padding: 20px;
box-sizing: border-box;
background: #f5f7fa;
border-radius: 8px;
}
.chart {
width: 100%;
height: 400px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.controls {
margin-top: 20px;
text-align: center;
}
.controls button {
margin: 0 10px;
padding: 8px 20px;
font-size: 14px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
background-color: #42b983;
color: white;
}
.controls button:hover {
background-color: #33a06f;
transform: translateY(-1px);
}
</style>