Vue3中toRaw和MarkRaw

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 永远不要把这个对象转换成响应式对象。即使你把它放到 reactiveref 中,它也不会被包装。

这个在之前已经说过了,这里再举一个现实世界示例

例如一些地图实例、图标实例,一般这些对象内部状态非常复杂,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>
相关推荐
一 乐1 小时前
医院挂号|基于springboot + vue医院挂号管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·医院挂号管理系统
whuhewei2 小时前
为什么客户端不存在跨域问题
前端·安全
妮妮喔妮2 小时前
supabase的webhook报错
开发语言·前端·javascript
qq_12084093713 小时前
Three.js 大场景分块加载实战:从全量渲染到可视集调度
开发语言·javascript·数码相机
yivifu3 小时前
手搓HTML双行夹批效果
前端·html·html双行夹注
奔跑的卡卡3 小时前
Web开发与AI融合-第一篇:Web开发与AI融合的时代序幕
前端·人工智能
IT_陈寒4 小时前
Redis批量删除的大坑,差点让我加班到天亮
前端·人工智能·后端
帆张芳显4 小时前
智表ZCELL产品V3.6 版发布,新增系统预置右键菜单操作、页签栏操作等功能
前端·canva可画·excel插件
漂流瓶jz4 小时前
运行时vs编译时:CSS in JS四种主流方案介绍和对比
前端·javascript·css
Asmewill4 小时前
uv包管理命令
前端