Welcome to the NOVA LAB Science Laboratory! You already know the basics of composables - now it's time for advanced design patterns. Just as NOVA LAB engineers design modular space station systems, we will design composables ready for any challenge.
A factory composable is a function that creates composables with different configurations - like a station module factory where each module has the same interfaces but different parameters.
1import { ref, computed } from 'vue'
2
3// Factory: creates a composable for any resource
4function createResourceManager(resourceName, maxCapacity = 100) {
5 const currentLevel = ref(maxCapacity)
6 const isLow = computed(() => currentLevel.value < maxCapacity * 0.2)
7 const isCritical = computed(() => currentLevel.value < maxCapacity * 0.05)
8 const percentage = computed(() =>
9 Math.round((currentLevel.value / maxCapacity) * 100)
10 )
11
12 function consume(amount) {
13 currentLevel.value = Math.max(0, currentLevel.value - amount)
14 }
15
16 function refill(amount) {
17 currentLevel.value = Math.min(maxCapacity, currentLevel.value + amount)
18 }
19
20 return {
21 resourceName,
22 currentLevel,
23 maxCapacity,
24 percentage,
25 isLow,
26 isCritical,
27 consume,
28 refill
29 }
30}
31
32// Usage - each resource has the same methods but independent state
33const oxygen = createResourceManager('Oxygen', 500)
34const fuel = createResourceManager('Fuel', 1000)
35const water = createResourceManager('Water', 200)
36
37oxygen.consume(50) // does not affect fuel or waterComposables can use other composables - like connecting station modules into larger systems.
1import { ref, computed, watch } from 'vue'
2
3// Base composable
4function useNotifications() {
5 const notifications = ref([])
6
7 function addNotification(message, type = 'info') {
8 notifications.value.unshift({
9 id: Date.now(),
10 message,
11 type,
12 timestamp: new Date()
13 })
14 // Limit to 50 notifications
15 if (notifications.value.length > 50) {
16 notifications.value.pop()
17 }
18 }
19
20 function clearAll() {
21 notifications.value = []
22 }
23
24 return { notifications, addNotification, clearAll }
25}
26
27// Composable that uses another composable
28function useLabMonitor(thresholds) {
29 const temperature = ref(22)
30 const pressure = ref(101)
31 const { notifications, addNotification, clearAll } = useNotifications()
32
33 watch(temperature, (val) => {
34 if (val > thresholds.maxTemp) {
35 addNotification(
36 `Critical temperature: ${val}°C!`,
37 'danger'
38 )
39 }
40 })
41
42 watch(pressure, (val) => {
43 if (val < thresholds.minPressure) {
44 addNotification(
45 `Pressure below normal: ${val} kPa`,
46 'warning'
47 )
48 }
49 })
50
51 return {
52 temperature,
53 pressure,
54 notifications,
55 addNotification,
56 clearAll
57 }
58}Vue 3 allows dependency injection through
provide/inject - like central station systems accessible to every module.1import { ref, provide, inject } from 'vue'
2
3// Symbol as key - avoids name collisions
4const MissionContextKey = Symbol('MissionContext')
5
6// Composable providing context
7function provideMissionContext() {
8 const missionName = ref('Mars Exploration 2087')
9 const crewCount = ref(6)
10 const missionDay = ref(1)
11
12 const context = {
13 missionName,
14 crewCount,
15 missionDay,
16 advanceDay() { missionDay.value++ }
17 }
18
19 provide(MissionContextKey, context)
20 return context
21}
22
23// Composable consuming context
24function useMissionContext() {
25 const context = inject(MissionContextKey)
26 if (!context) {
27 throw new Error(
28 'useMissionContext requires provideMissionContext in a parent component'
29 )
30 }
31 return context
32}1<!-- ParentComponent.vue -->
2<script setup>
3const { missionName, missionDay, advanceDay } = provideMissionContext()
4</script>
5
6<!-- ChildComponent.vue (any nesting depth) -->
7<script setup>
8const { missionName, missionDay } = useMissionContext()
9// Automatically reactive - changes in parent update children
10</script>Every station system must have safeguards in case of failure.
1import { ref, computed } from 'vue'
2
3function useAsyncOperation(asyncFn) {
4 const data = ref(null)
5 const error = ref(null)
6 const loading = ref(false)
7 const retryCount = ref(0)
8 const MAX_RETRIES = 3
9
10 const hasError = computed(() => error.value !== null)
11 const canRetry = computed(() => retryCount.value < MAX_RETRIES)
12
13 async function execute(...args) {
14 loading.value = true
15 error.value = null
16
17 try {
18 data.value = await asyncFn(...args)
19 retryCount.value = 0 // Reset on success
20 } catch (e) {
21 error.value = e.message || 'Unknown error'
22 console.error(`Operation failed: ${error.value}`)
23 } finally {
24 loading.value = false
25 }
26 }
27
28 async function retry(...args) {
29 if (!canRetry.value) {
30 error.value = 'Retry limit exceeded'
31 return
32 }
33 retryCount.value++
34 await execute(...args)
35 }
36
37 return { data, error, loading, hasError, canRetry, retryCount, execute, retry }
38}
39
40// Usage
41const sensorData = useAsyncOperation(async (sensorId) => {
42 const response = await fetch(`/api/sensors/${sensorId}`)
43 if (!response.ok) throw new Error('Failed to fetch data')
44 return response.json()
45})
46
47// In a component
48await sensorData.execute('temp-sensor-1')
49if (sensorData.hasError.value && sensorData.canRetry.value) {
50 await sensorData.retry('temp-sensor-1')
51}