Why do I need to declare props twice in vue-class-component? - vue.js

When working with vue-class-component, I need to declare my props both in the #Component decorator AND in the class itself. It seems rather redundant and error prone.
Is this intended or am I missing something here?
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
// Here we declare that score and counter are props
#Component({
props: {
score:Number,
counter:Number
}
})
// Now I have to declare score and counter again ?
// Adding to the confusion is the fact that Typescript types are
// written differently from Vue types (number vs Number)
export default class ScoreBar extends Vue {
score:number
counter:number
created(){
console.log(`score prop is ${this.score} counter is ${this.counter}`)
}
}
</script>

Related

How to make provide/inject reactive in Vue 3 to avoid props drilling?

I have a root component that has a lot of descendants. In order to avoid props drilling, I want to use provide/inject.
In the root component in the setup function, I use provide.
In the child component in the setup function, I get the value via inject.
Then the child component might emit an event, that forces the root component to reload data that it provides to the child components.
However, the data in the child component is not changed.
Previous answers that I found usually were related to Vue 2, and I'm struggling with Vue 3 composition API.
I tried to use watch/watchEffect, and "re-provide" the data, but it didn't work (and I'm not sure if it's a good solution).
Sample code: https://codesandbox.io/s/mystifying-diffie-e3eqyq
I don't want to be that guy, but read the docs!
Anyway:
App.vue
setup() {
let randomNumber = ref(Math.random());
function updateRandomNumber() {
randomNumber.value = Math.random()
}
// This should be an AJAX call to re-configurate all the children
// components. All of them needs some kind of config.
// How to "re-provide" the data after a child component asked this?
provide("randomNumber", {
randomNumber,
updateRandomNumber
});
},
ChildComponent.vue
<template>
<div>Child component</div>
<button #click="updateRandomNumber">Ask root component for re-init</button>
<div>Injected data: {{ randomNumber }}</div>
</template>
<script>
import { inject } from "vue";
export default {
setup() {
// How to "re-inject" the data from parent?
const {randomNumber, updateRandomNumber} = inject("randomNumber");
return {
randomNumber,
updateRandomNumber
};
},
};
</script>

Find nearest parent Vue component of template ref (Vue 3)

When a Vue template ref is mounted, I want to get the nearest parent Vue component. This should be generic and work for any template ref so I've put it in a composition function (but that's just an implementation detail).
I had this working but my implementation used elem.__vueParentComponent while iteratively searching an element's ancestors. While reading the Vue source code I saw __vueParentComponent was only enabled for dev mode or if dev tools is enabled in production. Thus, I don't want to rely on that flag being enabled.
I thought this might be possible using vnodes but this isn't easily google-able. Here's an example of what I'm trying to do:
function useNearestParentInstance(templateRef) {
function getNearestParentInstance(el) {
// code here
}
onMounted(() => {
const el = templateRef.value;
const instance = getNearestParentInstance(el);
// do something with instance
});
}
<template>
<div>
<SomeComponent>
<div>
<div ref="myElem"></div>
</div>
</SomeComponent>
</div>
</template>
<script>
export default {
setup() {
const myElem = ref();
// nearest would be SomeComponent's instance in this case
useNearestParentInstance(myElem);
...
}
}
</script>
If you want the nearest vue parent you can simply use
ref().$parent // Not sure if syntax is same in vue3
ref().$parent will get the first vuecomponent that is the parent of the ref that you placed.

Vue 3 use dynamic component with dynamic imports

