question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Class style `props` definition

See original GitHub issue

Summary

To be able to define component props with class properties. You can use prop helper to specify detailed prop options:

import { Vue, prop } from 'vue-class-component'

// Define props in a class
class Props {
  count = prop({
    // Same as Vue core's prop option
    type: Number,
    required: true,
    validator: (value) => value >= 0
  })
}

// Pass the Props class to `Vue.with` so that the props are defined in the component
export default class MyComp extends Vue.with(Props) {}

In TypeScript, you can omit prop helper when you only need to define its type (runtime validation does not happen in that case):

import { Vue, prop } from 'vue-class-component'

class Props {
  // optional prop
  foo?: string

  // required prop
  bar!: string

  // optional prop with default
  baz = prop<string>({ default: 'default value' })
}

export default class MyComp extends Vue.with(Props) {}

You need to specify "useDefineForClassFields": true for TypeScript compiler option to let Vue Class Component aware of the properties without initializer (in the above example foo and bar):

{
  "compilerOptions": {
    "useDefineForClassFields": true
  }
}

Motivation

One motivation is to properly type Props type parameter of a component for props type checking in TSX and Vetur. TSX can validate props type on compile type thanks to TypeScript:

import { defineComponent } from 'vue'

// The type Props = { count: number } in component type
const Counter = defineComponent({
  props: {
    count: {
      type: Number,
      required: true
    }
  }
})

<Counter count={'Hello'} /> // Error because `count` is of type `number`

Vetur also offers similar prop type validation on <template> block. To utilize these features, we need to properly type Props type parameter of a component.

The other motivation is less verbosity. Vue’s basic props option requires us to define props with values then infers the prop type from the value. For example, we have to annotate complex type with PropType utility:

interface Person {
  firstName: string
  lastName: string
}

const App = defineComponent({
  props: {
    // Specify value `Object` then annotate it with `PropType<Person>`
    person: Object as PropType<Person>
  }
})

This is relatively verbose compared to the existing @Prop decorator approach from vue-property-decorator.

interface Person {
  firstName: string
  lastName: string
}

@Component
class App extends Vue {
  // Just specify `Person` type (and `@Prop` decorator)
  @Prop person: Person
}

Ideally, the new approach should as short as the decorator approach.

Details

We will introduce two API: Vue.with static method and prop helper function.

Vue.with(...) method receives a class constructor that describes the component props. It collects all class properties and generates props option for the component under the hood. It also respects the property types for the props types:

import { Vue } from 'vue-class-component'

class Props {
  optional?: string
  required!: number
}

class App extends Vue.with(Props) {
  // Vue.with generates the following props option under the hood
  // props: { optional: null, required: null }

  mounted() {
    // It retains the property types for props
    this.optional // string | undefined
    this.required // number
  }
}

Note that we have to specify useDefineForClassFields: true component option in TypeScript to make the above code works.

We can also specify detailed prop options by using prop helper (e.g. default, validator). The prop helper receives exact same as Vue core’s props option object:

class Props {
  // with validator
  count: number = prop({
    validator: (count: number) => count >= 0
  })
  
  // with default
  // You can specify the type via `prop` type parameter
  amount = prop<number>({ default: 1 })
}

Note that we have to specify the type of prop via prop helper type parameter when we use default value. This is to differentiate required prop and with-default prop on the type level. That is, required should be always of type string but withDefault should be of type string in the component while being of type string | undefined when it is used on a parent component since it does not have to receive a value. If the type is able to be inferred from the default value, you don’t have to specify it.

class Props {
  // type is `string`
  required!: string

  // type is `WithDefault<string>`
  withDefault = prop({ default: 'default' })
}

class App extends Vue.with(Props) {
  mounted() {
    this.required // string
    this.withDefault // string
  }
}

// In the usage of TSX/template
// required: string
// withDefault: string | undefined
<App required="Hello" />

Alternative approaches

Decorator approach

