**Vue DevTools** — это расширение для браузера (Chrome, Firefox, Edge), которое добавляет вкладку Vue в панель разработчика. Оно позволяет инспектировать компонентное дерево, просматривать и редактировать реактивное состояние в реальном времени, отслеживать события и работать с Pinia.
Установка: **Chrome Web Store** или **Firefox Add-ons** — поиск "Vue.js devtools".
Вкладка **Components** показывает дерево компонентов вашего приложения. Для каждого компонента доступны:
ref, reactive, computed из <script setup> (можно редактировать!)Клик на компонент в дереве DevTools выделяет его на странице, и наоборот — иконка "select" позволяет кликнуть на элемент в браузере и сразу найти его компонент.
Если приложение использует Pinia, в DevTools появляется вкладка **Pinia**:
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubled, increment }
})В DevTools видно: текущее значение count, вычисляемое doubled, можно вызвать increment() прямо из интерфейса DevTools без кода. Также есть **timeline** — история всех изменений стора с timestamp.
Вкладка **Timeline** записывает хронологию событий:
$emit с аргументами и источникомfrom/toЭто незаменимо для отладки "почему компонент перерендерился" или "откуда пришло это событие".
Для библиотек и продвинутых сценариев можно добавить собственную вкладку в DevTools:
// Только в development режиме
if (import.meta.env.DEV) {
const { setupDevtoolsPlugin } = await import('@vue/devtools-api')
setupDevtoolsPlugin({
id: 'my-plugin',
label: 'Мой плагин',
app,
}, (api) => {
api.addInspector({
id: 'my-inspector',
label: 'Мои данные',
icon: 'storage',
})
api.on.getInspectorTree((payload) => {
if (payload.inspectorId === 'my-inspector') {
payload.rootNodes = [
{ id: 'root', label: 'Состояние' },
]
}
})
api.on.getInspectorState((payload) => {
if (payload.inspectorId === 'my-inspector') {
payload.state = {
'Данные': [
{ key: 'cacheSize', value: myCache.size },
]
}
}
})
})
}**Проблема: компонент не обновляется.** Откройте компонент в DevTools и посмотрите на состояние. Если данные в Setup обновляются, но шаблон не — проблема в реактивности (возможно, забыли .value или мутируете объект напрямую вместо замены).
**Проблема: лишние рендеры.** Установите расширение и включите Timeline → Component events. Фильтруйте по имени компонента и смотрите, что вызывает обновления.
Отладка производительности:
// Включить детальные предупреждения Vue
app.config.performance = true // в development
// Предупреждения о множественных обновлениях
app.config.warnHandler = (msg, vm, trace) => {
console.warn(msg, trace)
}Начиная с Vue 3.4 доступен **standalone DevTools** — отдельное приложение:
npm install -g @vue/devtools
vue-devtoolsВ приложении добавьте скрипт подключения — удобно для мобильных устройств и окружений, где нельзя установить расширение.
Симуляция функциональности Vue DevTools: инспекция компонентного дерева, timeline событий и трекинг изменений состояния
// ============================================
// Симуляция Vue DevTools
// ============================================
// DevTools подключаются к Vue через специальный хук.
// Здесь мы воспроизводим его логику, чтобы понять принцип.
// ============================================
// 1. Реестр компонентов (Component Inspector)
// ============================================
class ComponentRegistry {
constructor() {
this._components = new Map()
this._nextId = 1
}
register(name, state, props = {}) {
const id = 'comp-' + this._nextId++
this._components.set(id, {
id,
name,
state: { ...state },
props: { ...props },
children: [],
parent: null,
})
return id
}
addChild(parentId, childId) {
const parent = this._components.get(parentId)
const child = this._components.get(childId)
if (parent && child) {
parent.children.push(childId)
child.parent = parentId
}
}
updateState(id, newState) {
const comp = this._components.get(id)
if (comp) {
comp.state = { ...comp.state, ...newState }
}
}
inspect(id) {
const comp = this._components.get(id)
if (!comp) return null
return {
name: comp.name,
state: comp.state,
props: comp.props,
children: comp.children.map(cid => this._components.get(cid)?.name),
parent: comp.parent ? this._components.get(comp.parent)?.name : null,
}
}
// Рисуем дерево компонентов
printTree(id = null, indent = 0) {
// Находим корневые компоненты если id не указан
if (id === null) {
const roots = [...this._components.values()].filter(c => !c.parent)
for (const root of roots) this.printTree(root.id, indent)
return
}
const comp = this._components.get(id)
if (!comp) return
const prefix = ' '.repeat(indent)
const stateStr = Object.entries(comp.state)
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
.join(', ')
console.log(`${prefix}<${comp.name}> { ${stateStr} }`)
for (const childId of comp.children) {
this.printTree(childId, indent + 1)
}
}
}
// ============================================
// 2. Timeline событий
// ============================================
class DevToolsTimeline {
constructor() {
this._events = []
this._startTime = Date.now()
}
record(type, source, payload) {
const event = {
id: this._events.length + 1,
time: Date.now() - this._startTime,
type,
source,
payload: JSON.parse(JSON.stringify(payload)),
}
this._events.push(event)
return event
}
filter(type) {
return this._events.filter(e => e.type === type)
}
print(filterType = null) {
const events = filterType ? this.filter(filterType) : this._events
console.log(`\n Timeline [${events.length} событий]${filterType ? ' (фильтр: ' + filterType + ')' : ''}:`)
for (const e of events) {
const payloadStr = JSON.stringify(e.payload)
console.log(` +${String(e.time).padStart(4)}ms [${e.type.padEnd(12)}] ${e.source}: ${payloadStr}`)
}
}
}
// ============================================
// 3. Трекер мутаций (Pinia DevTools)
// ============================================
function createTrackedStore(name, initialState) {
const timeline = new DevToolsTimeline()
let _state = { ...initialState }
const subscribers = []
const store = {
get state() { return { ..._state } },
commit(mutationName, updater) {
const before = { ..._state }
updater(_state)
const after = { ..._state }
// Записываем в timeline как DevTools
timeline.record('pinia', name, {
mutation: mutationName,
before,
after,
diff: Object.fromEntries(
Object.entries(after).filter(([k, v]) => v !== before[k])
),
})
for (const sub of subscribers) sub({ ..._state })
},
subscribe(fn) {
subscribers.push(fn)
},
printTimeline() {
timeline.print('pinia')
},
}
return store
}
// ============================================
// Демонстрация
// ============================================
console.log('=== Vue DevTools Симуляция ===')
// Создаём компонентное дерево
const registry = new ComponentRegistry()
const appId = registry.register('App', { theme: 'light' })
const headerIdx = registry.register('Header', { isOpen: false }, { title: 'Мой сайт' })
const mainId = registry.register('Main', {})
const counterId = registry.register('Counter', { count: 0 }, { step: 1 })
const listId = registry.register('ProductList', { items: [], loading: true })
registry.addChild(appId, headerIdx)
registry.addChild(appId, mainId)
registry.addChild(mainId, counterId)
registry.addChild(mainId, listId)
console.log('\n--- Дерево компонентов ---')
registry.printTree()
console.log('\n--- Инспекция Counter ---')
console.log(registry.inspect(counterId))
// Обновляем состояние (как при клике в приложении)
registry.updateState(counterId, { count: 3 })
registry.updateState(listId, { loading: false, items: ['Товар A', 'Товар B'] })
console.log('\n--- После обновлений ---')
registry.printTree()
// Pinia store с трекингом
console.log('\n--- Pinia Store (с Timeline) ---')
const cartStore = createTrackedStore('cart', {
items: [],
total: 0,
coupon: null,
})
cartStore.commit('addItem', (s) => {
s.items.push({ id: 1, name: 'Vue Book', price: 500 })
s.total = 500
})
cartStore.commit('addItem', (s) => {
s.items.push({ id: 2, name: 'JS Course', price: 1200 })
s.total = 1700
})
cartStore.commit('applyCoupon', (s) => {
s.coupon = 'VUEJS20'
s.total = Math.round(s.total * 0.8)
})
cartStore.printTimeline()
console.log('\nТекущее состояние стора:', cartStore.state)**Vue DevTools** — это расширение для браузера (Chrome, Firefox, Edge), которое добавляет вкладку Vue в панель разработчика. Оно позволяет инспектировать компонентное дерево, просматривать и редактировать реактивное состояние в реальном времени, отслеживать события и работать с Pinia.
Установка: **Chrome Web Store** или **Firefox Add-ons** — поиск "Vue.js devtools".
Вкладка **Components** показывает дерево компонентов вашего приложения. Для каждого компонента доступны:
ref, reactive, computed из <script setup> (можно редактировать!)Клик на компонент в дереве DevTools выделяет его на странице, и наоборот — иконка "select" позволяет кликнуть на элемент в браузере и сразу найти его компонент.
Если приложение использует Pinia, в DevTools появляется вкладка **Pinia**:
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubled, increment }
})В DevTools видно: текущее значение count, вычисляемое doubled, можно вызвать increment() прямо из интерфейса DevTools без кода. Также есть **timeline** — история всех изменений стора с timestamp.
Вкладка **Timeline** записывает хронологию событий:
$emit с аргументами и источникомfrom/toЭто незаменимо для отладки "почему компонент перерендерился" или "откуда пришло это событие".
Для библиотек и продвинутых сценариев можно добавить собственную вкладку в DevTools:
// Только в development режиме
if (import.meta.env.DEV) {
const { setupDevtoolsPlugin } = await import('@vue/devtools-api')
setupDevtoolsPlugin({
id: 'my-plugin',
label: 'Мой плагин',
app,
}, (api) => {
api.addInspector({
id: 'my-inspector',
label: 'Мои данные',
icon: 'storage',
})
api.on.getInspectorTree((payload) => {
if (payload.inspectorId === 'my-inspector') {
payload.rootNodes = [
{ id: 'root', label: 'Состояние' },
]
}
})
api.on.getInspectorState((payload) => {
if (payload.inspectorId === 'my-inspector') {
payload.state = {
'Данные': [
{ key: 'cacheSize', value: myCache.size },
]
}
}
})
})
}**Проблема: компонент не обновляется.** Откройте компонент в DevTools и посмотрите на состояние. Если данные в Setup обновляются, но шаблон не — проблема в реактивности (возможно, забыли .value или мутируете объект напрямую вместо замены).
**Проблема: лишние рендеры.** Установите расширение и включите Timeline → Component events. Фильтруйте по имени компонента и смотрите, что вызывает обновления.
Отладка производительности:
// Включить детальные предупреждения Vue
app.config.performance = true // в development
// Предупреждения о множественных обновлениях
app.config.warnHandler = (msg, vm, trace) => {
console.warn(msg, trace)
}Начиная с Vue 3.4 доступен **standalone DevTools** — отдельное приложение:
npm install -g @vue/devtools
vue-devtoolsВ приложении добавьте скрипт подключения — удобно для мобильных устройств и окружений, где нельзя установить расширение.
Симуляция функциональности Vue DevTools: инспекция компонентного дерева, timeline событий и трекинг изменений состояния
// ============================================
// Симуляция Vue DevTools
// ============================================
// DevTools подключаются к Vue через специальный хук.
// Здесь мы воспроизводим его логику, чтобы понять принцип.
// ============================================
// 1. Реестр компонентов (Component Inspector)
// ============================================
class ComponentRegistry {
constructor() {
this._components = new Map()
this._nextId = 1
}
register(name, state, props = {}) {
const id = 'comp-' + this._nextId++
this._components.set(id, {
id,
name,
state: { ...state },
props: { ...props },
children: [],
parent: null,
})
return id
}
addChild(parentId, childId) {
const parent = this._components.get(parentId)
const child = this._components.get(childId)
if (parent && child) {
parent.children.push(childId)
child.parent = parentId
}
}
updateState(id, newState) {
const comp = this._components.get(id)
if (comp) {
comp.state = { ...comp.state, ...newState }
}
}
inspect(id) {
const comp = this._components.get(id)
if (!comp) return null
return {
name: comp.name,
state: comp.state,
props: comp.props,
children: comp.children.map(cid => this._components.get(cid)?.name),
parent: comp.parent ? this._components.get(comp.parent)?.name : null,
}
}
// Рисуем дерево компонентов
printTree(id = null, indent = 0) {
// Находим корневые компоненты если id не указан
if (id === null) {
const roots = [...this._components.values()].filter(c => !c.parent)
for (const root of roots) this.printTree(root.id, indent)
return
}
const comp = this._components.get(id)
if (!comp) return
const prefix = ' '.repeat(indent)
const stateStr = Object.entries(comp.state)
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
.join(', ')
console.log(`${prefix}<${comp.name}> { ${stateStr} }`)
for (const childId of comp.children) {
this.printTree(childId, indent + 1)
}
}
}
// ============================================
// 2. Timeline событий
// ============================================
class DevToolsTimeline {
constructor() {
this._events = []
this._startTime = Date.now()
}
record(type, source, payload) {
const event = {
id: this._events.length + 1,
time: Date.now() - this._startTime,
type,
source,
payload: JSON.parse(JSON.stringify(payload)),
}
this._events.push(event)
return event
}
filter(type) {
return this._events.filter(e => e.type === type)
}
print(filterType = null) {
const events = filterType ? this.filter(filterType) : this._events
console.log(`\n Timeline [${events.length} событий]${filterType ? ' (фильтр: ' + filterType + ')' : ''}:`)
for (const e of events) {
const payloadStr = JSON.stringify(e.payload)
console.log(` +${String(e.time).padStart(4)}ms [${e.type.padEnd(12)}] ${e.source}: ${payloadStr}`)
}
}
}
// ============================================
// 3. Трекер мутаций (Pinia DevTools)
// ============================================
function createTrackedStore(name, initialState) {
const timeline = new DevToolsTimeline()
let _state = { ...initialState }
const subscribers = []
const store = {
get state() { return { ..._state } },
commit(mutationName, updater) {
const before = { ..._state }
updater(_state)
const after = { ..._state }
// Записываем в timeline как DevTools
timeline.record('pinia', name, {
mutation: mutationName,
before,
after,
diff: Object.fromEntries(
Object.entries(after).filter(([k, v]) => v !== before[k])
),
})
for (const sub of subscribers) sub({ ..._state })
},
subscribe(fn) {
subscribers.push(fn)
},
printTimeline() {
timeline.print('pinia')
},
}
return store
}
// ============================================
// Демонстрация
// ============================================
console.log('=== Vue DevTools Симуляция ===')
// Создаём компонентное дерево
const registry = new ComponentRegistry()
const appId = registry.register('App', { theme: 'light' })
const headerIdx = registry.register('Header', { isOpen: false }, { title: 'Мой сайт' })
const mainId = registry.register('Main', {})
const counterId = registry.register('Counter', { count: 0 }, { step: 1 })
const listId = registry.register('ProductList', { items: [], loading: true })
registry.addChild(appId, headerIdx)
registry.addChild(appId, mainId)
registry.addChild(mainId, counterId)
registry.addChild(mainId, listId)
console.log('\n--- Дерево компонентов ---')
registry.printTree()
console.log('\n--- Инспекция Counter ---')
console.log(registry.inspect(counterId))
// Обновляем состояние (как при клике в приложении)
registry.updateState(counterId, { count: 3 })
registry.updateState(listId, { loading: false, items: ['Товар A', 'Товар B'] })
console.log('\n--- После обновлений ---')
registry.printTree()
// Pinia store с трекингом
console.log('\n--- Pinia Store (с Timeline) ---')
const cartStore = createTrackedStore('cart', {
items: [],
total: 0,
coupon: null,
})
cartStore.commit('addItem', (s) => {
s.items.push({ id: 1, name: 'Vue Book', price: 500 })
s.total = 500
})
cartStore.commit('addItem', (s) => {
s.items.push({ id: 2, name: 'JS Course', price: 1200 })
s.total = 1700
})
cartStore.commit('applyCoupon', (s) => {
s.coupon = 'VUEJS20'
s.total = Math.round(s.total * 0.8)
})
cartStore.printTimeline()
console.log('\nТекущее состояние стора:', cartStore.state)Реализуй класс `StateInspector`, который отслеживает изменения объектов состояния. Методы: `track(name, obj)` — начать отслеживать объект под именем name; `snapshot(name)` — сделать снапшот текущего состояния (глубокую копию через JSON); `diff(name)` — вернуть объект с полями, которые изменились с последнего снапшота (сравнивая текущее значение с сохранённым); `history(name)` — массив всех снапшотов.
Используй Map: this._tracked = new Map(). В track: this._tracked.set(name, { ref: obj, snapshots: [] }); this.snapshot(name). В snapshot: entry.snapshots.push({ value: JSON.parse(JSON.stringify(entry.ref)), timestamp: Date.now() }). В diff: возьми последний снапшот entry.snapshots.at(-1)?.value, сравни с текущим entry.ref по всем ключам.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке