I"m porting my new app from vue2 to vue3. Since mixins are not recommended way of reusing code in vue3, Im trying to convert them into composable.
I've 2 mixins (methods) __(key) and __n(key, number) in mixins/translate.js which will translate any word into the app's locale.
module.exports = {
methods: {
/**
* Translate the given key.
*/
__(key, replace = {}) {
// logic
return translation
}
Now this is how I converted it as
Composables/translate.js
export function __(key, replace = {}) {
// logic
return translation
}
and since I need these functions to be accessbile in every component without explicitly importing. I'm importing it in app.js
import {__, __n} from '#/Composables/translate.js';
Questions
__() is not accessible in every component. How to make this function accessible in every component without explicit import
Is this the right of doing things?
These functions are required essentially in every component, declaring them in every component is impractical.
#1, You can put it into the globalProperties object
import {__, __n} from '#/Composables/translate.js';
const app = createApp(AppComponent);
app.config.globalProperties.__ = __;
app.config.globalProperties.__n = __n;
#2, though opinion based, importing for every component that needs it would be my preferred way.
Related
I am trying to migrate a mixin in Vue 2 to composable in vue 3. This mixin was created using class component syntax and all the form UI components (around 30 ) use this mixin. We defined all the common props in this mixin and as a result of that, all those common props were available for all the components that are using the mixin. Can we declare props within composable as well? If not, is there any other way to share reusable props from one commonplace? So that later if we need to change any one prop, we can change from one place instead of making the same change in every component that is using the prop.
import { Prop } from "vue-property-decorator";
import { Inject } from "inversify-props";
// 1: Can we use generic type in composable?
abstract class BaseForm<T> extends Vue{
// 1: This is a common property that all the form components will use
// Also some of the methods defined in this class would use this property
protected item: T = {} as T;
// 2: Common props for all the forms are defined
// Can we declare props composable as well?
// If not, is there any other way to enforce that all my components that are created using composition API
// get this prop from a commonplace?
#prop([required: true, type: Boolean])
protected edit!: boolean;
....
}
I have a pinia store created with setup syntax like:
defineStore('id', () => {
const counter = ref(0)
return { counter }
})
Everything has been working great with setup syntax because I can re-use other pinia stores.
Now, however, I see the need to re-use Pinia stores on other pages but their state needs to be reset.
In Vuex for example, I was using registerModule and unregisterModule to achieve having a fresh store.
So the question is: How to reset the pinia store with setup syntax?
Note: The $reset() method is only implemented for stores defined with the object syntax, so that is not an option.
Note 2: I know that I can do it manually by creating a function where you set all the state values to their initial ones
Note 3: I found $dispose but it doesn't work. If $dispose is the answer, then how it works resetting the store between 2 components?
You can use a Pinia plugin that adds a $reset() function to all stores:
On the Pinia instance, call use() with a function that receives a store property. This function is a Pinia plugin.
Deep-copy store.$state as the initial state. A utility like lodash.clonedeep is recommended for state that includes nested properties or complex data types, such as Set.
Define store.$reset() as a function that calls store.$patch() with a deep-clone of the initial state from above. It's important to deep-clone the state again in order to remove references to the copy itself.
// store.js
import { createPinia } from 'pinia'
import cloneDeep from 'lodash.clonedeep'
const store = createPinia()
1️⃣
store.use(({ store }) => {
2️⃣
const initialState = cloneDeep(store.$state)
3️⃣
store.$reset = () => store.$patch(cloneDeep(initialState))
})
demo
Feel free to read the article based on this answer: How to reset stores created with function/setup syntax
You can do this as suggested in the documentation here
myStore.$dispose()
const pinia = usePinia()
delete pinia.state.value[myStore.$id]
I have just started my first project with Vue.js, I have managed to do a lot of basic things and now I am trying to structure the project. I want to achieve the highest possible code reuse. One of the most frequent cases of my application is going to be showing messages of different types, confirmation, information, etc. For this reason, I want to create a mechanism that allows me to launch these messages globally, regardless of where I call them.
As far as I have been able to advance, I have opted for the following variant:
1- I have created a directory called classes in my src directory.
2- I have created a file called MessageBox.js inside classes directory with the following content:
import Vue from 'vue';
export default class MessageBox extends Vue {
confirm() {
return alert('Confirm');
}
information() {
return alert('Information');
}
}
I define it like this because I want to call these methods globally as follows:
MessageBox.confirm();
I am really new to Vue.js and I was wondering if there is any other way to achieve the results I am looking for in a more efficient way .... or .. maybe more elegant?
Thank you very much in advance..
There are at least 2 ways of going about this:
Event bus
Rely on Vue.js internals to create a simple EventBus. This is a design pattern used in Vue.js.
Create a file and add the following lines to it
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
Create your component that takes care of displaying global dialogs. This is usually registered at the top of the tree, so it can cover the entire real estate.
Import the event bus import EventBus from 'event_bus' and then register for the new events
EventBus.$on('SHOW_CONFIRM', (data) => {
// business logic regarding confirm dialog
})
Now you can import it in any component that wants to fire an event like so
EventBus.$emit('SHOW_CONFIRM', confirmData);
Vuex
You can also use vuex to store global data regarding dialogs and add mutations to trigger the display of the dialogs.
Again, you should define a component that takes care of displaying and push it towards the top of the visual tree.
Note: in both cases you should handle cases in which multiple dialog need to be shown at the same time. Usually using a queue inside the displaying component works.
It's an antipattern in modern JavaScript to merge helper functions that don't rely on class instance into a class. Modules play the role of namespaces.
Helper functions can be defined as is:
messageBox.js
export function confirm() {
return alert('Confirm');
}
They can be imported and used in component methods. In case they need to be used in templates, they can be assigned to methods where needed one by one:
Some.vue
import { confirm } from './util/messageBox';
export default {
methods: { confirm }
}
Or all at once:
import * as messageBox from './util/messageBox';
export default {
methods: { ...messageBox }
}
Helpers can be also be made reusable as Vue mixins:
messageBox.js
...
export const confirmMixin = {
methods: { confirm };
}
export default {
methods: { confirm, information };
}
And used either per component:
Some.vue
import { confirmMixin } from './util/messageBox';
export default {
mixins: [confirmMixin]
}
Or globally (isn't recommended because this introduces same maintenance problems as the use of global variables):
import messageBoxMixin from './util/messageBox';
Vue.mixin(messageBoxMixin);
Following this tutorial, I'm trying to programmatically create instances of a component on my page.
The main snippet is this:
import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)
However I get two errors:
The component I'm trying to instantiate contains references to the store, and these don't work: "TypeError: Cannot read property 'state' of undefined".
For the last line of the snippet (this.$refs.container.appendChild(instance.$el)) I get this error: "Uncaught TypeError: Cannot read property 'container' of undefined"
I'm really not sure how to troubleshoot this, if anyone strong in Vue.js could give me some hint as to why I'm getting these errors and to solve them that would be terrific.
1) Since you're manually instantiating that component and it doesn't belong to your main app's component tree, the store won't be automatically injected into it from your root component. You'll have to manually provide the store to the constructor when you instantiate the component ..
import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";
let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass({ store });
2) In a Vue Single File Component (SFC), outside of the default export this doesn't refer to the Vue instance, so you don't have access to $refs or any other Vue instance property/method. To gain access to the Vue instance you'll need to move this line this.$refs.container.appendChild(instance.$el) somewhere inside the default export, for example in the mounted hook or inside one of your methods.
See this CodeSandbox for an example of how you may go about this.
This is another way to instantiate a component in Vue.js, you can use two different root elements.
// Instantiate you main app
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
//
// Then instantiate your component dynamically
//
// Create a component or import it.
const Hello = {
props: ['text'],
template: '<div class="hello">{{ text }}</div>',
};
// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);
// Use componentClass to instantiate your component.
const vm = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
// then mount it to an element.
.$mount('#mount');
It works by assigning "this" to the property "parent". By setting the parent you also have access to the $store in the new instance. (Provided that "this" is another Vue instance/Component and already has access to the store, of course)
new (Vue.extend(YourNewComponent))({
parent: this,
propsData: {
whatever: 'some value',
},
}).$mount(el.querySelector('.some-id'))
If you don't need the reference to the parent, you can just leave "parent: this," out.
Important note: When mounting many (like 500+) items on the page this way you will get a huge performance hit. It is better to only give the new Component the necessary stuff via props instead of giving it the entire "this" object.
I went down this path, following all the examples above, and even this one: https://css-tricks.com/creating-vue-js-component-instances-programmatically/
While I got far, and it works (I made a lot of components this way), at least for my case, it came with drawbacks. For example I'm using Vuetify at the same time, and the dynamically added components didn't belong to the outer form, which meant that while local (per component) validation worked, the form didn't receive the overall status. Another thing that did not work was to disable the form. With more work, passing the form as parent property, some of that got working, but what about removing components. That didn't go well. While they were invisible, they were not really removed (memory leak).
So I changed to use render functions. It is actually much easier, well documented (both Vue 2 and Vue 3), and everything just works. I also had good help from this project: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
Basically, to add a function dynamically, just implement the render() function instead of using a template. Works a bit like React. You can implement any logic in here to choose the tag, the options, everything. Just return that, and Vue will build the shadow-DOM and keep the real DOM up to date.
The methods in here seems to manipulate the DOM directly, which I'm glad I no longer have to do.
I'm trying to use Vue for a little project.
I started with only client code. So when I did
const mv = new Vue({...});
I was able to access to the component from outside with a mv.
Now I'm using Vue.cli, so I define my component inside a mv.vue and then I have
export default {
data () {}
}
Here, how can I get the nme of my component?
Thanks for any reply or hint :)
You can get the name of the component , you can do this
this.$vnode.tag
If you want the parent component's name from its child do this
this.$parent.$vnode.tag
You can name your component like so:
export default {
name: 'mv',
data () {
return {}
}
}
But to access it you'd need to use the name that you import it with. For example:
import theVariableIWantItToBe from './mv.vue'
console.log(theVariableIWantItToBe)
To expand on the very good #vamsi-krishna answer, and update it, I've discovered that Vue now often puts a prefix on the $vnode.tag along the lines of vue-component-3-YourComponentName.
You can fix this by using the following code. And perhaps, just in case of a missing tag, fall back to the ID of the root element in the component.
Occasionally, Vue doesn't pass back a component at all in its errorHandler and warnHandler global events. So I've handled that scenario first.
if (!vm){
return '[unknown]'
}
if (vm.$vnode.tag) {
const res = vm.$vnode.tag
return res.replace(/vue-component-\d+-/i, '')
}
if (vm.$el.id) {
return vm.$el.id
}