Home > Enterprise >  Vue3 transform props structure maintaining reactivity
Vue3 transform props structure maintaining reactivity

Time:04-05

I have a component that gets data from the API and passes it to another component that displays it (passing down again to multiple child components). So I tried to split that information into multiple ref objects for passing down to each child component.

The problem is, doing that I loose reactivity... the new ref object does not update when parent component changes their data.

I created an example to show the problem.

ParentComponent

<template>
  <p>All data: {{ data }}</p>
  <child-component :data="data"></child-component>
</template>
<script setup>
import { ref } from "vue";
import ChildComponent from "./ChildComponent.vue";

let data = ref({
  a: {},
  b: {},
  c: {}, // More properties...
});

setTimeout(() => { // FAKE API
  data.value.a = {
    name: "prop A",
    description: "aaa"
  };
  data.value.b = {
    name: "prop B",
    description: "bbb"
  };
  data.value.c = {
    name: "prop C",
    description: "ccc"
  };
  // More properties updated...
}, 2000);
</script>

ChildComponent

<template>
  <p>child component props: {{data}}</p>
  <p>child component manipulated: {{manipulated}}</p>
</template>

<script setup>
import { ref } from "vue";

const props = defineProps(['data'])

let manipulated = ref({ // Only need a and b properties.
  a: props.data.a,
  b: props.data.b
})

</script>

Output when mount components

All data: { "a": {}, "b": {}, "c": {} }

child component props: { "a": {}, "b": {}, "c": {} }

child component manipulated: { "a": {}, "b": {} }

Output after updated parent component data:

All data: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }

child component props: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }

child component manipulated: { "a": {}, "b": {} }

Desired result after updated parent component data:

All data: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }

child component props: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }

child component manipulated: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" } }

How can achieve that? With primitives works... but with objects there is missing something to maintain the reactivity...

CodePudding user response:

Option 1: Use deep watcher

You could use a deep watcher (i.e., watch with the deep flag) on props.data that updates manipulated with the new values of props.data:

// ChildComponent.vue
import { watch } from 'vue'
⋮
watch(
  () => props.data,
  newValue => {
    const { a, b } = newValue
    manipulated.value = { a, b }
  },
  { deep: true }
)

demo 1

Option 2: Use computed

If manipulated were just a read-only prop, you could switch from the ref and watch to a computed instead:

import { computed } from 'vue'

const props = defineProps(['data'])

let manipulated = computed(() => ({
  a: props.data.a,
  b: props.data.b,
}))

demo 2

  • Related