I use Vue 3 and I have a dynamic component. It takes a prop called componentName so I can send any component to it. It works, kind of.
Part of the template
<component :is="componentName" />
The problem is that I still need to import all the possible components. If I send About as a componentName I need to import About.vue.
Part of the script
I import all the possible components that can be added into componentName. With 30 possible components, it will be a long list.
import About "#/components/About.vue";
import Projects from "#/components/Projects.vue";
Question
It there a way to dynamically import the component used?
I already faced the same situation in my template when I tried to make a demo of my icons which are more than 1k icon components so I used something like this :
import {defineAsyncComponent,defineComponent} from "vue";
const requireContext = require.context(
"#/components", //path to components folder which are resolved automatically
true,
/\.vue$/i,
"sync"
);
let componentNames= requireContext
.keys()
.map((file) => file.replace(/(^.\/)|(\.vue$)/g, ""));
let components= {};
componentNames.forEach((component) => { //component represents the component name
components[component] = defineAsyncComponent(() => //import each component dynamically
import("#/components/components/" + component + ".vue")
);
});
export default defineComponent({
name: "App",
data() {
return {
componentNames,// you need this if you want to loop through the component names in template
};
},
components,//ES6 shorthand of components:components or components:{...components }
});
learn more about require.context

Sharing variable across all vue component instances

I am trying to refactor some code and move some code to the mixin. But I am getting different behaviour. I wanted an object to be shared across all instances of a component. So I had written something like below
<script>
export default {
registryManager: new RegistryManager()
}
</script>
In the instances, I was accessing this by this.$options.registeryManager.
Now I have moved this code to a mixin.
Mixin
export default {
registryManager: new RegistryManager()
}
Component
<script>
import registryManager from './mixins/registryManager';
export default {
mixins: [registryManager]
}
</script>
Let say you are having 2 components A, B. Earlier, All the instances of component A had one registryManager and All the instances of component B had one separate registryManger.
With the use of mixin, All the instances of component A and component B are sharing one registry manager because regardless of how many components are using a mixin, only one instance of the mixin is created
Is there any way to create one instance of the mixin per component to get earlier behaviour?
This is the expected behaviour. There is only one class instance, new RegistryManager() is evaluated only once in mixin module, Vue is unaware that registryManager needs to be instantiated multiple times.
Since there is a one-to-one relationship between component constructor and class instance, it can be assigned as constructor property.
This can be done globally:
Object.defineProperty(Vue.prototype, 'registryManager', {
get() {
if (!this.constructor._registryManager) {
this.constructor._registryManager = new RegistryManager();
}
return this.constructor._registryManager;
}
});
The class is also lazily instantiated this way. Can be placed inside install(Vue) {...} and used as a plugin.
Or locally as a mixin:
beforeCreate() {
if (!this.constructor._registryManager) {
this.constructor._registryManager = new RegistryManager();
}
this.registryManager = this.constructor._registryManager;
}
You could create a factory method inside the mixin that expects an id (name of the component for instance) and returns an instance of a RegisterManager.
The mixin module will have to keep a map from ids to instances of RegisterManager.
Something similar to this.
const instances = {
}
export default {
getRegistryManager: (id) => {
if (!instances[id]) {
instances[id] = new RegistryManager()
}
return instances[id]
}
}
And then call getRegistryManager from your components.
<script>
import getRegistryManager from './mixins/registryManager';
export default {
mixins: [getRegistryManager("ComponentName")]
}
</script>

vue-class-component syntax with typescript

The example on https://github.com/vuejs/vue-class-component (for the component syntax that's new in vuejs3) but I'm using straight type script instead of babel. I therefore tried:
<script lang="ts">
import Vue from "vue"
import Component from "vue-class-component"
#Component({
props: {
propMessage: String
}
})
export default class MyComponent extends Vue {
helloMsg = "Hello, " + this.propMessage
}
</script>
Unfortunately, this.propMessage can't be found. How do I have to change the syntax to make it found?
I would guess this.props.propMessage would work (but i can't test it right now) and you may have already tried.
I recommend you using #Prop decorator from vue-property-decorator which provide a clearer syntax on the long-end.
It would give something like :
#Component
export default class MyComponent extends Vue {
#Prop() propMessage !: string;
helloMsg = "Hello, " + this.propMessage;
}
Good luck