In our Dear React, Meet Vue series, we’ve built a basic Tic-Tac-Toe with props/emits, reactive arrays, and computed state. Today, we’ll layer on:
- Collecting player names and form validation
- Focusing inputs as soon as the form appears
- Watching game state changes for side-effects
You’ll see how Vue lets you treat state as plain JS, emit events instead of callbacks, and hook into reactivity without boilerplate hook arrays.
1. Player Setup Form: v-model
for Two-Way Binding
Imagine asking teammates for their names before kickoff. In React, you’d juggle value
, onChange
, and manual setters. In Vue:
<input v-model="names.x" placeholder="Player X name" />
<span v-if="errors.x">Name required</span>
v-model
secretly wires up both :value="names.x"
and @input="names.x = $event.target.value"
. No setNames
calls—just assign to names.x
as if it were a plain variable. Reactive errors.x
flips to “Name required” the moment names.x
is empty, thanks to a watchEffect
rerunning our tiny validator.
2. Lifecycle Hook: Auto-Focus
You wouldn’t ask players to introduce themselves without a microphone—so let’s focus the first input. In React you’d write a useEffect with an empty array; in Vue:
onMounted(() => {
formRef.value.querySelector('input').focus()
})
onMounted
fires exactly once, after the form renders—no “did I forget a dependency?” paranoia.
Template ref="formRef"
gives you a direct line to the DOM, no extra hook arrays or refs juggling. Checkout the Vue docs for Lifecycle Diagram.
3. Reactive Side-Effects: watch
vs. watchEffect
Once our players are named and the game starts, we want to log each move and celebrate the winner with a toast. In React you might sprinkle useEffect hooks everywhere; in Vue:
watch(squares, (newBoard, oldBoard) => {
console.log('Move made:', newBoard)
})
watch(winner, (who) => {
if (who) alert(`${who} wins!`)
})
watch
tracks specific sources (squares, winner) and only fires when they actually change. No dependency arrays required; Vue’s Proxy and computed graph handle it for you.
For quick-and-dirty reactions (like auto-validating names), watchEffect
reruns whenever any dependency inside changes.
4. Move History: Rewind & Replay
As the React tutorial explores “time travel” in their Tic-Tac-Toe, we’ll do the same in Vue—only with way less boilerplate:
// 1. History stores board snapshots
const history = reactive([Array(9).fill(null)])
const step = ref(0)
// 2. Compute the current board from history
const squares = computed(() => history[step.value])
function handleSelect(i) {
if (squares.value[i] || winner.value) return
// 3. Clone and mutate the current snapshot
const next = squares.value.slice()
next[i] = xIsNext.value ? 'X' : 'O'
// 4. If we rewound, toss out future snapshots
history.splice(step.value + 1)
// 5. Record the new state and advance
history.push(next)
step.value++
}
-
Reactive history: Think of
history
as our DVR. Vue wraps it in a Proxy, so calls tohistory.splice()
orhistory.push()
magically update any computed or template bound to it, no moresetHistory
needed. -
Step pointer: Our
step
is the remote’s playhead. Changingstep.value
rewinds or fast-forwards to that snapshot, and the board redraws itself. -
Computed board:
squares
is justcomputed(() => history[step.value])
. Vue auto-tracks dependencies, so you never have to re-initialize or call a setter,history
drives everything.
In React land, you’d juggle useState
for both history
and step
, splice with spreads, and call setHistory([...history.slice(0, step+1), next])
on every move. Vue’s approach? State is plain JavaScript. You mutate history
in place, Vue’s reactivity patches only the diffs, and computed props flow downstream automatically. No spreads, no manual setters; just intuitive array ops that feel like second nature.
Wrapping Up & What’s Next
We’ve come a long way from our initial Vite setup—discovering how Vue’s template-first syntax, ref
/reactive
state, props
/emits
, computed
, watch
, v-model
, and lifecycle hooks replace React’s hooks and boilerplate with a more intuitive, JS-native mental model. We built a fully reactive Tic-Tac-Toe, added a player-setup form with instant validation, and even rewound the game with move history—all without a single setState spread or dependency array to juggle.
Next up, we’ll take a step back and really think in Vue—mirroring React’s “Thinking in React” approach. We’ll break down how to decompose UIs into components the Vue way, organize data flow, and architect apps around Vue’s core mental model.
Stay tuned!
Top comments (0)