关于readonly和shallowReadonly的用法以及使用场景

145 阅读2分钟

Vue3 只读 API

1. readonly vs shallowReadonly

1.1 基本概念

  • readonly: 深层只读,递归地将对象的所有属性设置为只读
  • shallowReadonly: 浅层只读,只将对象的第一层属性设置为只读

1.2 使用对比

// readonly 示例
const original = reactive({
  count: 0,
  nested: {
    value: 'hello'
  }
})

const copy = readonly(original)

// 这些修改都会失败并产生警告
copy.count++  // 警告
copy.nested.value = 'world'  // 警告

// shallowReadonly 示例
const shallowCopy = shallowReadonly({
  count: 0,
  nested: {
    value: 'hello'
  }
})

// 只有第一层属性是只读的
shallowCopy.count++  // 警告
shallowCopy.nested.value = 'world'  // 可以修改

2. 实际应用场景

2.1 使用 readonly 的场景

  1. Props 保护
<script setup>
import { readonly } from 'vue'

const props = defineProps(['settings'])
// 确保组件不会修改 props
const safeProps = readonly(props)

// 使用 safeProps 代替直接使用 props
</script>
  1. 共享状态保护
// store.js
import { reactive, readonly } from 'vue'

const state = reactive({
  user: {
    name: 'John',
    preferences: {
      theme: 'dark',
      notifications: true
    }
  }
})

// 只暴露只读版本
export const useStore = () => ({
  state: readonly(state),
  // 提供修改方法
  updateTheme: (theme) => {
    state.user.preferences.theme = theme
  }
})

2.2 使用 shallowReadonly 的场景

  1. 部分属性保护
const config = shallowReadonly({
  api: {
    endpoint: 'https://api.example.com',
    timeout: 5000
  },
  settings: {
    // 这些内部值可以修改
    cache: true,
    debug: false
  }
})

// 不能修改顶层属性
config.api = {}  // 警告

// 但可以修改嵌套属性
config.settings.cache = false  // 可以修改
  1. 性能优化
// 大型对象只保护关键属性
const largeConfig = shallowReadonly({
  constants: {
    // 大量配置数据
  },
  runtime: {
    // 可修改的运行时数据
    cache: new Map(),
    temp: {}
  }
})

3. 组件通信中的应用

3.1 父子组件通信

<!-- 父组件 -->
<script setup>
import { reactive, readonly } from 'vue'

const state = reactive({
  user: {
    name: 'John',
    settings: {
      theme: 'dark'
    }
  }
})

// 传递给子组件的只读版本
const readonlyState = readonly(state)
</script>

<template>
  <child-component :state="readonlyState" />
</template>

<!-- 子组件 -->
<script setup>
const props = defineProps(['state'])

// 尝试修改会产生警告
const updateTheme = () => {
  props.state.user.settings.theme = 'light' // 警告
}
</script>

3.2 状态管理

// store/index.js
import { reactive, readonly, shallowReadonly } from 'vue'

const state = reactive({
  user: {
    profile: {
      name: '',
      email: ''
    },
    preferences: {
      theme: 'light',
      notifications: true
    }
  },
  cache: new Map() // 不需要深层只读的数据
})

export const store = {
  // 完全只读的状态
  state: readonly(state),
  
  // 浅层只读的缓存数据
  cache: shallowReadonly(state.cache),
  
  // 修改方法
  updateProfile(profile) {
    state.user.profile = profile
  },
  
  updatePreferences(preferences) {
    Object.assign(state.user.preferences, preferences)
  }
}

4. 最佳实践

  1. 选择合适的只读级别
// ✅ 需要完全保护的数据使用 readonly
const sensitiveData = readonly(userData)

// ✅ 只需保护部分属性时使用 shallowReadonly
const config = shallowReadonly({
  api: { /* 受保护的 API 配置 */ },
  cache: { /* 可修改的缓存数据 */ }
})
  1. 组合使用
const store = {
  // 完全只读的核心状态
  state: readonly(coreState),
  
  // 浅层只读的配置
  config: shallowReadonly(config),
  
  // 可变的缓存数据
  cache: reactive(cacheData)
}
  1. 与 TypeScript 配合
interface State {
  readonly user: {
    readonly name: string;
    readonly settings: {
      readonly theme: string;
    };
  };
  cache: Map<string, any>; // 可修改的缓存
}

const state: State = shallowReadonly({
  user: {
    name: 'John',
    settings: {
      theme: 'dark'
    }
  },
  cache: new Map()
})

5. 注意事项

  1. 响应式转换
// readonly 会保持源对象的响应式
const original = reactive({ count: 0 })
const copy = readonly(original)

// original 的变化会触发 copy 的更新
original.count++ // copy.count 也会更新
  1. 集合类型处理
const original = reactive(new Map())
const readonlyMap = readonly(original)

// 集合方法也会被限制
readonlyMap.set('key', 'value') // 警告
  1. 嵌套对象
const nested = shallowReadonly({
  first: {
    second: {
      value: 1
    }
  }
})

// 只有第一层受保护
nested.first = {} // 警告
nested.first.second.value = 2 // 可以修改
  1. 与 ref 配合使用
const count = ref(0)
const readonlyCount = readonly(count)

count.value++ // 原始 ref 可以修改
readonlyCount.value++ // 警告