readonly() создаёт **только для чтения** Proxy над реактивным (или обычным) объектом. Любая попытка изменить его свойства вызовет предупреждение в режиме разработки:
import { reactive, readonly } from 'vue'
const state = reactive({ count: 0, name: 'Алексей' })
const readonlyState = readonly(state)
readonlyState.count = 5
// [Vue warn]: Set operation on key "count" failed: target is readonly.
console.log(readonlyState.count) // 0 — не изменилосьВажно: readonly не клонирует объект. Если исходный объект изменится, изменения будут видны через readonly-версию:
state.count = 10
console.log(readonlyState.count) // 10 — отражает изменения источникаКлассический паттерн — хранить состояние приватно, а наружу выдавать readonly-версию:
// store.js
import { reactive, readonly } from 'vue'
function createStore() {
const state = reactive({ user: null, isLoading: false })
function setUser(user) { state.user = user }
function setLoading(v) { state.isLoading = v }
// Компоненты могут только читать state, но не изменять напрямую
return { state: readonly(state), setUser, setLoading }
}Как и reactive, readonly работает рекурсивно — защищает все вложенные объекты:
const config = readonly({
api: {
url: 'https://api.example.com',
timeout: 5000
}
})
config.api.url = 'other' // тоже выдаст предупреждениеДля поверхностной защиты есть shallowReadonly().
markRaw() помечает объект специальным флагом, чтобы Vue **никогда не оборачивал его в Proxy**. Полезно для объектов, которые:
import { reactive, markRaw } from 'vue'
// Экземпляр Chart.js — не нужна реактивность
const chartInstance = markRaw(new Chart(canvas, options))
const state = reactive({
chart: chartInstance, // Vue не обернёт в Proxy!
data: [],
})
// state.chart — это тот же оригинальный объект Chart, не ProxyVue предоставляет функции для проверки типа объекта:
import { reactive, readonly, isProxy, isReactive, isReadonly, isRef, ref } from 'vue'
const raw = { a: 1 }
const reactiveObj = reactive(raw)
const readonlyObj = readonly(reactiveObj)
const count = ref(0)
isProxy(reactiveObj) // true — это Proxy
isProxy(readonlyObj) // true — тоже Proxy
isProxy(raw) // false
isReactive(reactiveObj) // true
isReactive(readonlyObj) // true (readonly реактивного = реактивный readonly)
isReadonly(readonlyObj) // true
isReadonly(reactiveObj) // false
isRef(count) // true
isRef(reactiveObj) // falsereadonly() — публичное API хранилища, конфигурация приложения, пропсы (Vue уже делает это для вас)markRaw() — сторонние библиотеки с DOM-управлением, большие статические данные (словари, константы), классы с методами которые Proxy ломаетРеализация readonly через Proxy и markRaw через символ-флаг, демонстрация паттерна хранилища
// Реализуем readonly и markRaw через Proxy и Symbol
const RAW_MARK = Symbol('markRaw')
const IS_READONLY = Symbol('isReadonly')
// markRaw: помечаем объект флагом — Vue не будет его оборачивать
function markRaw(obj) {
Object.defineProperty(obj, RAW_MARK, { value: true, enumerable: false })
return obj
}
function isMarkedRaw(obj) {
return obj && obj[RAW_MARK] === true
}
// readonly: Proxy, который блокирует set
function readonly(obj) {
const proxy = new Proxy(obj, {
get(target, key) {
if (key === IS_READONLY) return true
const value = target[key]
// Рекурсивно оборачиваем вложенные объекты
if (typeof value === 'object' && value !== null && !isMarkedRaw(value)) {
return readonly(value)
}
return value
},
set(target, key) {
console.warn(`[Vue warn] Set operation on key "${String(key)}" failed: target is readonly.`)
return true // Не кидаем ошибку, просто предупреждаем
},
deleteProperty(target, key) {
console.warn(`[Vue warn] Delete operation on key "${String(key)}" failed: target is readonly.`)
return true
}
})
return proxy
}
function isReadonly(obj) {
return !!(obj && obj[IS_READONLY])
}
// === Демонстрация readonly ===
console.log('=== readonly(): защита от мутаций ===')
const state = { count: 0, user: { name: 'Алексей', role: 'admin' } }
const readonlyState = readonly(state)
console.log('Чтение работает:')
console.log(' readonlyState.count:', readonlyState.count)
console.log(' readonlyState.user.name:', readonlyState.user.name)
console.log('\nПопытки мутации:')
readonlyState.count = 999 // предупреждение
readonlyState.user.role = 'user' // предупреждение (вложенный объект тоже защищён)
console.log('Значения не изменились:')
console.log(' readonlyState.count:', readonlyState.count) // 0
console.log(' readonlyState.user.role:', readonlyState.user.role) // 'admin'
// Но исходный объект можно изменить
state.count = 42
console.log(' После изменения state.count = 42:')
console.log(' readonlyState.count:', readonlyState.count) // 42
console.log('\nisReadonly проверки:')
console.log(' isReadonly(readonlyState):', isReadonly(readonlyState)) // true
console.log(' isReadonly(state):', isReadonly(state)) // false
// === Демонстрация markRaw ===
console.log('\n=== markRaw(): исключение из реактивности ===')
// Симулируем библиотечный объект (Chart.js, Three.js и т.д.)
class ChartLibrary {
constructor(options) {
this.options = options
this.canvas = null
console.log(' Chart создан')
}
update(data) {
console.log(' Chart.update() вызван с:', JSON.stringify(data))
}
}
const rawChart = markRaw(new ChartLibrary({ type: 'bar' }))
console.log('isMarkedRaw(rawChart):', isMarkedRaw(rawChart)) // true
// Внутри reactive-объекта markRaw-объект не оборачивается
const appState = { chart: rawChart, data: [1, 2, 3] }
const readonlyApp = readonly(appState)
const chart = readonlyApp.chart
console.log('Получили chart через readonly:', chart.constructor.name)
chart.update({ values: [10, 20, 30] }) // работает без Proxy-обёртки
// === Паттерн хранилища ===
console.log('\n=== Паттерн: хранилище с readonly публичным API ===')
function createUserStore() {
const _state = { user: null, isLoading: false, error: null }
return {
state: readonly(_state),
login(username) {
_state.isLoading = true
console.log(` [store] Вход как "${username}"`)
// Симулируем асинхронность
_state.user = { name: username, id: Math.random().toString(36).slice(2) }
_state.isLoading = false
},
logout() {
_state.user = null
console.log(' [store] Выход')
}
}
}
const store = createUserStore()
console.log('user до login:', store.state.user)
store.state.user = { name: 'Хакер' } // предупреждение — нельзя!
console.log('После попытки прямой мутации:', store.state.user) // null
store.login('Алексей')
console.log('После store.login():', store.state.user)readonly() создаёт **только для чтения** Proxy над реактивным (или обычным) объектом. Любая попытка изменить его свойства вызовет предупреждение в режиме разработки:
import { reactive, readonly } from 'vue'
const state = reactive({ count: 0, name: 'Алексей' })
const readonlyState = readonly(state)
readonlyState.count = 5
// [Vue warn]: Set operation on key "count" failed: target is readonly.
console.log(readonlyState.count) // 0 — не изменилосьВажно: readonly не клонирует объект. Если исходный объект изменится, изменения будут видны через readonly-версию:
state.count = 10
console.log(readonlyState.count) // 10 — отражает изменения источникаКлассический паттерн — хранить состояние приватно, а наружу выдавать readonly-версию:
// store.js
import { reactive, readonly } from 'vue'
function createStore() {
const state = reactive({ user: null, isLoading: false })
function setUser(user) { state.user = user }
function setLoading(v) { state.isLoading = v }
// Компоненты могут только читать state, но не изменять напрямую
return { state: readonly(state), setUser, setLoading }
}Как и reactive, readonly работает рекурсивно — защищает все вложенные объекты:
const config = readonly({
api: {
url: 'https://api.example.com',
timeout: 5000
}
})
config.api.url = 'other' // тоже выдаст предупреждениеДля поверхностной защиты есть shallowReadonly().
markRaw() помечает объект специальным флагом, чтобы Vue **никогда не оборачивал его в Proxy**. Полезно для объектов, которые:
import { reactive, markRaw } from 'vue'
// Экземпляр Chart.js — не нужна реактивность
const chartInstance = markRaw(new Chart(canvas, options))
const state = reactive({
chart: chartInstance, // Vue не обернёт в Proxy!
data: [],
})
// state.chart — это тот же оригинальный объект Chart, не ProxyVue предоставляет функции для проверки типа объекта:
import { reactive, readonly, isProxy, isReactive, isReadonly, isRef, ref } from 'vue'
const raw = { a: 1 }
const reactiveObj = reactive(raw)
const readonlyObj = readonly(reactiveObj)
const count = ref(0)
isProxy(reactiveObj) // true — это Proxy
isProxy(readonlyObj) // true — тоже Proxy
isProxy(raw) // false
isReactive(reactiveObj) // true
isReactive(readonlyObj) // true (readonly реактивного = реактивный readonly)
isReadonly(readonlyObj) // true
isReadonly(reactiveObj) // false
isRef(count) // true
isRef(reactiveObj) // falsereadonly() — публичное API хранилища, конфигурация приложения, пропсы (Vue уже делает это для вас)markRaw() — сторонние библиотеки с DOM-управлением, большие статические данные (словари, константы), классы с методами которые Proxy ломаетРеализация readonly через Proxy и markRaw через символ-флаг, демонстрация паттерна хранилища
// Реализуем readonly и markRaw через Proxy и Symbol
const RAW_MARK = Symbol('markRaw')
const IS_READONLY = Symbol('isReadonly')
// markRaw: помечаем объект флагом — Vue не будет его оборачивать
function markRaw(obj) {
Object.defineProperty(obj, RAW_MARK, { value: true, enumerable: false })
return obj
}
function isMarkedRaw(obj) {
return obj && obj[RAW_MARK] === true
}
// readonly: Proxy, который блокирует set
function readonly(obj) {
const proxy = new Proxy(obj, {
get(target, key) {
if (key === IS_READONLY) return true
const value = target[key]
// Рекурсивно оборачиваем вложенные объекты
if (typeof value === 'object' && value !== null && !isMarkedRaw(value)) {
return readonly(value)
}
return value
},
set(target, key) {
console.warn(`[Vue warn] Set operation on key "${String(key)}" failed: target is readonly.`)
return true // Не кидаем ошибку, просто предупреждаем
},
deleteProperty(target, key) {
console.warn(`[Vue warn] Delete operation on key "${String(key)}" failed: target is readonly.`)
return true
}
})
return proxy
}
function isReadonly(obj) {
return !!(obj && obj[IS_READONLY])
}
// === Демонстрация readonly ===
console.log('=== readonly(): защита от мутаций ===')
const state = { count: 0, user: { name: 'Алексей', role: 'admin' } }
const readonlyState = readonly(state)
console.log('Чтение работает:')
console.log(' readonlyState.count:', readonlyState.count)
console.log(' readonlyState.user.name:', readonlyState.user.name)
console.log('\nПопытки мутации:')
readonlyState.count = 999 // предупреждение
readonlyState.user.role = 'user' // предупреждение (вложенный объект тоже защищён)
console.log('Значения не изменились:')
console.log(' readonlyState.count:', readonlyState.count) // 0
console.log(' readonlyState.user.role:', readonlyState.user.role) // 'admin'
// Но исходный объект можно изменить
state.count = 42
console.log(' После изменения state.count = 42:')
console.log(' readonlyState.count:', readonlyState.count) // 42
console.log('\nisReadonly проверки:')
console.log(' isReadonly(readonlyState):', isReadonly(readonlyState)) // true
console.log(' isReadonly(state):', isReadonly(state)) // false
// === Демонстрация markRaw ===
console.log('\n=== markRaw(): исключение из реактивности ===')
// Симулируем библиотечный объект (Chart.js, Three.js и т.д.)
class ChartLibrary {
constructor(options) {
this.options = options
this.canvas = null
console.log(' Chart создан')
}
update(data) {
console.log(' Chart.update() вызван с:', JSON.stringify(data))
}
}
const rawChart = markRaw(new ChartLibrary({ type: 'bar' }))
console.log('isMarkedRaw(rawChart):', isMarkedRaw(rawChart)) // true
// Внутри reactive-объекта markRaw-объект не оборачивается
const appState = { chart: rawChart, data: [1, 2, 3] }
const readonlyApp = readonly(appState)
const chart = readonlyApp.chart
console.log('Получили chart через readonly:', chart.constructor.name)
chart.update({ values: [10, 20, 30] }) // работает без Proxy-обёртки
// === Паттерн хранилища ===
console.log('\n=== Паттерн: хранилище с readonly публичным API ===')
function createUserStore() {
const _state = { user: null, isLoading: false, error: null }
return {
state: readonly(_state),
login(username) {
_state.isLoading = true
console.log(` [store] Вход как "${username}"`)
// Симулируем асинхронность
_state.user = { name: username, id: Math.random().toString(36).slice(2) }
_state.isLoading = false
},
logout() {
_state.user = null
console.log(' [store] Выход')
}
}
}
const store = createUserStore()
console.log('user до login:', store.state.user)
store.state.user = { name: 'Хакер' } // предупреждение — нельзя!
console.log('После попытки прямой мутации:', store.state.user) // null
store.login('Алексей')
console.log('После store.login():', store.state.user)Реализуй функцию `createReadonlyProxy(obj)`, которая возвращает Proxy, где все операции чтения работают нормально, а операции записи (`set`) выбрасывают ошибку `TypeError` с сообщением `"Cannot mutate readonly object"`. Затем напиши функцию `safeRead(proxy, key)`, которая пытается прочитать свойство, и если объект заморожен (`Object.isFrozen`) или является Proxy — безопасно возвращает значение.
В обработчике `set` используй `throw new TypeError("Cannot mutate readonly object")`. Возврат `true` не нужен — исключение прервёт выполнение. В `safeRead` оберни `return obj[key]` в `try { ... } catch { return undefined }`.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке