I'm using vuejs with typescript and vue-class-component. I'm trying to create a custom component with reactive data.
Here is my code:
<template>
<div>
<input v-model="data.name" placeholder="Name">
<input v-model="data.value" placeholder="Value">
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
interface Model {
name: string;
value: number;
}
@Component
export default class ClubVue extends Vue {
private data: Model;
public mounted() {
this.data = {...this.$store.getters.data};
}
}
</script>
In this first version, I've got this error :
Property or method "data" is not defined on the instance but referenced during render
That's normal, as said in vue-class-component page, undefined data won't be reflective. I need to initialize data to null. Furthermore, i get this typescript error:
Property 'data' has no initializer and is not definitely assigned in the constructor
So I want to do this:
private data: Model = null;
But I get this typescript error:
Type 'null' is not assignable to type 'Model'.
I don't wan't to change the data type to Model | null
because I would have to check if data is null everywhere I will use it, and I know that data will never be null.
private data!: Model;
Does not work either because data will be undefined and so won't be reactive.
I don't want to turn off typescript checks because they are useful for other parts of code.
Is there a proper way to initialize data here?
A correct type for such property is:
private data: Model | null = null;
It can be used with type guards:
if (this.data) {
console.log(this.data.name); // Model
}
Or non-null assertion operator:
console.log(this.data!.name); // Model
A workaround is to cheat typing system with assertion:
private data: Model = null as unknown as Model;
vue-class-component doesn't take TypeScript into account because undefined
is easier to handle in TypeScript than null
, particularly because this would allow to mark a property as optional.
I know that data will never be null.
It will be null
until the component is mounted, this leaves a room for mistake:
public created() {
console.log(this.data.name); // runtime error but no compilation error
}
public mounted() {
this.data = {...this.$store.getters.data};
}
You can use a getter instead of assigning the data directly, assuming the data is set in the store when it is created. Make sure your store getters are typesafe!
@Component
export default class ClubVue extends Vue {
private _data: Model | undefined;
get data(): Model {
if (!this._data) {
this._data = {...this.$store.getters.data};
}
return this._data;
}
}
This way, data
will never be undefined, since it will either return _data
or set _data
to the current store content and then return that.
This may fail if _data
is a primitive instead of an object, and evaluates to false
(e.g. (Number)0
or (String)""
). In that case, use this._data === undefined
instead of !this._data
.
You can also shorten the getter to
get data():Model {
return this._data = this._data || {...this.$store.getters.data};
}
but this is less clear, especially if the reader is not aware that an assignment will return the value/reference that is being assigned, and even worse to read with primitive types:
return this._data =
this._data === undefined
? {...this.$store.getters.data}
: this._data;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With