There has been an approach with @Prop decorator but there are issues which already described in #447

TL;DR

  • Cannot type the Props type parameter, then there is no way to check props type.
  • There are concerns regarding uncertainties of the spec.

Mixin approach

This is an approach proposed in #447. But it turned out too verbose compared to the decorator approach. There is also a feedback that defining it as a mixin is not intuitive.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:28
  • Comments:29 (7 by maintainers)

github_iconTop GitHub Comments

15reactions
coyotte508commented, Oct 8, 2020

I, for one, am a very happy user of vue-property-decorator, and it works well:

class MyComponent extends Vue {
  @Prop()
  simpleOptionalProp?: string;

  @Prop({type: String})
  propWithTypeValidation!: string

  @Prop({default: 'test'})
  propWithDefaultValue!: string
}

It makes code really readable / simple, with everything in the class definitions and not imported types. I understand the concerns with the uncertainty of the spec but we’d just have to move to the next major version of vue-class-component if the times comes. Wouldn’t already need to do so because Options is a decorator anyway?

I would further add that:

  • The title of this repo is ‘ECMAScript / TypeScript decorator for class-style Vue components.’
  • vue-class-component has 390k weekly downloads on NPM, vue-property-decorator has 358k weekly downloads. Dirty math makes it that 92% of vue-class-component’s users also download vue-property-decorator and most likely use @Prop.
7reactions
rdhainautcommented, Oct 13, 2020

I read the thread, the answers, the motivations of the proposal and I allow myself to react.

First of all, I am grateful for the work and efforts of this library. A big thank-you.

I recognize the potential benefit of this proposal but find that there are a few points which have been overlooked (or have been left out).

  1. First of all from an object-oriented point of view using inheritance is just the wrong way. This is one of the SOLID principles (prefer composition to inheritance). The interface does not meet expectations but is more correct from an object-oriented point of view. This is also a breaking change impacting all the components.

  2. I remind you that multiple inheritance is not possible in JS / TS. I have already developed components that have been able to take advantage of the inheritance. Example: StepOne, StepTwo, StepThree which inherits from StepAbstract (thus sharing properties and methods). It works perfectly in Vue. With your solution how to share methods through these components?

  3. I would also like to know how the $ refs will be managed? For me the problem seems the same but the double inheritance is not possible.

  4. In the arguments, there was a comparison with ReactJS (which uses interfaces to set properties). Another comparison, often forgotten, is Angular which allows to define @Input (equivalent to Prop) and which are type checked in the template by the angular service. So that seems technically possible, doesn’t it?

  5. Defining props inside the class is still more visible and readable. Why ? Because we have an “entity”, an object that defines the behavior of the component in one place. If we do inheritance, it surprises and it makes the reading less “linear” because we have to think of an object tree. It’s just how the cognition charge works.

  6. In the other thread, the decorators were questioned because they are in an uncertain state. This problem has been known from the start and represents a risk. This point should be handled by typescript / Babel and not by libraries. Also, if this is really a problem then you should no longer use a decorator. For me to stop using just one doesn’t really make sense.

For me, there is a huge implication in the new proposal. The pure and simple stop of the decorators. Personnaly, i am a huge fan of decorator because i found that solution elegant and works better with oriented object.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Styling and CSS
CSS classes are generally better for performance than inline styles. What is CSS-in-JS? “CSS-in-JS” refers to a pattern where CSS is composed ......
Read more >
Class Components With Props - WebStorm Guide
In React, components can have properties and state. When they have state (or when they need access to React's lifecycle methods), we use...
Read more >
Vue Class Component: Overview
By defining your component in class-style, you not only change the syntax but also can utilize some ECMAScript language features such as class...
Read more >
styled-components: Basics
styled-components utilises tagged template literals to style your components. It removes the mapping between components and styles. This means that when you're ...
Read more >
In vue.js component, how to use props in css?
You actually can! You should define the CSS variables in a Computed Property, then call the computed property as a style attribute to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found