Vue.js course · Module 3: Reactivity
Reactivity in Practice - Control Panel
In this lesson5
Let's combine everything in practical NOVA LAB system examples.
So far you have looked at each reactivity tool on its own. Real station panels combine them all at once: the configuration form has to validate data on the fly, the module monitor filters the list by the typed phrase, and the supply system adds up the order totals. All three panels below share the same layering. First the data in ref() or reactive(), then values derived with computed(), next the actions, meaning the functions that change the state, and finally the template with reactive bindings.
Reactor Configuration Panel with Validation
The form template uses v-model, the two-way binding between a field and the data, which you will learn in detail in module 5. The .number modifier turns the typed text into a number, and you know @submit.prevent and :disabled from module 2:
1<template>
2 <form @submit.prevent="submitConfig" class="reactor-config" style="background: #0a0e27; color: #00ff88;">
3 <div>
4 <label>Module name:</label>
5 <input v-model="config.name" style="background: #1a1e37; color: #00ff88;" />
6 <span v-if="errors.name" style="color: #ff4444;">{{ errors.name }}</span>
7 </div>
8
9 <div>
10 <label>Maximum temperature (K):</label>
11 <input v-model.number="config.maxTemp" type="number" style="background: #1a1e37; color: #00ff88;" />
12 <span v-if="errors.maxTemp" style="color: #ff4444;">{{ errors.maxTemp }}</span>
13 </div>
14
15 <div>
16 <label>Status:</label>
17 <input v-model="config.status" style="background: #1a1e37; color: #00ff88;" />
18 </div>
19
20 <button :disabled="!isValid" style="background: #00b4d8; color: #0a0e27;">Save configuration</button>
21 </form>
22</template>Error messages appear through v-if only when there is something to show, and the save button stays disabled until the form is valid. The form data is a coherent group of fields, so we keep it in reactive():
1<script setup>
2import { reactive, computed } from 'vue'
3
4const config = reactive({
5 name: '',
6 maxTemp: null,
7 status: ''
8})Validation is a value derived from these fields. In the first version of this panel, the computed property itself wrote the messages into a separate errors object. The Vue docs forbid side effects in computed properties, though: a getter should only compute and return a result. That is why the errors themselves are computed here:
1// Validation as a derived value - no side effects
2const errors = computed(() => ({
3 // Name validation
4 name: config.name.length < 3 ? 'Name must be at least 3 characters' : '',
5 // Temperature validation
6 maxTemp: config.maxTemp && (config.maxTemp < 1000 || config.maxTemp > 10000)
7 ? 'Temperature must be between 1000 and 10000 K'
8 : ''
9}))
10
11const isValid = computed(() => !errors.value.name && !errors.value.maxTemp)The template needed no change: errors is a top-level ref, so errors.name works in it without .value. Typing the third character of the name recalculates both computed properties and enables the button. The action that sends the configuration only reads the finished result:
1function submitConfig() {
2 if (isValid.value) {
3 console.log('Sending configuration to NOVA LAB:', config)
4 }
5}
6</script>Module Monitoring System
The monitor combines a search field and a drop-down list with a filtered list of modules. v-model works on <select> as well:
1<template>
2 <div class="module-monitor" style="background: #0a0e27; color: #00ff88;">
3 <input v-model="searchQuery" placeholder="Search module..." style="background: #1a1e37; color: #00ff88;" />
4 <select v-model="filterStatus" style="background: #1a1e37; color: #00ff88;">
5 <option value="">All statuses</option>
6 <option value="ACTIVE">Active</option>
7 <option value="STANDBY">Standby</option>
8 </select>
9
10 <ul>
11 <li v-for="module in filteredModules" :key="module.id" style="border-bottom: 1px solid #00b4d8;">
12 {{ module.name }} - {{ module.status }}
13 </li>
14 </ul>
15
16 <p>Found: {{ filteredModules.length }} modules</p>
17 </div>
18</template>The template filters nothing on its own - it receives the ready list filteredModules. The state is two simple refs and an array of modules:
1<script setup>
2import { ref, computed } from 'vue'
3
4const searchQuery = ref('')
5const filterStatus = ref('')
6
7const modules = ref([
8 { id: 1, name: 'Oxygen Generator Alpha', status: 'ACTIVE' },
9 { id: 2, name: 'Plasma Core Beta', status: 'STANDBY' },
10 { id: 3, name: 'Water Recycler Gamma', status: 'ACTIVE' },
11 { id: 4, name: 'Power Distributor Delta', status: 'STANDBY' }
12])The filter combines both conditions. The !filterStatus.value || part means: when no status is selected, let every module through:
1const filteredModules = computed(() => {
2 return modules.value.filter(module => {
3 const matchesSearch = module.name
4 .toLowerCase()
5 .includes(searchQuery.value.toLowerCase())
6
7 const matchesStatus = !filterStatus.value ||
8 module.status === filterStatus.value
9
10 return matchesSearch && matchesStatus
11 })
12})
13</script>The original modules array stays untouched, and the list on screen refreshes with every typed character.
Mission Supply System
The supply order shows the number of items, the entries with a remove button and the total in credits:
1<template>
2 <div class="supply-cart" style="background: #0a0e27; color: #00ff88;">
3 <h2>Order ({{ supplies.itemCount }} items)</h2>
4
5 <div v-for="item in supplies.items" :key="item.id" style="border-bottom: 1px solid #00b4d8;">
6 {{ item.name }} - {{ item.quantity }} x {{ item.price }} CR
7 <button @click="removeItem(item.id)" style="background: #ff4444; color: #fff;">Remove</button>
8 </div>
9
10 <p><strong>Total: {{ supplies.total }} CR (Credits)</strong></p>
11 </div>
12</template>Here the derived values are written differently than in the previous panels: as getters inside the reactive object itself. A getter is a JavaScript property whose value is computed on every read by a function marked with the get keyword:
1<script setup>
2import { reactive } from 'vue'
3
4const supplies = reactive({
5 items: [
6 { id: 1, name: 'Oxygen Canister', quantity: 10, price: 50 },
7 { id: 2, name: 'Plasma Cell', quantity: 5, price: 200 }
8 ],
9
10 // Computed as property
11 get itemCount() {
12 return this.items.reduce((sum, item) => sum + item.quantity, 0)
13 },
14
15 get total() {
16 return this.items.reduce((sum, item) =>
17 sum + item.quantity * item.price, 0)
18 }
19})At the start itemCount is 15 and total is 1500 CR. A getter computes its result on every read and, unlike computed(), does not cache it, which does not matter for a short list. The remove action changes the array in place:
1function removeItem(id) {
2 const index = supplies.items.findIndex(item => item.id === id)
3 if (index > -1) {
4 supplies.items.splice(index, 1)
5 }
6}
7</script>After the plasma cells are removed, the total drops to 500 CR, because splice is a method Vue detects.
readonly() - A View Without Write Access
The monitor in the mess hall should show the reactor state, but it must not change it. readonly() returns a deep, read-only Proxy:
1import { reactive, readonly } from 'vue'
2
3const reactorState = reactive({ temperature: 2500 })
4const reactorView = readonly(reactorState)
5
6reactorView.temperature = 9999 // console warning, change rejected
7reactorState.temperature = 2600 // a change through the original works
8console.log(reactorView.temperature) // 2600In development mode an attempt to write ends with a Vue warning, and the value stays unchanged. The read-only view is not a copy: it shows every change of the original. The pattern is simple - you keep the writable state to yourself, share the readonly version with the outside, and make changes only through your own functions.
Choosing the Right Tool
The Vue docs recommend ref() as the primary tool. reactive() works well for coherent groups of fields, shallowRef() with triggerRef() for huge lists, markRaw() for instances from external libraries, and readonly() for sharing state. Under the hood Vue 3 intercepts access to objects with the native Proxy, and access to refs with getters and setters. Stick to the split into data, derived values, actions and template and every panel will stay readable. In the module project you will build a complete reactor monitoring system from these layers.
Remember: a good station panel has a single source of truth, derived values instead of manual copies, and actions that are the only thing allowed to touch the state.
Code for this lesson: App.vue
1<script setup>
2import { ref, reactive, markRaw, isReactive, toRaw } from 'vue'
3
4// markRaw() - Plasma reactor control
5// Advanced reactivity control in NOVA LAB
6
7// Large object simulator (e.g. 3D library)
8class HeavySimulationEngine {
9 constructor(name) {
10 this.name = name
11 this.iterations = 0
12 this.data = new Array(1000).fill(0).map((_, i) => ({
13 id: i,
14 value: Math.random() * 100
15 }))
16 }
17
18 simulate() {
19 this.iterations++
20 // Heavy computations
21 return this.data.reduce((sum, item) => sum + item.value, 0)
22 }
23
24 reset() {
25 this.iterations = 0
26 }
27}
28
29// Reactor with a normal object (reactive)
30const reactiveReactor = reactive({
31 id: 'REACTOR-A',
32 status: 'ONLINE',
33 power: 100,
34 engine: new HeavySimulationEngine('Reactive Engine'),
35 lastUpdate: new Date().toISOString()
36})
37
38// Reactor with markRaw (non-reactive engine)
39const optimizedReactor = reactive({
40 id: 'REACTOR-B',
41 status: 'ONLINE',
42 power: 100,
43 engine: markRaw(new HeavySimulationEngine('Optimized Engine')),
44 lastUpdate: new Date().toISOString()
45})
46
47// Reactivity check
48const reactiveEngineIsReactive = isReactive(reactiveReactor.engine)
49const optimizedEngineIsReactive = isReactive(optimizedReactor.engine)
50
51// Counters for performance demonstration
52const reactiveSimulationCount = ref(0)
53const optimizedSimulationCount = ref(0)
54
55function runReactiveSimulation() {
56 const start = performance.now()
57
58 // Modification of the reactive engine
59 reactiveReactor.engine.simulate()
60 reactiveReactor.lastUpdate = new Date().toISOString()
61 reactiveSimulationCount.value++
62
63 const end = performance.now()
64 console.log(`Reactive simulation: ${(end - start).toFixed(2)}ms`)
65}
66
67function runOptimizedSimulation() {
68 const start = performance.now()
69
70 // Modification of the markRaw engine
71 optimizedReactor.engine.simulate()
72 optimizedReactor.lastUpdate = new Date().toISOString()
73 optimizedSimulationCount.value++
74
75 const end = performance.now()
76 console.log(`Optimized simulation: ${(end - start).toFixed(2)}ms`)
77}
78
79// Practical example - map with many objects
80const sensorNetwork = reactive({
81 location: 'Mars Base Alpha',
82 activeSensors: 0,
83 sensors: markRaw(new Map([
84 ['TEMP-001', { type: 'temperature', value: 22 }],
85 ['PRES-001', { type: 'pressure', value: 101 }],
86 ['OXY-001', { type: 'oxygen', value: 21 }]
87 ]))
88})
89
90function addSensor() {
91 const id = `SENS-${Date.now()}`
92 sensorNetwork.sensors.set(id, {
93 type: 'generic',
94 value: Math.random() * 100
95 })
96 // We update the reactive counter
97 sensorNetwork.activeSensors = sensorNetwork.sensors.size
98}
99
100function removeSensor() {
101 const keys = Array.from(sensorNetwork.sensors.keys())
102 if (keys.length > 0) {
103 sensorNetwork.sensors.delete(keys[keys.length - 1])
104 sensorNetwork.activeSensors = sensorNetwork.sensors.size
105 }
106}
107
108// toRaw() - access to the original object
109function getRawReactor() {
110 const raw = toRaw(reactiveReactor)
111 console.log('Raw reactor (non-reactive):', raw)
112 console.log('Is reactive?', isReactive(raw)) // false
113}
114
115// Example with external library integration
116const chartConfig = reactive({
117 title: 'Reactor Power Output',
118 // Large chart library - we don't need it reactive
119 chartInstance: markRaw({
120 render() {
121 return 'Chart rendered'
122 },
123 update(data) {
124 console.log('Chart updated with:', data)
125 },
126 destroy() {
127 console.log('Chart destroyed')
128 }
129 })
130})
131
132function updateChart() {
133 chartConfig.chartInstance.update({
134 power: reactiveReactor.power,
135 timestamp: new Date()
136 })
137}
138</script>
139
140<template>
141 <div class="mark-raw-demo">
142 <div class="header">
143 <h1>REACTOR CONTROL - NOVA LAB</h1>
144 <p class="subtitle">markRaw() and toRaw() - Advanced reactivity control</p>
145 </div>
146
147 <!-- Performance comparison -->
148 <div class="comparison-section">
149 <h2>Performance comparison: Reactive vs markRaw</h2>
150
151 <div class="reactors-grid">
152 <!-- Reactor A - Reactive -->
153 <div class="reactor-card">
154 <div class="reactor-header">
155 <h3>{{ reactiveReactor.id }}</h3>
156 <span class="badge badge-reactive">REACTIVE</span>
157 </div>
158
159 <div class="reactor-stats">
160 <div class="stat">
161 <div class="label">Status</div>
162 <div class="value">{{ reactiveReactor.status }}</div>
163 </div>
164 <div class="stat">
165 <div class="label">Power</div>
166 <div class="value">{{ reactiveReactor.power }}%</div>
167 </div>
168 <div class="stat">
169 <div class="label">Engine iterations</div>
170 <div class="value">{{ reactiveReactor.engine.iterations }}</div>
171 </div>
172 <div class="stat">
173 <div class="label">Engine reactive?</div>
174 <div class="value">{{ reactiveEngineIsReactive ? 'YES' : 'NO' }}</div>
175 </div>
176 </div>
177
178 <div class="simulation-info">
179 <p>Simulations run: <strong>{{ reactiveSimulationCount }}</strong></p>
180 <p class="warning">Every change in the engine triggers reactivity!</p>
181 </div>
182
183 <button @click="runReactiveSimulation" class="btn btn-primary">
184 ▶Run simulation
185 </button>
186
187 <div class="code-box">
188 <pre>// Normal object - reactive
189const reactor = reactive({
190 engine: new Engine()
191})
192// engine and all its properties
193// are reactive Slower!</pre>
194 </div>
195 </div>
196
197 <!-- Reactor B - Optimized -->
198 <div class="reactor-card">
199 <div class="reactor-header">
200 <h3>{{ optimizedReactor.id }}</h3>
201 <span class="badge badge-optimized">OPTIMIZED</span>
202 </div>
203
204 <div class="reactor-stats">
205 <div class="stat">
206 <div class="label">Status</div>
207 <div class="value">{{ optimizedReactor.status }}</div>
208 </div>
209 <div class="stat">
210 <div class="label">Power</div>
211 <div class="value">{{ optimizedReactor.power }}%</div>
212 </div>
213 <div class="stat">
214 <div class="label">Engine iterations</div>
215 <div class="value">{{ optimizedReactor.engine.iterations }}</div>
216 </div>
217 <div class="stat">
218 <div class="label">Engine reactive?</div>
219 <div class="value">{{ optimizedEngineIsReactive ? 'YES' : 'NO' }}</div>
220 </div>
221 </div>
222
223 <div class="simulation-info">
224 <p>Simulations run: <strong>{{ optimizedSimulationCount }}</strong></p>
225 <p class="success">The engine is not reactive - more efficient!</p>
226 </div>
227
228 <button @click="runOptimizedSimulation" class="btn btn-success">
229 Run simulation (faster)
230 </button>
231
232 <div class="code-box">
233 <pre>// markRaw - not reactive
234const reactor = reactive({
235 engine: markRaw(new Engine())
236})
237// engine is NOT reactive
238// Faster!</pre>
239 </div>
240 </div>
241 </div>
242 </div>
243
244 <!-- Sensor network -->
245 <div class="sensors-section">
246 <h2>Sensor network (practical markRaw example)</h2>
247
248 <div class="sensors-card">
249 <div class="sensors-header">
250 <h3>{{ sensorNetwork.location }}</h3>
251 <div class="sensor-count">
252 Active sensors: <strong>{{ sensorNetwork.activeSensors }}</strong>
253 </div>
254 </div>
255
256 <div class="sensors-grid">
257 <div
258 v-for="[id, sensor] in sensorNetwork.sensors"
259 :key="id"
260 class="sensor-item"
261 >
262 <div class="sensor-id">{{ id }}</div>
263 <div class="sensor-type">{{ sensor.type }}</div>
264 <div class="sensor-value">{{ sensor.value.toFixed(2) }}</div>
265 </div>
266 </div>
267
268 <div class="controls">
269 <button @click="addSensor" class="btn btn-success">
270 Add sensor
271 </button>
272 <button @click="removeSensor" class="btn btn-danger">
273 Remove sensor
274 </button>
275 </div>
276
277 <div class="info">
278 <p>Sensor map uses <code>markRaw()</code></p>
279 <p>Only the <code>activeSensors</code> counter is reactive</p>
280 </div>
281
282 <div class="code-box">
283 <pre>const network = reactive({
284 activeSensors: 0,
285 // Map doesn't need to be reactive
286 sensors: markRaw(new Map())
287})
288
289// Update only the reactive counter
290network.activeSensors = network.sensors.size</pre>
291 </div>
292 </div>
293 </div>
294
295 <!-- Library integration -->
296 <div class="library-section">
297 <h2>Integration with external libraries</h2>
298
299 <div class="library-card">
300 <h3>{{ chartConfig.title }}</h3>
301
302 <div class="chart-placeholder">
303 <div class="chart-visual">
304 Reactor power chart
305 </div>
306 <p>Chart instance: {{ chartConfig.chartInstance.render() }}</p>
307 </div>
308
309 <button @click="updateChart" class="btn btn-info">
310 Update chart
311 </button>
312
313 <div class="code-box">
314 <pre>import Chart from 'chart-library'
315
316const config = reactive({
317 title: 'My Chart',
318 // Large library - we don't need reactivity
319 chartInstance: markRaw(new Chart())
320})
321
322// Manually update when needed
323config.chartInstance.update(newData)</pre>
324 </div>
325 </div>
326 </div>
327
328 <!-- toRaw() demo -->
329 <div class="toraw-section">
330 <h2>toRaw() - Access to the original object</h2>
331
332 <div class="toraw-card">
333 <p>Sometimes you need access to the original, non-reactive object</p>
334
335 <button @click="getRawReactor" class="btn btn-warning">
336 Get RAW reactor (check console)
337 </button>
338
339 <div class="code-box">
340 <pre>const reactive = reactive({ value: 1 })
341
342// Get the original object
343const raw = toRaw(reactive)
344
345// Changes in raw will NOT trigger reactivity
346raw.value = 2
347
348console.log(isReactive(raw)) // false</pre>
349 </div>
350
351 <div class="use-cases">
352 <h4>When to use toRaw()?</h4>
353 <ul>
354 <li>Passing to API that doesn't need reactivity</li>
355 <li>Operations on raw data without triggering reactivity</li>
356 <li>Debugging - checking original values</li>
357 </ul>
358 </div>
359 </div>
360 </div>
361
362 <!-- Info box -->
363 <div class="info-box">
364 <h3>When to use markRaw()?</h3>
365 <div class="use-cases-grid">
366 <div class="use-case">
367 <h4>Large data objects</h4>
368 <p>Gigantic data structures that don't need to be reactive</p>
369 </div>
370 <div class="use-case">
371 <h4>External libraries</h4>
372 <p>Chart.js, Three.js, D3.js instances - don't need reactivity</p>
373 </div>
374 <div class="use-case">
375 <h4>Map, Set, WeakMap</h4>
376 <p>Collections you change frequently, but don't need to track every change</p>
377 </div>
378 <div class="use-case">
379 <h4>Performance optimization</h4>
380 <p>When reactivity slows down the application unnecessarily</p>
381 </div>
382 </div>
383
384 <div class="warning-box">
385 <h4>Warning!</h4>
386 <p><code>markRaw()</code> is irreversible - the object will never become reactive!</p>
387 <p>Use only when you are sure that reactivity is not needed.</p>
388 </div>
389 </div>
390 </div>
391</template>
392
393<style scoped>
394.mark-raw-demo {
395 min-height: 100vh;
396 background: #0a0e27;
397 color: #e0e0e0;
398 padding: 2rem;
399 font-family: 'Courier New', monospace;
400}
401
402.header {
403 text-align: center;
404 margin-bottom: 2rem;
405 border-bottom: 2px solid #00ff88;
406 padding-bottom: 1rem;
407}
408
409.header h1 {
410 color: #00ff88;
411 font-size: 2rem;
412 margin: 0;
413 text-shadow: 0 0 10px #00ff88;
414}
415
416.subtitle {
417 color: #00b4d8;
418 margin: 0.5rem 0 0 0;
419}
420
421.comparison-section,
422.sensors-section,
423.library-section,
424.toraw-section {
425 background: #1a1f3a;
426 padding: 1.5rem;
427 border-radius: 8px;
428 border: 1px solid #00b4d8;
429 margin-bottom: 2rem;
430}
431
432h2 {
433 color: #00ff88;
434 margin-top: 0;
435}
436
437.reactors-grid {
438 display: grid;
439 grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
440 gap: 1.5rem;
441}
442
443.reactor-card,
444.sensors-card,
445.library-card,
446.toraw-card {
447 background: #0a0e27;
448 padding: 1.5rem;
449 border-radius: 6px;
450}
451
452.reactor-header,
453.sensors-header {
454 display: flex;
455 justify-content: space-between;
456 align-items: center;
457 margin-bottom: 1.5rem;
458 padding-bottom: 1rem;
459 border-bottom: 1px solid #333;
460}
461
462.reactor-header h3,
463.sensors-header h3 {
464 color: #00b4d8;
465 margin: 0;
466}
467
468.badge {
469 padding: 0.3rem 0.8rem;
470 border-radius: 20px;
471 font-size: 0.8rem;
472 font-weight: bold;
473}
474
475.badge-reactive {
476 background: #ff8800;
477 color: #0a0e27;
478}
479
480.badge-optimized {
481 background: #00ff88;
482 color: #0a0e27;
483}
484
485.reactor-stats {
486 display: grid;
487 grid-template-columns: repeat(2, 1fr);
488 gap: 1rem;
489 margin-bottom: 1.5rem;
490}
491
492.stat {
493 background: #1a1f3a;
494 padding: 1rem;
495 border-radius: 4px;
496}
497
498.stat .label {
499 font-size: 0.85rem;
500 color: #888;
501 margin-bottom: 0.3rem;
502}
503
504.stat .value {
505 font-size: 1.2rem;
506 color: #00ff88;
507 font-weight: bold;
508}
509
510.simulation-info {
511 background: #1a1f3a;
512 padding: 1rem;
513 border-radius: 4px;
514 margin-bottom: 1rem;
515}
516
517.simulation-info p {
518 margin: 0.5rem 0;
519}
520
521.simulation-info strong {
522 color: #00ff88;
523}
524
525.warning {
526 color: #ff8800;
527}
528
529.success {
530 color: #00ff88;
531}
532
533.btn {
534 width: 100%;
535 padding: 1rem;
536 font-size: 1rem;
537 border: none;
538 border-radius: 6px;
539 cursor: pointer;
540 font-weight: bold;
541 transition: all 0.3s ease;
542 font-family: 'Courier New', monospace;
543 margin-bottom: 1rem;
544}
545
546.btn-primary {
547 background: linear-gradient(135deg, #00b4d8, #0088cc);
548 color: white;
549}
550
551.btn-success {
552 background: linear-gradient(135deg, #00ff88, #00dd77);
553 color: #0a0e27;
554}
555
556.btn-danger {
557 background: linear-gradient(135deg, #ff4444, #cc0000);
558 color: white;
559}
560
561.btn-info {
562 background: linear-gradient(135deg, #00b4d8, #00ff88);
563 color: #0a0e27;
564}
565
566.btn-warning {
567 background: linear-gradient(135deg, #ffaa00, #ff8800);
568 color: #0a0e27;
569}
570
571.btn:hover {
572 transform: scale(1.02);
573 box-shadow: 0 0 20px currentColor;
574}
575
576.code-box {
577 background: #1a1f3a;
578 padding: 1rem;
579 border-radius: 4px;
580 border-left: 3px solid #00ff88;
581}
582
583.code-box pre {
584 margin: 0;
585 color: #00ff88;
586 font-size: 0.85rem;
587 overflow-x: auto;
588}
589
590.sensor-count {
591 color: #00ff88;
592 font-weight: bold;
593}
594
595.sensors-grid {
596 display: grid;
597 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
598 gap: 1rem;
599 margin: 1.5rem 0;
600}
601
602.sensor-item {
603 background: #1a1f3a;
604 padding: 1rem;
605 border-radius: 4px;
606 border-left: 3px solid #00b4d8;
607}
608
609.sensor-id {
610 color: #00b4d8;
611 font-weight: bold;
612 font-size: 0.9rem;
613}
614
615.sensor-type {
616 color: #888;
617 font-size: 0.85rem;
618 margin: 0.3rem 0;
619 text-transform: uppercase;
620}
621
622.sensor-value {
623 color: #00ff88;
624 font-size: 1.2rem;
625 font-weight: bold;
626}
627
628.controls {
629 display: flex;
630 gap: 1rem;
631}
632
633.controls .btn {
634 flex: 1;
635}
636
637.info {
638 background: #1a1f3a;
639 padding: 1rem;
640 border-radius: 4px;
641 margin-top: 1rem;
642}
643
644.info p {
645 margin: 0.5rem 0;
646 color: #ccc;
647}
648
649.info code {
650 background: #0a0e27;
651 padding: 0.2rem 0.5rem;
652 border-radius: 3px;
653 color: #00ff88;
654}
655
656.chart-placeholder {
657 background: #1a1f3a;
658 padding: 2rem;
659 border-radius: 4px;
660 text-align: center;
661 margin: 1rem 0;
662}
663
664.chart-visual {
665 font-size: 3rem;
666 margin-bottom: 1rem;
667}
668
669.use-cases {
670 background: #1a1f3a;
671 padding: 1rem;
672 border-radius: 4px;
673 margin-top: 1rem;
674}
675
676.use-cases h4 {
677 color: #00b4d8;
678 margin-top: 0;
679}
680
681.use-cases ul {
682 color: #ccc;
683 padding-left: 1.5rem;
684}
685
686.use-cases li {
687 margin: 0.5rem 0;
688}
689
690.info-box {
691 background: #1a1f3a;
692 padding: 1.5rem;
693 border-radius: 8px;
694 border-left: 4px solid #00ff88;
695}
696
697.info-box h3 {
698 color: #00ff88;
699 margin-top: 0;
700}
701
702.use-cases-grid {
703 display: grid;
704 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
705 gap: 1rem;
706 margin: 1rem 0;
707}
708
709.use-case {
710 background: #0a0e27;
711 padding: 1rem;
712 border-radius: 4px;
713}
714
715.use-case h4 {
716 color: #00b4d8;
717 margin-top: 0;
718}
719
720.use-case p {
721 color: #ccc;
722 margin: 0;
723 font-size: 0.9rem;
724}
725
726.warning-box {
727 background: #2a1f1a;
728 border: 2px solid #ff8800;
729 padding: 1rem;
730 border-radius: 4px;
731 margin-top: 1rem;
732}
733
734.warning-box h4 {
735 color: #ffaa00;
736 margin-top: 0;
737}
738
739.warning-box p {
740 color: #ccc;
741 margin: 0.5rem 0;
742}
743
744.warning-box code {
745 background: #0a0e27;
746 padding: 0.2rem 0.5rem;
747 border-radius: 3px;
748 color: #00ff88;
749}
750</style>Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is the recommended approach to reactivity in Vue 3?
2. What mechanism does Vue 3 use to implement reactivity?
These are 2 of 11 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Create a reactive object and wrap it in readonly() to protect it from changes.
- Vertical ordering
Arrange the steps for using readonly in a component in the correct order.
- Click in order
Arrange the steps for using markRaw with an external library in the correct order.
- Code editor
Use markRaw() for an object that doesn't need reactivity (e.g., a library instance or configuration).
- Vertical ordering
Arrange the steps for optimizing with triggerRef() in the correct order.
- Click in order
Arrange the import statement for reactive functions in the correct order.
- Vertical ordering
Arrange the reactivity concepts from simplest to most advanced.
- Vertical ordering
Arrange the steps for using shallowRef in the correct order.
- Vertical ordering
Arrange the steps for designing state in a component in the correct order.
- Click in order
Arrange the steps for creating a reactive form in the correct order.