Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

takeaway

It has been said that React is suitable for medium and large projects, while Vue is suitable for medium and small projects. However, no one has made it clear why, so THE author directly ignored such comments before. Recently, while researching how to improve project maintainability, I discovered a flaw in Vue’s design that could disrupt one-way data flow. If this is the argument that Vue is not suitable for large projects, it can be attached reluctantly.

To be clear, THE author does not have any positive or negative tendencies, and even favors Vue more emotionally, so we should look at this issue objectively, and the title just wants to attract more eyeballs.

The body of the

Why one-way data flow

This is why state is often called local or encapsulated. It is not accessible to any component other than the one that owns and sets it. This is why state is called local or encapsulated. No component is accessible except the component that owns and sets it.

Please understand the word “access” correctly, actually “modify” is easier to understand. After all, state can be passed to a child component as props, and a child component can use it, but not modify it.

Again, there are many scenario child components that can modify the parent component’s data. The most typical example is the form component of the Input class. Let’s look at the code:

<Input :value="value" @update-value="v => { this.value = v; }" />
Copy the code

Note that Input has the ability to modify the value of the parent component because the parent component sent it an updateValue emit. All it can do is “use” the method. If the parent component does not pass it a similar method, it cannot modify the value.

This ensures that when reading the code you will have some expectation of the behavior of the child component. More specifically, if I pass a value to the child component without passing an updateValue, the child component cannot change the value and debug can ignore the child component. This is called “certainty”.

To impress, let’s look at another example:

<Parent>
  <ChildA :value="value" />
  <ChildB :value="value" />
  <ChildC :value="value" />
  <ChildD :value="value" @update-value="handleUpdateValue" />
</Parent>
Copy the code

Multiple child components use value, and if there is a limit to one-way data flow, we can be sure that only ChildD has the ability to modify value. Without the limitation of one-way data flow, how do we locate which component changed the value? I’m afraid you can only go into the code of each sub-component to see, if it is a complex project, this situation will be “nesting doll”, which is disastrous for project maintenance.

A loophole in the Vue

Vue’s documentation emphasizes the importance of one-way data flow, but leaves a loophole and doesn’t provide a solution.

Note that in JavaScript objects and arrays are passed in by reference, so for a prop of an array or object type, changing the object or array itself in a child component will affect the state of the parent component.

Here’s an example:

// Parent
<script setup lang="ts">
import { ref } from "vue";
import ChildItem from "./ChildItem.vue";
const data = ref({
  root: {
    leaf: 0,}}); </script><template>
  <! Select * from 'datafile'; select * from 'datafile';
  <ChildItem :data="data" />
  <pre>{{ JSON.stringify(data, null, 2) }}</pre>
</template>


// Child
<script setup lang="ts">
import { defineProps } from "vue";
import { set } from "lodash-es";
const props = defineProps<{
  data: Record<"root", Record<"leaf", number>>; } > ();// Subcomponents can modify the props value directly
const handleChangeLvl2 = () = > {
  // props.data.root.leaf = 2; // EsLint will report errors, but it can still be modified successfully
  set(props, "data.root.leaf".2); // EsLint can be modified successfully even if it avoids errors
};
</script>
<template>
  <button @click="handleChangeLvl2">Change leaf value</button>
</template>
Copy the code

As shown in the example above, one-way data flows can be “forcibly” broken, which is the vulnerability in Vue.

How to Avoid bugs

There is no hard and fast way to avoid this vulnerability, only through the tools to try to circumvent it. Such as:

  • Set esLint rules and require all developers to enable them;
  • Further, lint checks in CI/CD and can’t merge code without passing;
  • Be careful with CR

No matter which way to take, all need a certain cost, fortunately, relying on tools can be regarded as “once investment, lifetime benefit” approach. Even so, the problem won’t be completely resolved until Vue’s update mechanism changes, which is almost impossible at this point.

The correct way to do this is to explicitly pass in the emit to modify the data, and clone it to ensure that it is immutable:


      

<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
import { cloneDeep } from "lodash-es";
type Value = Record<"root", Record<"leaf", number>>;
const props = defineProps<{
  modelValue: Value; } > ();const emit = defineEmits<{
  (e: "update:modelValue".data: Value): void; } > ();const handleChangeLvl2 = () = > {
  // Clone first, then modify
  const val = cloneDeep(props.modelValue);
  val.root.leaf++;
  // Assign the value of clone
  emit("update:modelValue", val);
};
</script>
<template>
  <button @click="handleChangeLvl2">Change leaf value</button>
</template>
Copy the code

conclusion

Some people may think it’s a bit of a storm in a teacup. They think it’s convenient to break one-way data flow once in a while, but why not?

Indeed, any discussion that deviates from reality is hooliganism. If your project is small, just a few pages, and then you have to go through all the hard work of CI/CD and popularizing standards, the ROI is too low. However, for medium and large projects, it is necessary to maintain absolute one-way data flow. As mentioned above, in complex projects, a small problem can pop up like a nesting doll, with disastrous consequences.

I’ll end with a quote:

“The levee of thousands of feet, with ants hole to collapse; A hundred feet of room, with a sudden gap of smoke burn.” — Han Feizi Yu Lao