Props and Events
By Syncfusion®, Inc. · Last updated
defineProps() with runtime and type-based declarations, prop types, required props, defaults and validators, reactive props destructure, one-way data flow, deriving local values, $emit and defineEmits(), typed events, component v-model with defineModel(), many models and modifiers, and how components talk.
Talking Between Components
- A parent often needs to give data to a child.
- A child often needs to tell the parent something happened.
- Props = data passed down from parent to child.
- Events = messages sent up from child to parent.
Animation: the parent App sends props name and price down to DishCard. DishCard sends an add event back up to App
App (parent)
DishCard (child)
props ↓ name, price events ↑ add
Props go down. Events go up.
Simple lesson: props down, events up.
A Real-World Example
- Meera's menu uses one
DishCardfor every dish. - The parent passes each card a name and a price.
- When someone taps Add, the card tells the parent, and the parent updates the cart.
- What happened: each card got its own
nameandprice. - Add sent an
addevent with the dish name. The parent pushed it into the cart. - Switch the top bar to JavaScript to see the same component without types.
What You Will Learn
- How to declare props with
defineProps(). - Two ways to declare them: runtime options and TypeScript types.
- Required props, defaults and validators.
- Why a child must not change its props.
- How to send events with
$emitanddefineEmits(). - How to send data with an event.
- How to make your own component work with
v-model. - How to use many
v-models and custom modifiers. - Which way to pass data in each situation.
defineProps()
defineProps()lists the props a component accepts.- It is a compiler macro: you do not import it.
- It returns an object with the prop values. In the template, use the names directly.
- Pass a fixed text:
name="Idli". Pass JavaScript (numbers, variables)::price="50". - Name props in camelCase in the script (
dishName). In templates both:dishNameand:dish-namework. - A
Booleanprop written with no value istrue:<DishCard featured />. Left out, it isfalse.
Runtime Declaration
- Describe each prop with an object:
type,required,defaultandvalidator. - This works in TypeScript and JavaScript, and Vue checks the values while the app runs.
- A
defaultfor an object or array must be a function that returns it.
A failed check, such as a missing required prop or a validator that returns false, gives a Vue warning in the Console. It does not stop the app. In production builds, these checks are skipped.
Type-Based Declaration (TypeScript)
- In TypeScript, give the props as a type:
defineProps<{ name: string; price?: number }>(). ?makes a prop optional.- Since Vue 3.5, you can destructure props and give defaults in one line. They stay reactive.
- Older code uses
withDefaults(defineProps<...>(), { price: 0 })for the same job. - The JavaScript flavour shows the runtime version of the same component.
Simple lesson: list props with defineProps(). Use a type in TypeScript, or runtime options anywhere.
One-Way Data Flow
- Props flow down only. When the parent's value changes, the child gets the new value.
- A child must never change a prop. Props are read-only.
- If a child changes a prop, Vue warns, and the parent may overwrite it later.
- Need to change it? Copy it into a local ref, or work out a new value with
computed. - A copy (
ref(props.price)) takes the starting value only. If the parent changes the prop later, the copy does not follow. Usecomputedwhen it must follow.
- What happened: "+1 (local)" changed the child's own copy. That is fine.
- "Change the prop" was blocked, and Vue wrote a warning in the Console.
Simple lesson: never change a prop. Copy it, or derive from it.
A local copy made with ref(props.start) does not follow later changes from the parent. Use computed when the child must always match the prop.
Events
- A child sends an event with
$emit('name', data)in the template. - In the script, declare events with
defineEmits(). It returns anemitfunction. - The parent listens with
@name="handler", like a normal DOM event. - Extra arguments to
emitreach the handler. In an inline handler they are$event.
- What happened: each star button emitted
ratewith two values. - The parent's
onRate(stars, dish)received both. - Clear used
$emitstraight from the template.
In TypeScript, defineEmits<{ rate: [stars: number] }>() types each event's data. Calling emit('rate', 'five') is then marked as an error in your editor. Runtime validation is also possible with an object of functions.
Simple lesson: declare events with defineEmits(). Send them with emit(). Listen with @.
v-model on Components
v-modelon a component = a prop down + an update event up.- Since Vue 3.4,
defineModel()creates both for you. - It returns a ref. Change it in the child, and the parent's ref changes too.
v-model:namegives a model a name, so one component can have many.
Custom Modifiers
- A parent can add its own modifier:
v-model.capitalize="dish". - The child reads it from the second value of
defineModel(). - A
setfunction changes the value before it reaches the parent.
Simple lesson: defineModel() gives a component v-model support in one line.
Which Way to Pass Data?
| Need | Use | Lesson |
|---|---|---|
| Parent gives data to a child | Props | this lesson |
| Child tells the parent something happened | Events | this lesson |
| Two-way value between parent and child | v-model with defineModel() | this lesson |
| Many levels down, skipping the middle | provide / inject | Slots, Attributes and Provide/Inject |
| Data shared by far-apart components | A Pinia store | State Management with Pinia |
Simple lesson: props down, events up, v-model for both, and other tools for long distances.
TL;DR
- Props = data passed down. Declared with
defineProps(). - Runtime props =
{ type, required, default, validator }. Works in JS and TS. - Type-based props =
defineProps<{ name: string }>(). TypeScript only. - Props destructure =
const { price = 0 } = defineProps(...)stays reactive (Vue 3.5). - One-way data flow = a child never changes its props.
- Events =
emit('name', data)up.@namein the parent. defineEmits()= declares events. Types each event's data in TS.defineModel()= a prop + update event as one ref, forv-model.v-model:name= many models on one component.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Changing a prop in the child | Props are like local variables | Copy into a ref, or emit an event | A warning, and the parent overwrites it |
price="50" for a number | Vue guesses the type | Write :price="50" | The child gets the text "50" |
| Object default without a function | default: [] is fine | default: () => [] | All instances share one array |
Forgetting defineEmits | emit exists by itself | const emit = defineEmits([...]) | emit is not defined |
| Listening for an event the child never sends | Any @name works | Match a name from defineEmits | The handler never runs |
Type-based props in a .js file | Types work everywhere | Use runtime options in JavaScript | A syntax error in JavaScript |
Local ref for v-model in the child | The parent sees the local ref | Use defineModel() | The parent's value never changes |
Check Yourself
Exercises
What to Remember
- Props go down, events go up.
- Declare props with runtime options, or with a type in TypeScript.
- Never change a prop. Copy it, derive from it, or emit an event.
defineEmits()declares events.emit('name', data)sends them.defineModel()makes a component work withv-model.
Next: Slots, Attributes and Provide/Inject