4008063323.net

Enhancing Vue Code: Avoiding Misuse of Watch Functions

Written on

My article is accessible to all; non-members can follow this link to view the complete text.

Foreword

Last Friday evening at 8 PM, I was eagerly anticipating the completion of product acceptance to ensure a smooth launch.

Unexpectedly, the product team approached me, indicating that new features needed to be added, and the colleague responsible for that segment had already left for the day.

Although I felt a rush of frustration internally, I agreed to take on the task without voicing any objections.

This particular section of the business was intricate, and my unfamiliarity with it, combined with hunger, nearly caused me to falter while navigating the code logic.

The Vue file requiring changes contained thousands of lines of code and over ten watch instances tied to ref variables related to business logic iteration.

I spent considerable time deciphering the logic behind these watches before cautiously integrating new business logic into the existing code, fearful of unintentionally disrupting the original logic and facing blame for any bugs.

The Challenges of Overusing Watch

Let’s examine a specific example:

<template>

{{ dataList }}

</template>

<script setup lang="ts">

import { ref, watch } from "vue";

const dataList = ref([]);

const props = defineProps(["disableList", "type", "id"]);

watch(

() => props.disableList,

() => {

// The logic based on disableList is very complex, and it computes a new list synchronously

const newList = getListFromDisabledList(dataList.value);

dataList.value = newList;

},

{ deep: true }

);

watch(

() => props.type,

() => {

// The logic based on type is very complex and computes a new list synchronously

const newList = getListFromType(dataList.value);

dataList.value = newList;

}

);

watch(

() => props.id,

() => {

// Fetch dataList from the server

fetchDataList();

},

{ immediate: true }

);

</script>

In this scenario, dataList is displayed in the template. When props.id is modified and at initialization, dataList is fetched asynchronously from the server.

Updates to props.disableList and props.type result in synchronous recalculations of dataList.

At first glance, the code appears acceptable, but complications emerge when a new colleague unfamiliar with this segment assumes responsibility.

Typically, when inheriting an unfamiliar business area, we require a point of reference.

In frontend development, this reference is usually the rendered page in the browser.

In Vue, since the page is generated from templates, identifying the reactive variables used within the template and their sources can clarify the business logic.

For instance, tracing the origins of the dataList variable usually sheds light on the business logic.

In this example, dataList is sourced from various places. Initially, it is updated asynchronously from the server via a watch on props.id. Subsequently, it is synchronously modified through watches on both props.disableList and props.type.

At this stage, a colleague unfamiliar with the business who receives a request to revise the logic for dataList must first understand the logic behind its multiple sources.

After grasping the logic, they must analyze which watch requires modification to align with the product specifications.

However, in practice, when managing someone else's code (especially complex ones), we tend to avoid altering existing code and instead add our own on top.

Modifying intricate legacy code risks introducing bugs for which we might be held responsible. Consequently, our common approach is to add another watch to implement the latest business logic for dataList:

watch(

() => props.xxx,

() => {

// Add the latest business logic

const newList = getListFromXxx(dataList.value);

dataList.value = newList;

}

);

After several iterations, this Vue file can become overcrowded with numerous watch statements, resulting in "spaghetti code."

There might be cases where this coding style is deliberate, as it secures one's position within the team, making others hesitant to modify such a convoluted section of code.

Using Computed to Address the Issue

Recognizing the shortcomings of the previous example, what should maintainable code resemble? In my perspective, it should look like this:

dataList is displayed in the template, then updated synchronously, followed by an asynchronous fetch from the server.

This process can be visualized as a single thread. When a new developer joins the team to iterate on dataList-related business, they only need to ascertain whether the latest product requirements necessitate modifying the code during the synchronous or asynchronous phase, and then add the new code accordingly.

Let’s explore how to optimize the previous example for enhanced maintainability. The source of dataList can primarily be categorized into synchronous and asynchronous origins.

The asynchronous source cannot be modified, as business dictates that after props.id is updated, the latest dataList must be retrieved from the server.

We can consolidate all synchronous source code into computed. The optimized code is as follows:

<template>

{{ renderDataList }}

</template>

<script setup lang="ts">

import { ref, computed, watch } from "vue";

const props = defineProps(["disableList", "type", "id"]);

const dataList = ref([]);

const renderDataList = computed(() => {

// Calculate the list based on disableList

const newDataList = getListFromDisabledList(dataList.value);

// Calculate the list based on type

return getListFromType(newDataList);

});

watch(

() => props.id,

() => {

// Fetch dataList from the server

fetchDataList();

},

{

immediate: true,

}

);

</script>

In the template, we now render renderDataList instead of dataList. renderDataList is a computed property that incorporates all synchronous logic tied to dataList. The flowchart for the code logic is as follows:

When a new team member is tasked with iterating on dataList-related business, they can quickly clarify the business logic due to the linear sequence of the entire structure.

They can then decide, based on the product specifications, whether to adjust the synchronous or asynchronous logic. Below is a demonstration of modifying the synchronous logic:

const renderDataList = computed(() => {

// Add the latest business logic from the product

const xxxList = getListFromXxx(dataList.value);

// Calculate the list based on disableList

const newDataList = getListFromDisabledList(xxxList);

// Calculate the list based on type

return getListFromType(newDataList);

});

Conclusion

This article outlines two primary scenarios for using watch: one for when the watched value changes and requires synchronous updates to dataList, and the other for when it changes and necessitates asynchronous fetching of dataList from the server.

If both types of updates are carelessly mixed within the watch function, the subsequent maintainers will struggle to untangle the dataList-related logic.

This confusion arises because the watch function is altering the dataList values throughout, making it unclear where to implement new business logic.

In such cases, we typically add a new watch to encapsulate the latest business logic.

Over time, if the code accumulates numerous watch instances, maintainability declines.

Our optimization strategy is to consolidate all watch code responsible for synchronously updating dataList into a computed property named renderDataList.

Future maintainers need only determine if a new business requirement entails a synchronous update to dataList, in which case they should implement the new logic within computed.

If it involves an asynchronous update to dataList, the new business logic should be placed in watch.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Innovative Advances in Nuclear Fusion: A Path to Clean Energy

Explore the groundbreaking techniques in nuclear fusion and their potential to revolutionize clean energy production.

Uncovering the Surprising Discoveries in a Vintage Fridge

A humorous tale of archaeological discoveries made in an old refrigerator, revealing unexpected relics from history.

Harnessing Ketosis: Transforming Emotions and Mental Health

Discover how ketosis from fasting can enhance emotional stability and mental well-being through its transformative effects.

Unlocking the Secrets of Your Soil: My Soil Test Kit Review

Discover how the My Soil test kit can enhance your gardening experience by providing detailed soil analysis for optimal growth.

The Harsh Reality I Faced After a Decade in Teaching

A reflective piece on the challenges faced after 10 years of teaching, highlighting the journey and personal revelations.

Super New Moon in Pisces: Embracing New Beginnings and Dreams

Explore the transformative energy of the Super New Moon in Pisces, encouraging new beginnings, intuition, and practical steps toward your dreams.

Unlocking Profitable Strategies: 10 Ways to Earn Money Online

Discover ten effective methods for generating income online, based on personal experiences and real results.

Title: Navigating Anxiety: Concerns About Our Future

Exploring the complexities of anxiety and the impact of societal changes on our future outlook.