iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Props

Vue props: defineProps, types, required vs optional, defaults, validation, and the one-way data flow rule.

Vue — props

EXAMPLE
<!-- ===== Basic props ===== -->
<!-- ChildComp.vue -->
<script setup>
const props = defineProps(['title', 'count']);
console.log(props.title);
</script>

<template>
  <h2>{{ title }} ({{ count }})</h2>
</template>

<!-- Use: -->
<ChildComp title="Hello" :count="3" />

<!-- ===== Typed props (TS or object form) ===== -->
<script setup lang="ts">
defineProps<{
  title: string;
  count?: number;
  user: { id: number; name: string };
}>();
</script>

<!-- ===== Object syntax with defaults + validation ===== -->
<script setup>
defineProps({
  title: { type: String, required: true },
  count: { type: Number, default: 0 },
  user: { type: Object, required: true },
  tags: { type: Array, default: () => [] },     // defaults for objects / arrays must be factories
  status: {
    type: String,
    default: 'new',
    validator: (v) => ['new', 'shipped', 'cancelled'].includes(v),
  },
});
</script>

<!-- ===== With defaults helper (script setup) ===== -->
<script setup>
const props = withDefaults(defineProps<{
  title: string;
  count?: number;
  active?: boolean;
}>(), {
  count: 0,
  active: false,
});
</script>

<!-- ===== Boolean attribute shorthand ===== -->
<MyChip active />              <!-- shorthand for :active="true" -->
<MyChip :active="false" />

<!-- ===== One-way data flow rule ===== -->
<!-- Parent owns the value; child MUST NOT mutate the prop directly. -->
<script setup>
const props = defineProps(['count']);
// props.count = 99;          // dev warning + bug

// Convert to local if you need a writable copy:
import { ref, watch } from 'vue';
const local = ref(props.count);
watch(() => props.count, (v) => local.value = v);
</script>

<!-- For two-way communication, prefer events: -->
<button @click="$emit('update:count', local + 1)">+</button>
<!-- Parent: <Child v-model:count="value" /> -->

<!-- ===== Passing as v-model ===== -->
<!-- Child.vue -->
<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
</script>
<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>

<!-- Parent uses v-model: -->
<Child v-model="name" />

<!-- ===== Static vs dynamic ===== -->
<MyChip color="blue" />        <!-- static, always 'blue' -->
<MyChip :color="theme.color" /> <!-- dynamic, reactive -->

<!-- ===== Destructuring (be careful) ===== -->
<script setup>
const props = defineProps(['title']);
const { title } = props;           // breaks reactivity if title changes; rarely what you want

// Use computed for reactive transforms:
import { computed } from 'vue';
const upper = computed(() => props.title.toUpperCase());
</script>

<!-- ===== Patterns to internalise =====
- Type props (TS) or object form with required / default / validator
- Defaults for arrays / objects as factory functions
- One-way down; emit events for change
- v-model:fieldName for two-way binding sugar
-->

<!-- ===== Pitfalls =====
- Mutating props directly (dev warning + bug)
- Destructuring props in setup -> loses reactivity
- Forgetting factory defaults for objects/arrays
- Boolean props passed as string 'false' (which is truthy)
-->

Why it matters

Props are the contract between parent and child: types, required, defaults, validator. One-way down; use events or v-model:field for change. Type with TS or object form, factory defaults for objects/arrays, computed for derived values, and props.x.subfield modifications happen at the parent.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<!-- Child.vue -->
<script setup>
defineProps({ name: String, age: { type: Number, default: 0 } });
</script>
<template>
    <h2>{{ name }} ({{ age }})</h2>
</template>
Try it Yourself »

Discussion

Loading…