Appearance
The mental model shift that actually matters
Vue updates the thing that changed. React re-runs the function and diffs the result. That one sentence explains half the confusion.
If you take only one idea from this whole tutorial - take this: React components are functions that return UI for the current state. Call them again with new state → new UI description → React patches the DOM.
Templates vs JSX
In Vue you write a template, and the compiler turns it into render code:
vue
<template>
<button @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>In React you write the render yourself with JSX (which is still just function calls under the hood):
tsx
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
)
}Comments for the code above:
- No separate
<template>/<script>blocks - it's one function onClickinstead of@click(DOM property names, camelCase){count}instead of- expression holes in JSX- You don't mutate
count- you ask React to set a new value
Reactivity vs re-render
Vue tracks which properties you read and updates only the dependents. Feels surgical.
React doesn't track property access that way. When state changes, the component function runs again. Child components may re-render too (unless you memoize - and you shouldn't start there).
So instincts change:
| Vue habit | React habit |
|---|---|
Mutate a ref / reactive object | Call a setter with a new value |
computed(() => …) | Derive during render: const full = first + ' ' + last |
watch(source, cb) | Often useEffect - or better, derive / handle in the event |
| Fine-grained updates | Whole function re-runs; trust the reconciler |
Composition API ≈ hooks (sort of)
If you live in <script setup>, hooks won't feel alien:
| Vue | React |
|---|---|
ref(0) | useState(0) |
reactive({…}) | usually useState({…}) or multiple states |
computed | plain derived value in render |
watch / watchEffect | useEffect (careful) |
onMounted | useEffect(() => {…}, []) |
provide / inject | Context |
composables (useX) | custom hooks (useX) |
The big difference: hooks have rules. Call them at the top level of the component (or another hook), every render, same order. No conditional useState. Vue composables are more chill about that.
Props and "slots"
Props are props. Named slots become… composition with children (and sometimes render props). We'll get there in the components chapter.
Default slot in Vue:
vue
<Card>
<p>Hello</p>
</Card>In React:
tsx
<Card>
<p>Hello</p>
</Card>Looks the same! Inside Card, you read props.children instead of <slot />.
What I'd keep in your head
- Component = function of state + props
- State updates are immutable (new value, not mutate-in-place)
- Effects are for syncing with the outside world - not for "when X changes, set Y" (that one is a classic trap)
- Your Vue Router / Pinia instincts still help - the libraries just have different names
Once that clicks, JSX syntax is just noise. Next we'll scaffold a Vite React app and poke at it.
Enjoy!