typescriptvuejs3vue-propsvue-reactivity

In Vue3's compositional API, how can I v-bind a dynamic group of props to a child component and not lose reactivity?


The context is that I have a Button component and a SquareButton component that wraps it as a higher order component. The SquareButton takes the same props as Button, but includes an extra prop needed for a calculation. All the rest of the props should be passed down to the Button. Like so (to simplify):

Button.vue

<script setup lang="ts">
export interface ButtonProps {
  width?: number
  height?: number
  // Plus a bunch more. These all need to make it onto the button.
}

// Already I am losing reactivity by destructuring here.
// However, I need access to `rest`.
const { width, height, ...rest } = withDefaults(defineProps<ButtonProps>(), {
  width: 150,
  height: 75,
})
</script>

<template>
  <button v-bind="rest">
    <slot />
  <button>
</template>

<style scoped>
  button {
    display: flex;
    width: v-bind(width + "px");
    height: v-bind(height + "px");
  } 
</style>

SquareButton.vue

<script setup lang="ts">
import Button from "./Button.vue"

export interface SquareButtonProps extends ButtonProps {
  size?: number
}

const { size, ...rest } = withDefaults(
  defineProps<SquareButtonProps>(),
  {
    size: 30,
  }
)
</script>

<template>
  <Button :width="size" :height="size" v-bind="rest">
    <slot />
  </Button>
</template>

So the first problem is that I don't want to lose reactivity. If one of these props changes, I need everything to update. This means destructuring is bad. Various solutions to this problem recommend using the toRefs function. So let's refactor to use this technique:

SquareButton.vue refactored

<script setup lang="ts">
import Button from "./Button.vue"
import { toRefs } from "vue"

export interface SquareButtonProps extends ButtonProps {
  size?: number
}

const props = withDefaults(
  defineProps<SquareButtonProps>(),
  {
    size: 30,
  }
)

const { size, ...rest } = toRefs(props)
</script>

<template>
  <!-- Error -->
  <Button :width="size" :height="size" v-bind="rest">
    <slot />
  </Button>
</template>

The error I get here occurs because Button is expecting props with normal value types like strings and numbers. But instead, I'm now giving it reactive objects, and it is rightly angry.

So how in the world am I supposed to forward these props down without losing reactivity? The reactivity is important because, when props change, I need my components to update.


Solution

    1. remembering never to destructur the props, you can directly use it in template.
    2. v-bind will automatically add to the root tag of the components,this called Attribute Inheritance ,see the document

    <script setup lang="ts">
    import Button from "./Button.vue"
    export interface SquareButtonProps extends ButtonProps {
      size?: number
    }
    const props = withDefaults(
      defineProps<SquareButtonProps>(),
      {
        size: 30,
      }
    )
    </script>
    <template>
      <Button :width="size" :height="size">
        <slot />
      </Button>
    </template>