You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3+TypeScript如何防止嵌套Prop字段被篡改?

防止Vue组件嵌套Prop字段被篡改的方法

在Vue中,props遵循单向数据流规则,但嵌套的引用类型prop(如对象、数组)会因引用关系导致子组件可直接修改其内部字段,破坏数据流规范。以下是几种有效的解决方式:

1. 子组件内创建局部副本,修改副本而非原props

通过创建局部响应式变量存储props的拷贝,子组件仅修改该副本,同时监听props变化同步更新副本,确保原props不受影响:

<script setup lang="ts">
import { ref, watch } from 'vue';

export interface Person {
  name: string;
}

const props = defineProps<{
  person: Person;
}>();

// 基于props创建局部副本
const localPerson = ref({ ...props.person });

// 监听props的变化,实时同步副本
watch(
  () => props.person,
  (newPerson) => {
    localPerson.value = { ...newPerson };
  },
  { deep: true } // 深监听对象内部字段变化
);

function onClick(): void {
  localPerson.value.name = 'Joe'; // 修改副本,不影响原props
}
</script>

<template>
  <button @click="onClick">Click</button>
  <p>当前名称:{{ localPerson.name }}</p>
</template>

2. 使用readonly创建只读代理(开发环境约束)

用Vue的readonly方法包装prop对象,开发环境下修改其嵌套字段会直接抛出错误,起到警示作用;生产环境虽不阻止修改,但能规范开发流程:

<script setup lang="ts">
import { readonly } from 'vue';

export interface Person {
  name: string;
}

const props = defineProps<{
  person: Person;
}>();

// 生成只读代理对象
const readOnlyPerson = readonly(props.person);

function onClick(): void {
  readOnlyPerson.name = 'Joe'; // 开发环境会抛出错误,阻止非法修改
}
</script>

<template>
  <button @click="onClick">Click</button>
</template>

3. 父组件传递冻结对象(浅/深冻结)

在父组件中使用Object.freeze(浅冻结)或自定义深冻结函数处理对象,使对象无法被修改,严格模式下修改冻结对象会直接报错:

浅冻结示例(父组件)

<script setup lang="ts">
import Child from './Child.vue';

// 浅冻结对象,仅冻结第一层字段
const person = Object.freeze({ name: 'Alice' });
</script>

<template>
  <Child :person="person" />
</template>

深冻结工具函数(处理多层嵌套对象)

// 自定义深冻结函数
function deepFreeze(obj: any): any {
  Object.freeze(obj);
  for (const key in obj) {
    const value = obj[key];
    if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {
      deepFreeze(value);
    }
  }
  return obj;
}

// 父组件中使用
const person = deepFreeze({ name: 'Alice', address: { city: 'New York' } });

4. 通过emit通知父组件修改(推荐方案)

遵循Vue单向数据流设计理念,子组件不直接修改props,而是通过emit触发事件,由父组件负责更新数据:

子组件代码

<script setup lang="ts">
export interface Person {
  name: string;
}

const props = defineProps<{
  person: Person;
}>();

// 定义触发事件
const emit = defineEmits<{
  'update:person': [updatedPerson: Person];
}>();

function onClick(): void {
  // 创建新对象,避免修改原引用
  const updatedPerson = { ...props.person, name: 'Joe' };
  emit('update:person', updatedPerson);
}
</script>

<template>
  <button @click="onClick">Click</button>
</template>

父组件代码

<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue';

const person = ref({ name: 'Alice' });

// 接收子组件事件,更新数据
function handleUpdatePerson(newPerson: Person) {
  person.value = newPerson;
}
</script>

<template>
  <Child :person="person" @update:person="handleUpdatePerson" />
  <p>父组件当前名称:{{ person.name }}</p>
</template>

内容的提问来源于stack exchange,提问作者rodrigocfd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 10:00:33