Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Vue 3 Composition API and Reactivity Fundamentals

Tech 1

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>

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.