Vue 3 Composition API and Reactivity Fundamentals
Setup Function and Basic Reactivity
The setup() function serves as the entry point for Vue 3's Composition API, executing before component creation.
<template>
<div>Composition API: {{ message }}</div>
<div>Options API: {{ legacyMessage }}</div>
<div>
Array iteration:
<h5 v-for="(item, idx) in items" :key="idx">{{ item }}</h5>
</div>
<div>
<button @click="handleClick">Standard Function</button>
<button @click="arrowFunction">Arrow Function</button>
</div>
</template>
<script>
export default {
name: "DemoComponent",
data() {
return {
legacyMessage: "Vue 2 approach"
};
},
setup() {
console.log("Setup function executed");
const message = "All variables and functions defined here must be returned";
console.log(message);
const items = ["setup", "function", "usage"];
function handleClick() {
alert("Standard function in setup");
}
const arrowFunction = () => {
alert("Arrow function reference");
};
return { message, items, handleClick, arrowFunction };
}
};
</script>
<style scoped></style>
Reactive References and Data Types
Ref for Primitive Values
The ref() function creates reactive references for primitive data types.
<template>
<div>
<h1>Name: {{ userName }}</h1>
<h1>Age: {{ userAge }}</h1>
<h1 v-for="(num, idx) in numberArray" :key="idx">Array item: {{ num }}</h1>
<h1 v-for="(entry, idx) in objectCollection" :key="idx">
Object properties:
<h5 v-for="(prop, propIdx) in entry" :key="propIdx">{{ prop }}</h5>
</h1>
<button @click="updateData">Modify State</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const userName = ref("John Doe");
const userAge = ref(28);
const numberArray = [10, 20, 30, 40];
const objectCollection = ref({
platform: "E-commerce",
categories: ["Retail", 5, 15]
});
const updateData = () => {
userName.value = "Elon Musk";
userAge.value = 50;
objectCollection.value.platform = "Digital Marketplace";
objectCollection.value.categories = "Online Shopping";
};
return { userName, userAge, numberArray, objectCollection, updateData };
}
};
</script>
<style scoped></style>
Reactive for Complex Structures
Use reactive() for arrays and objects requiring deep reactivity.
<template>
<div>
<table>
<th>Numeric Array</th>
<td v-for="(val, idx) in numericList" :key="idx">{{ val }}</td>
<th>Object Properties</th>
<h5>Name: {{ userProfile.name }}</h5>
<h5>Age: {{ userProfile.age }}</h5>
<h5>Platform: {{ userProfile.platform }}</h5>
<h5 v-for="(nestedItem, nestedIdx) in userProfile.nested.items" :key="nestedIdx">
{{ nestedItem }}
</h5>
</table>
<button @click="modifyProfile">Update Profile</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
const initialName = "Alice";
const initialAge = 25;
const initialPlatform = "Social Media";
const numericList = reactive([8, 16, 24, 32, 40]);
const userProfile = reactive({
name: initialName,
age: initialAge,
platform: initialPlatform,
nested: {
items: ["first", "second", "third"]
}
});
const modifyProfile = () => {
console.log(userProfile);
userProfile.age = 35;
userProfile.name = "Bob";
};
return { numericList, userProfile, modifyProfile };
}
};
</script>
<style scoped></style>
toRef for Property References
toRef() creates a reference to a specific property of a reactive object.
<template>
<div>
<h2>{{ person.name }}</h2>
<h2>{{ person.age }}</h2>
<button @click="changePerson">Modify Reference</button>
</div>
</template>
<script>
import { toRef } from "vue";
export default {
setup() {
const person = { name: "Richard", age: 45 };
const nameReference = toRef(person, 'name');
const changePerson = () => {
nameReference.value = "Thomas";
console.log(person);
};
return { person, changePerson };
}
};
</script>
<style scoped></style>
toRefs for Object Destructuring
toRefs() converts all properties of a reactive object into individual refs.
<template>
<div>
<h2>{{ displayName }}</h2>
<h2>{{ displayAge }}</h2>
<h5>{{ counterA }}</h5>
<h5>{{ counterB }}</h5>
<h5>{{ character }}</h5>
<h5>{{ sequence[0] }}</h5>
<h5>{{ nestedObject.property }}</h5>
<button @click="inspectObjects">Debug Objects</button>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
setup() {
const baseData = { name: "Michael", age: 38 };
const reactiveBase = reactive(baseData);
const inspectObjects = () => {
console.log(baseData);
console.log(reactiveBase);
};
const extendedData = reactive({
counterA: 5,
counterB: 10,
character: 'x',
sequence: [7, 14, 21],
nestedObject: { property: 12 }
});
return {
...toRefs(extendedData),
...toRefs(reactiveBase),
person: baseData,
inspectObjects
};
}
};
</script>
<style scoped></style>
Computed Properties and Watchers
Computed Properties
Computed properties automatically track dependencies and cache results.
<template>
<div>
<br />
<h1>Counter A: {{ counterA }}</h1>
<button @click="counterA++">Increment A</button>
<br />
<h1>Counter B: {{ counterB }}</h1>
<button @click="counterB++">Increment B</button>
<br />
<h1>Counter C: {{ counterC }}</h1>
<button @click="counterC++">Increment C</button>
<br />
<h1>Counter D: {{ counterD }}</h1>
<button @click="counterD++">Increment D</button>
<br />
<h1>Nested Value: {{ complexObject.section.value }}</h1>
<button @click="complexObject.section.value++">Increment Nested</button>
</div>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
setup() {
const initialValue = 0;
const counterA = ref(initialValue);
const counterB = ref(0);
const counterC = ref(0);
const counterD = ref(0);
watch(counterA, (newValue, oldValue) => {
console.log(`New: ${newValue}, Old: ${oldValue}`);
});
watch([counterB, counterC, counterD], (newValues, oldValues) => {
console.log(`New values: ${newValues}`);
console.log(`Old values: ${oldValues}`);
}, { immediate: true });
const complexObject = reactive({
section: {
value: 8
}
});
watch(() => complexObject.section.value, (newValue, oldValue) => {
console.log(`Nested new: ${newValue}, old: ${oldValue}`);
});
return { counterA, counterB, counterC, counterD, complexObject };
}
};
</script>
<style scoped></style>
Watch Effect
watchEffect() automatically tracks reactive dependencies and runs when they change.
<template>
<div>
<br>
<h5>Primary Counter: {{ primaryCounter }}</h5>
<button @click="primaryCounter++">Increase</button>
</div>
</template>
<script>
import { reactive, ref, watchEffect } from "vue";
export default {
setup() {
const primaryCounter = ref(0);
const documentData = reactive({ title: "Document Title", content: "Main Content" });
const userData = reactive({
identifier: "User123",
metadata: {
category: "Premium",
balance: 150.75
}
});
const stopWatcher = watchEffect(() => {
console.log("Initial execution?");
console.log(`Counter: ${primaryCounter.value}`);
console.log(`User Data: ${userData}`);
console.log(`Document: ${documentData}`);
const trackedValue = primaryCounter;
console.log(trackedValue);
});
stopWatcher();
return { primaryCounter, documentData, userData, stopWatcher };
}
};
</script>
Shallow Reactivity
Shallow APIs provide performance optimization by limiting reactivity depth.
<template>
<div>
<br />
<h5>Deep Ref: {{ deepCounter }}</h5>
<button @click="deepCounter++">Increment Deep</button>
<br />
<h5>Shallow Values: {{ levelOne }} | {{ levelTwo.nestedValue }}</h5>
<button @click="levelOne++">Top Level Increment</button>
<button @click="levelTwo.nestedValue++">Nested Increment</button>
<br />
<h5>Shallow Ref: {{ shallowPrimitive }} | {{ shallowObject.property }}</h5>
<button @click="shallowPrimitive++">Primitive Increment</button>
<button @click="shallowObject.property += 'x'">Object Modification</button>
</div>
</template>
<script>
import { reactive, ref, shallowReactive, shallowRef, toRefs } from "vue";
export default {
setup() {
const deepCounter = ref(0);
const documentMeta = reactive({ heading: "Header", body: "Content" });
const profileData = reactive({
name: "Sarah",
details: {
type: "Professional",
score: 180.50
}
});
const shallowPrimitive = shallowRef(42);
const shallowObject = shallowRef({ property: 42, label: "Item" });
const shallowStructure = shallowReactive({
levelOne: 1,
levelTwo: {
nestedValue: 12
}
});
return {
deepCounter,
documentMeta,
profileData,
shallowPrimitive,
shallowObject,
...toRefs(shallowStructure)
};
}
};
</script>
Component Communication and State Management
Parent-Child Communication
Parenet components can access child methods through template refs.
Parent Component:
<CreateDialog ref="dialogReference"/>
<script setup>
import CreateDialog from './CreateDialog.vue'
import { ref } from 'vue'
const dialogReference = ref(null)
const addRecord = () => {
dialogReference.value.toggleVisibility()
}
</script>
Child Component:
<script setup>
import { ref } from 'vue'
const isVisible = ref(false);
const toggleVisibility = () => {
isVisible.value = !isVisible.value;
}
defineExpose({
toggleVisibility
})
</script>
Vuex Integration
Install required dependency:
npm install vuex@next --save
Import store composition function:
import { useStore } from 'vuex'
Create store module:
import { createStore } from "vuex";
export default createStore({
state: {
userName: "Richard",
userDetails: { fullName: "Richard", description: "Height", years: 28 }
},
mutations: {},
actions: {},
modules: {}
});
Usage in components:
<template>
<div>Vuex Name: {{ computedName }}</div>
<div>Vuex Details: {{ computedDetails.fullName }} || {{ computedDetails.description }}</div>
<div>Destructured Props: {{ fullName }} || {{ description }}</div>
</template>
<script>
import { computed, reactive, toRefs } from "vue";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const computedName = computed(() => {
console.log(store.state.userName);
console.log(store.state.userDetails);
return store.state.userName;
});
const computedDetails = computed(() => {
console.log(store.state.userName);
console.log(store.state.userDetails);
return store.state.userDetails;
});
const destructuredDetails = computed(() => {
console.log(store.state.userName);
console.log(store.state.userDetails);
return reactive(store.state.userDetails);
});
return {
computedName,
computedDetails,
...toRefs(destructuredDetails)
};
}
};
</script>
Asynchronous Vuex Operations
Store configuration with async support:
import { createStore } from "vuex";
export default createStore({
state: {
userName: "Richard",
userDetails: { fullName: "Richard", description: "Height", years: 28 }
},
mutations: {
updateState(state) {
console.log("Mutation triggered by action");
state.userName = "Thomas";
}
},
actions: {
executeUpdate(store) {
console.log("Action method invoked");
store.commit("updateState");
}
},
modules: {}
});
Component implementation:
<template>
<div>Vuex Name: {{ computedName }}</div>
<div><button @click="triggerAsync">Async Operation</button></div>
</template>
<script>
import { computed } from "vue";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const computedName = computed(() => {
console.log(store.state.userName);
console.log(store.state.userDetails);
return store.state.userName;
});
function triggerAsync() {
store.dispatch("executeUpdate");
}
return { computedName, triggerAsync };
}
};
</script>
Synchronous Vuex mutations:
function triggerSync() {
store.commit('updateState', 'Additional Parameter');
}
Lifecycle Hooks
Vue 3 provides composition-based lifecycle hooks for component management.
<template>
<div>Lifecycle demonstration</div>
</template>
<script>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured
} from "vue";
export default {
setup() {
onBeforeMount(() => {
console.log("Before mount - DOM not yet available");
});
onMounted(() => {
console.log("Component mounted - ideal for API calls");
});
onBeforeUpdate(() => {
console.log("Before update - similar to watchers");
});
onUpdated(() => {
console.log("After virtual DOM re-render");
});
onBeforeUnmount(() => {
console.log("Before component destruction");
});
onUnmounted(() => {
console.log("After component cleanup");
});
onErrorCaptured(() => {
console.log("Error captured from child components");
});
return {};
},
created() {
console.log("Created hook - executes immediately");
}
};
</script>
Code Organization and Reusability
Shared Utility Functions
common.js:
import { reactive } from "vue";
const sharedDataFactory = () => {
const dataContainer = reactive({
organization: "Corporation A",
competitor: "Corporation B",
duration: 36
});
return dataContainer;
};
export default sharedDataFactory;
Component usage:
<template>
<div>
Shared data pattern
<h5>{{ externalData.duration }}</h5>
<h5>{{ externalData.competitor }}</h5>
<h5>{{ externalData.organization }}</h5>
<h5>{{ externalData }}</h5>
</div>
</template>
<script>
import { reactive } from "@vue/reactivity";
import sharedDataFactory from "../config/common";
export default {
setup() {
const localData = reactive({
organization: "Corporation A",
competitor: "Corporation B",
duration: 36
});
const externalData = sharedDataFactory();
console.log(externalData);
return { localData, externalData };
}
};
</script>