Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Renderless Components i Functional Patterns

W Centrum Dowodzenia NOVA LAB nie wszystkie moduły interfejsu muszą mieć wizualną reprezentację. Niektóre odpowiadają wyłącznie za logikę — to renderless components. Poznajmy zaawansowane wzorce komponentów Vue.

Czym jest Renderless Component?

Renderless component to komponent, który nie renderuje własnego HTML — zamiast tego udostępnia logikę przez slot. Cały wygląd definiuje komponent-rodzic.

1<script setup>
2import { ref, onMounted, onUnmounted } from 'vue'
3
4// Renderless component — logika zegara
5const now = ref(new Date())
6let timer = null
7
8onMounted(() => {
9  timer = setInterval(() => {
10    now.value = new Date()
11  }, 1000)
12})
13
14onUnmounted(() => clearInterval(timer))
15
16defineExpose({ now })
17</script>
18
19<template>
20  <slot :now="now" :formatted="now.toLocaleTimeString('pl-PL')" />
21</template>

Użycie:

1<template>
2  <Clock v-slot="{ formatted }">
3    <div class="station-clock">{{ formatted }}</div>
4  </Clock>
5</template>

Komponent

Clock
nie ma własnego HTML — przekazuje dane przez scoped slot, a rodzic decyduje o wyglądzie.

Wzorzec Renderless z logiką toggle

Popularny wzorzec — komponent zarządzający stanem toggle:

1<!-- Toggle.vue -->
2<script setup>
3import { ref } from 'vue'
4
5const isOn = ref(false)
6
7function toggle() {
8  isOn.value = !isOn.value
9}
10
11function setOn() { isOn.value = true }
12function setOff() { isOn.value = false }
13</script>
14
15<template>
16  <slot :isOn="isOn" :toggle="toggle" :setOn="setOn" :setOff="setOff" />
17</template>

Użycie w różnych kontekstach:

1<template>
2  <!-- Jako przełącznik -->
3  <Toggle v-slot="{ isOn, toggle }">
4    <button @click="toggle">
5      System: {{ isOn ? 'AKTYWNY' : 'WYŁĄCZONY' }}
6    </button>
7  </Toggle>
8
9  <!-- Jako panel rozwijany -->
10  <Toggle v-slot="{ isOn, toggle }">
11    <div>
12      <h3 @click="toggle">Diagnostyka ▼</h3>
13      <div v-if="isOn">Szczegóły systemu...</div>
14    </div>
15  </Toggle>
16</template>

Functional Components

W Vue 3 każdy komponent z

<script setup>
jest już zoptymalizowany. Ale możesz też tworzyć proste komponenty funkcyjne jako zwykłe funkcje renderujące:

1import { h } from 'vue'
2
3// Functional component — nie ma stanu, tylko props
4function StationBadge(props) {
5  return h('span', {
6    class: 'badge',
7    style: {
8      color: props.color || '#00ff88',
9      border: '1px solid ' + (props.color || '#00ff88'),
10      padding: '4px 12px',
11      borderRadius: '12px',
12      fontSize: '12px'
13    }
14  }, props.label)
15}
16
17StationBadge.props = ['label', 'color']

Użycie w szablonie:

1<template>
2  <StationBadge label="ONLINE" color="#2ecc71" />
3  <StationBadge label="ALERT" color="#e74c3c" />
4</template>

Render Functions z h()

Funkcja

h()
(hyperscript) tworzy VNode — wirtualny element DOM:

1import { h } from 'vue'
2
3// h(tag, props, children)
4h('div', { class: 'panel' }, [
5  h('h2', null, 'Tytuł'),
6  h('p', null, 'Treść panelu')
7])

Komponent z render function zamiast szablonu:

1import { h, ref, defineComponent } from 'vue'
2
3const Counter = defineComponent({
4  setup() {
5    const count = ref(0)
6
7    return () => h('div', { class: 'counter' }, [
8      h('p', null, 'Licznik: ' + count.value),
9      h('button', {
10        onClick: () => count.value++
11      }, '+1')
12    ])
13  }
14})

Higher-Order Components (HOC)

HOC to funkcja, która przyjmuje komponent i zwraca nowy komponent z dodaną funkcjonalnością:

1import { h, ref, onMounted } from 'vue'
2
3function withLoading(WrappedComponent) {
4  return defineComponent({
5    setup(props, { attrs }) {
6      const loading = ref(true)
7
8      onMounted(() => {
9        setTimeout(() => {
10          loading.value = false
11        }, 1000)
12      })
13
14      return () => {
15        if (loading.value) {
16          return h('div', { class: 'loading' }, 'Ładowanie modułu...')
17        }
18        return h(WrappedComponent, { ...attrs })
19      }
20    }
21  })
22}

Użycie:

1const MissionPanelWithLoading = withLoading(MissionPanel)

Kiedy używać tych wzorców?

| Wzorzec | Kiedy stosować | |---------|---------------| | Renderless component | Logika wielokrotnego użytku z elastycznym UI | | Functional component | Proste, bezstanowe elementy prezentacyjne | | Render function (h()) | Dynamiczne generowanie struktury DOM | | HOC | Dodawanie zachowania do istniejących komponentów |

Renderless components i scoped slots to idiomatyczny sposób Vue na separację logiki od prezentacji — szczególnie przydatny w systemach tak złożonych jak Centrum Dowodzenia NOVA LAB.

Vai a CodeWorlds