Skip to content

Vue Introduction

Vue is a versatile and beginner-friendly JavaScript framework. This introduction covers what is relevant to our codebase.

We use the Composition API with <script setup>, which is the modern recommended style for Vue 3.

A typical .vue file includes:

  • A <template> block for HTML markup
  • A <script setup> block for TypeScript logic
  • A <style scoped> block for CSS styling
<template>
<div class="flex flex-col gap-2">
<!-- Child content here -->
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<style scoped>
/* Component-specific styles */
</style>

Use ref() for primitive values and reactive() for objects:

import { ref, reactive } from 'vue'
const count = ref(0)
const user = reactive({ name: 'Alice', age: 20 })
count.value++ // access .value in script
user.name = 'Bob' // direct access for reactive

In <template>, refs are auto-unwrapped (no .value needed):

<p>{{ count }}</p>
<AutonWaypointItem
v-for="(waypoint, index) in waypoints"
:key="waypoint.id"
:waypoint="waypoint"
:index="index"
@delete="removeWaypoint(index)"
/>

Only render the element if the condition is true.

<button
v-if="!enabled"
class="cmd-btn cmd-btn-danger"
@click="toggle"
>
Disabled
</button>
<button
v-else
class="cmd-btn cmd-btn-success"
@click="toggle"
>
Enabled
</button>

Bind a function to a DOM event:

<button @click="handleClick">Click me</button>
<input @keydown.enter="submitForm" />

Derived values that automatically update when their dependencies change:

import { ref, computed } from 'vue'
const items = ref([1, 2, 3])
const total = computed(() => items.value.reduce((a, b) => a + b, 0))

Run side effects when a reactive value changes:

import { ref, watch } from 'vue'
const query = ref('')
watch(query, (newVal) => {
console.log('Query changed:', newVal)
})
import { onMounted, onBeforeUnmount } from 'vue'
onMounted(() => {
document.addEventListener('keydown', handleKey)
})
onBeforeUnmount(() => {
document.removeEventListener('keydown', handleKey)
})

Common hooks:

  • onMounted() - after component is added to the DOM
  • onBeforeUnmount() - before component is removed, for cleanup
  • onUpdated() - after any DOM update

With <script setup>, imported components are automatically available in the template without registration:

<script lang="ts" setup>
import GamepadDisplay from './GamepadDisplay.vue'
import IndicatorDot from './IndicatorDot.vue'
</script>
<template>
<GamepadDisplay :axes="axes" :buttons="buttons" />
<IndicatorDot :is-active="connected" />
</template>

We use Pinia (not Vuex) for shared state across components:

import { useWebsocketStore } from '@/stores/websocket'
const websocketStore = useWebsocketStore()
websocketStore.sendMessage('arm', { type: 'ra_controller', axes, buttons })

Pinia stores are defined in src/stores/ and accessed via composable functions (useXxxStore).