Files
it-know-how/vue/03-Components, Props, Lifecycle, and Composables.md
2026-03-27 12:58:01 +01:00

4.2 KiB
Executable File

Components, Props, Lifecycle, and Composables

This note explains how Vue code is organized into reusable pieces.

Components

A component is a reusable UI unit.

Examples from your project:

  • FlexibilityTable.vue
  • FileUpload.vue
  • AppHeader.vue

A parent component can render a child component.

<template>
  <UserCard />
</template>

<script setup lang="ts">
import UserCard from './UserCard.vue';
</script>

Props: inputs passed into a component

Props are like function arguments for components.

Example:

<template>
  <UserCard :name="username" :age="34" />
</template>

Child component:

<script setup lang="ts">
interface Props {
  name: string;
  age: number;
}

const props = defineProps<Props>();
</script>

<template>
  <p>{{ props.name }} is {{ props.age }} years old.</p>
</template>

Python comparison

This is similar to passing constructor arguments or function arguments.

render_user_card(name=username, age=34)

Your real example

In FlexibilityTable:

interface FlexibilityTableProps {
  title: string;
  uploadMode?: 'fsp' | 'tso' | 'both' | 'none';
}

const props = withDefaults(defineProps<FlexibilityTableProps>(), {
  uploadMode: 'none',
});

This means:

  • the parent must give title
  • the parent may give uploadMode
  • if uploadMode is missing, use 'none'

Then template code can use:

<v-toolbar-title>
  {{ title }}
</v-toolbar-title>

and script code can use:

props.uploadMode

Lifecycle: when should code run?

Sometimes you want code to run when the component appears.

import { onMounted } from 'vue';

onMounted(() => {
  console.log('Component is mounted');
});

In your component:

onMounted(async () => {
  await refreshTableDataFromBackend();
});

That means when the table component is loaded, it immediately fetches backend data.

Composables

A composable is a reusable function that contains Vue logic.

Common pattern:

export function useCounter() {
  const count = ref(0);

  function increment() {
    count.value += 1;
  }

  return {
    count,
    increment,
  };
}

Use it inside a component:

const { count, increment } = useCounter();

Why composables exist

Without composables, components get too big.

Instead of putting all API logic, auth logic, upload logic, and export logic directly in one component, you move reusable logic into composables.

Your project has examples:

  • useJsonDownload()
  • useJsonUpload()
  • useAuth()

Real example: useJsonDownload

In FlexibilityTable:

const { downloadFlexData, downloadRawFlexData } = useJsonDownload();

That means:

  • this component imports data-loading logic from a composable
  • the composable handles HTTP and backend transformation details
  • the component only calls its functions

This is similar to separating Python business logic into a helper module.

Python style:

from downloads import download_flex_data

rows = download_flex_data(payload)

Vue composable style:

const { downloadFlexData } = useJsonDownload();
const rows = await downloadFlexData(payload);

Child-to-parent communication

Props go down from parent to child.

Events often go up from child to parent.

Example:

<FileUpload @upload="onFspUpload" />

This means the child component emits an upload event, and the parent runs onFspUpload when that happens.

Example structure

<template>
  <SearchBox @search="runSearch" />
</template>

<script setup lang="ts">
function runSearch(query: string) {
  console.log('search query:', query);
}
</script>

The child says: "something happened". The parent decides what to do.

Rule of Thumb

Put code in a component when:

  • it is very local to one UI element
  • it mostly controls rendering

Put code in a composable when:

  • it will be reused
  • it handles data fetching
  • it contains business logic
  • it makes the component easier to read

Reading Strategy

When you see a component, identify:

  1. props coming in
  2. local reactive state
  3. composables being used
  4. lifecycle hooks
  5. events going to child components