readonly 详解

readonly() 创建一个只读代理对象,防止数据被意外修改。这在保护组件内部状态、向子组件传递不可变数据时非常有用。

readonly()

readonly() 创建深度只读代理——所有层级的属性都不允许修改。

tsx
import { readonly } from 'vitarx'

const original = { name: '张三', profile: { age: 25 } }
const state = readonly(original)

// 读取正常
console.log(state.name) // '张三'
console.log(state.profile.age) // 25

// ❌ 修改直接属性——控制台输出警告,修改不生效
state.name = '李四'

// ❌ 修改嵌套属性——控制台输出警告,修改不生效(深度只读)
state.profile.age = 30

WARNING

在开发环境下,尝试修改 readonly 对象会在控制台输出警告信息。修改操作不会生效,但不会抛出错误。

shallowReadonly()

shallowReadonly() 创建浅层只读代理——只有直接属性是只读的,嵌套对象仍然可以修改。

tsx
import { shallowReadonly } from 'vitarx'

const original = { name: '张三', profile: { age: 25 } }
const state = shallowReadonly(original)

// ❌ 修改直接属性——控制台输出警告,修改不生效
state.name = '李四'

// ✅ 修改嵌套属性——允许修改(浅层只读)
state.profile.age = 30
console.log(state.profile.age) // 30

readonly vs shallowReadonly

特性readonly()shallowReadonly()
直接属性只读只读
嵌套属性只读可修改
性能略低(需要深度代理)略高(只代理一层)

与 reactive 结合使用

readonly() 常与 reactive() 结合使用——内部使用 reactive 维护可变状态,对外暴露 readonly 版本:

tsx
import { reactive, readonly, createApp } from 'vitarx'

function useCounter() {
  // 内部可变状态
  const state = reactive({ count: 0 })

  // 修改方法
  const increment = () => state.count++
  const decrement = () => state.count--

  // 对外暴露只读版本——外部无法直接修改
  return {
    state: readonly(state),
    increment,
    decrement
  }
}

function App() {
  const { state, increment, decrement } = useCounter()

  return (
    <div>
      <p>计数: {state.count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  )
}

createApp(App).mount('#app')

这种模式确保了外部只能通过提供的方法修改状态,不能直接篡改数据。

防止外部修改组件内部状态

在组件开发中,你可能需要将内部状态传递给子组件,但不希望子组件修改它。使用 readonly 可以有效防止这种情况:

tsx
import { reactive, readonly, createApp } from 'vitarx'

// 父组件
function Parent() {
  const config = reactive({
    theme: 'dark',
    fontSize: 14
  })

  return (
    <div>
      <p>当前主题: {config.theme}</p>
      <Child config={readonly(config)} />
    </div>
  )
}

// 子组件——接收到的 config 是只读的
function Child({ config }) {
  // ✅ 可以读取
  console.log(config.theme)

  // ❌ 无法修改——控制台输出警告
  // config.theme = 'light'

  return <p>子组件收到的主题: {config.theme}</p>
}

createApp(Parent).mount('#app')

集合类型的只读代理

readonly() 同样支持 Map、Set、WeakMap、WeakSet 等集合类型:

tsx
import { readonly } from 'vitarx'

const originalMap = new Map([['name', '张三']])
const readonlyMap = readonly(originalMap)

// ✅ 读取正常
console.log(readonlyMap.get('name')) // '张三'

// ❌ 修改操作——控制台输出警告
readonlyMap.set('name', '李四')
readonlyMap.delete('name')
readonlyMap.clear()

isReadonly()

isReadonly() 判断一个值是否为只读代理对象:

tsx
import { readonly, shallowReadonly, reactive, isReadonly } from 'vitarx'

const state = reactive({ count: 0 })
const readonlyState = readonly(state)
const shallowReadonlyState = shallowReadonly(state)

isReadonly(readonlyState) // true
isReadonly(shallowReadonlyState) // true
isReadonly(state) // false
isReadonly({}) // false

INFO

GetterRef(通过 toRef(() => ...) 创建)也是只读的,isReadonly() 对它返回 true

完整示例

下面是一个综合使用 readonly 相关 API 的示例:

tsx
import { reactive, readonly, shallowReadonly, isReadonly, createApp } from 'vitarx'

function App() {
  // 内部可变状态
  const internalState = reactive({
    name: '张三',
    profile: { age: 25, city: '北京' }
  })

  // 深度只读——对外暴露
  const publicState = readonly(internalState)

  // 浅层只读——嵌套属性仍可修改
  const semiState = shallowReadonly(internalState)

  const changeName = () => {
    // 内部可以正常修改
    internalState.name = '李四'
  }

  const changeCity = () => {
    // 内部可以修改嵌套属性
    internalState.profile.city = '上海'
  }

  return (
    <div style={{ padding: '20px' }}>
      <h2>readonly 详解示例</h2>

      <div style={{ marginTop: '16px' }}>
        <h3>内部状态(可修改)</h3>
        <p>姓名: {internalState.name}</p>
        <p>城市: {internalState.profile.city}</p>
        <button onClick={changeName}>修改姓名</button>
        <button onClick={changeCity}>修改城市</button>
      </div>

      <div style={{ marginTop: '16px' }}>
        <h3>只读代理(不可修改)</h3>
        <p>是否为只读: {String(isReadonly(publicState))}</p>
        <p>姓名: {publicState.name}</p>
        <p>城市: {publicState.profile.city}</p>
      </div>

      <div style={{ marginTop: '16px' }}>
        <h3>浅层只读代理</h3>
        <p>是否为只读: {String(isReadonly(semiState))}</p>
        <p>姓名: {semiState.name}</p>
        <p>城市: {semiState.profile.city}</p>
      </div>
    </div>
  )
}

createApp(App).mount('#app')

下一步