Appearance
Components and props (defineProps energy)
A React component is a function. Props are the arguments. That's the whole pitch.
A component with props
Vue:
vue
<script setup lang="ts">
defineProps<{ title: string; count?: number }>()
</script>
<template>
<h2>{{ title }} ({{ count ?? 0 }})</h2>
</template>React:
tsx
type CardProps = {
title: string
count?: number
}
function Card({ title, count = 0 }: CardProps) {
return (
<h2>
{title} ({count})
</h2>
)
}Comments for the code above:
- Type the props object - same idea as
defineProps<{…}>() - Default values via destructuring (
count = 0) instead ofwithDefaults - No
props.titlerequired if you destructure (you can keeppropsif you prefer)
children ≈ default slot
tsx
type PanelProps = {
title: string
children: React.ReactNode
}
function Panel({ title, children }: PanelProps) {
return (
<section>
<h3>{title}</h3>
<div>{children}</div>
</section>
)
}
// usage
<Panel title="Notes">
<p>Anything you put here is children.</p>
</Panel>As you can see - that's your default <slot />. Named slots don't have a first-class twin. People usually:
- Pass extra props that are React nodes:
footer={<Footer />} - Or split into smaller components
- Or use a render prop:
renderFooter={() => <Footer />}
One-way data flow (same story)
Parent owns state. Child gets values + callbacks:
tsx
function Parent() {
const [open, setOpen] = useState(false)
return (
<Modal
open={open}
onClose={() => setOpen(false)}
/>
)
}
type ModalProps = {
open: boolean
onClose: () => void
}
function Modal({ open, onClose }: ModalProps) {
if (!open) return null
return (
<div className="modal">
<button type="button" onClick={onClose}>
Close
</button>
</div>
)
}Comments for the code above:
v-modeldoesn't exist as magic - you passvalue+onChange/open+onClose- Naming
onSomethingfor callbacks is the community habit (like Vue'semit('close'))
Export styles
tsx
// default export - common for page-level components
export default function App() { /* … */ }
// named exports - nice for shared UI
export function Button() { /* … */ }I've been leaning named exports more lately - easier refactors, better autocomplete. Not a religion though.
File naming
Not enforced. Common patterns:
UserCard.tsxfor the componentuseUser.tsfor a custom hook- Colocate:
UserCard.tsx+UserCard.module.css
Next: state - useState and friends. This is where Vue ref muscle memory gets remapped.
Best,
-- ł.