Vue 3 composition API and access to Vue instance - javascript

In main.js I have something like this:
import { myUtilFunc} from './helpers';
Object.defineProperty(Vue.prototype, '$myUtilFunc', { value: myUtilFunc });
In this way I have acess to myUtilFunc across whole application with this.$myUtilFunc
But how can I achieve the same in setup() method in Vue 3 if I don't have access to this?

Use provide/inject
Provide
const app = createApp(App);
app.provide('someVarName', someVar); // `Provide` a variable to all components here
Inject:
// In *any* component
const { inject } = Vue;
...
setup() {
const someVar = inject('someVarName'); // injecting variable in setup
}
Note that you don't have to provide from the app root, but can also provide from any component to only its sub-components:
// In *any* component
setup() {
...
},
provide() {
return {
someVarName: someVar
}
}
Original answer
[Edit: While my original answer below is still useful for context properties, it's no longer recommended to use context.root, which is no longer mentioned in the guide and may soon be deprecated.]
In Vue 3, setup has an optional second argument for context. You can access the Vue instance through context.root instead of this:
setup(props, context) {
context.root.$myUtilFunc // same as `this.$myUtilFunc` in Vue 2
}
Things you can access through context:
context.attrs
context.slots
context.parent
context.root
context.emit

While Dan's answer is correct, I would like to provide an alternative mentioned in the comments to the accepted answer. There are pros and cons to each, so, you need to choose based on your needs.
To understand why the code below works, it is important to remember, that provided properties are transitive in the tree of components. I.e. inject('foo') will look for 'foo' in every parent going up the hierarchy all the way to the app; there is no need to declare anything in the middle wrappers.
So, we can write something like this, where globalDateFormatter() is just an example function we want to use in any component down the tree:
main.js
import { createApp } from 'vue'
import App from './App.vue'
const globalDateFormatter = (date) => {
return '[' + date.toLocaleString() + ']'
}
const app = createApp(App)
app.provide('globalDateFormatter', globalDateFormatter) // <-- define here
app.mount('#app')
And then, in some DeepDownComponent.vue:
<template>
<p> {{ fmt(new Date()) }} </p>
</template>
<script>
import { inject } from 'vue'
export default {
setup(){
const fmt = inject('globalDateFormatter', x => x.toString())
// ^-- use here, optional 2nd parameter is the default
return {fmt}
}
}
</script>
Obviously, you can directly import and use provide and inject with the following signatures
provide<T>(key: InjectionKey<T> | string, value: T): void
and
inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
anywhere in your code, doesn't have to be app.provide()
You can also provide values, even the global store, like this, just don't forget to use ref() or reactive() as needed.
In short, whenever you would prefer dependency injection, provide/inject are your friends.

Related

Would I lose any benefits if I switch from Vuex to custom Javascript store?

I am making a game editor. All it needs is a way to save and read data from a common store, such as sprites and tool settings.
The problem is Vuex just seems really messy to me. Maybe it's because I'm not building a standard SPA which Vuex was designed for, but it just seems that every time I want to do something simple it adds 50+ lines of code in getters, actions, and mutations that would otherwise be unnecessary. On top of that, it has limitations such as not being able to modify states from getters which would be really helpful when generating unique asset IDs. I also have no need for the dynamic loading/unloading of modules.
So my question, if I replaced Vuex with an imported object like the following:
class MyStore_Class{
constructor(){
this.val = 0;
}
//other methods and stuff to manipulate data
}
let MyStore = new MyStore();
export default MyStore;
Then imported this MyStore object into the components where I needed it, would I lose anything?
I ran some simple tests and it seems like it works perfectly as a drop in replacement for Vuex, but I'm afraid there might be some kind of downside that I would notice only later down the line.
EXIT: Pretty much all data for the app is local, so the separation of actions/mutations tends to mean that the only action code I am writing is commit('doMutation', newData) over and over again
Your solution could be a vue observable, really easy to do and lightweight in term of architecture ;)
Create a store.js file in your src/root folder
Create the state value/s you wish to have globally
Create the methods you needs for his interaction
Set it up in your components and there you go
Setup store.js
import Vue from "vue";
const state = Vue.observable({ val: 0 });
export const increment = () => state.counter++;
export const decrement = () => state.counter--;
export default state;
In your component
<template>
<div>
<p>The value is {{val}}</p>
<button #click="inc">+</button>
<button #click="dec">-</button>
</div>
</template>
<script>
import store, { increment, decrement } from "./store";
export default {
computed: {
// your getter in some way
counter() {
return store.counter;
}
},
methods: {
inc() {
increment();
},
dec() {
decrement();
}
}
};
</script>
I took these examples on this article, where you could read more about vue observable if you want, but i use it a lot on small projects where i need just few values accessible globally and that doesn't require a vuex architecture.
https://medium.com/better-programming/how-to-manage-vues-state-with-vue-observable-25988a88938b

Vue Composition API: Is there a better way in calling `emit` inside consumable file?

What is the correct way (or a better way) to access emit in the separated logic file?
This is what I did currently that works:
foo.js
export default (emit) => {
const foo = () => { emit('bar') };
return { foo };
}
Then on the consuming component:
import { defineComponent } from '#vue/composition-api';
import foo from './foo';
export default defineComponent({
setup(props, { emit }) {
const { foo } = foo(emit);
return { foo };
}
});
But I wonder if there is a more appropriate way on doing this? Or is it a bad practice to call emit inside a consumable file?
You probably have found the solution, but in case you would try similar way (as originally asked in question), there's this option called getCurrentInstance that has an emitter (the Vue 2 plugin for Composition API has one too).
import { getCurrentInstance } from 'vue';
export default () => {
const { emit } = getCurrentInstance();
const foo = () => {
emit('bar');
};
return { foo };
}
But bear in mind that this will only work for calling functions/components that have the SetupContext.
Edit
The above solution will work for Vue 3, yet with earlier version of Vue + the Composition API plugin, there is but a slight difference: As with the rest of the Instance Properties, you'll have to prefix it with $ to become $emit. (The following example now assumes Typescript as the target language, as mentioned on the comment).
import { getCurrentInstance } from '#vue/composition-api';
export default () => {
// Ugly workaround, since the plugin did not have the `ComponentInstance` type exported.
// You could use `any` here, but that would defeat the purpose of having type-safety, won't it?
// And we're marking it as `NonNullable` as the consuming components
// will most likely be (unless desired otherwise).
const { $emit, ...context } = getCurrentInstance() as NonNullable<ReturnType<typeof getCurrentInstance>>;
const foo = () => {
$emit.call(context, 'bar');
};
return { foo };
}
For Vue 3's Composition API, however, they do have this ComponentInternalInstance interface exported.
P.S. It's probably best to stick to the old-school way of assigning the instance to a variable and do context.$emit or vm.$emit rather than having to explicitly specify a context for everything. I initially came up with this idea without realizing that those Instance Properties are probably meant for internal uses, which is not exactly the case with the next Composition API.

Updating my imported variable's value in React.js

I am refractoring an app I've build using React.js. I am exporting a variable from the global scope of Spotify.js and importing it in two other files App.js and Button.js.
After calling a function from Spotify.js that sotres a new value to the variable, It's new value is exported to 'Button.js' but stays an empty string in 'App.js'.
Your help would be appriciated :)
export let userAccessToken = '';
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
import {userAccessToken, Spotify} from '../../util/Spotify';
export class App extends React.Component {
//a conditional rendering happens depending on if(!userAccessToken)
}
import {userAccessToken, Spotify} from '../../util/Spotify'
export class Button extends React.Component {
componentDidMount() {
if (!userAccessToken) {
console.log(`Button's UAT before calling the fn: ${userAccessToken}`)
Spotify.getUserAccessToken();
console.log(`Button's UAT after calling the fn: ${userAccessToken}`);
}
}
...
}
This is not how you share data between react components.
Use react context or pass props between components
You could use react context to share data or simply pass it as props (if the components are closely related in the component tree)
The only thing I can recommend is to export the userAccessToken, something like this, but you can't change its value outside the module
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
...
}
const accessToken = Spotify.getUserAccessToken();
export const userAccessToken = accessToken;
If you got to read this question I solved it.
Turns out I should have called my method Spotify.getUserAccessToken() from App.js using the react lifecycle method componentDidMount().
The export and import methods are like snapshots of the module and therefore when I exported the variable userAccessToke from Spotify.js before calling the my method I imported an empty string and it did not update in all files.
Thanks Jørgen and joseluismurillorios for your support and time spent answering :)

Where to put global helper js functions

I have several helper functions I intend to use throughout my Vue.js 2 application (i.e. within components and vuex). The one I'm using as an example here, toCurrency, converts a number to a currency string. I'm wondering where the best place to put this helper function is. I'm currently putting it at the top of my store.js file (I'm using vuex obviously) and calling it throughout the file. However, this means I also need to define it as a method when I want to use it in components. Is there a better place to put them so I don't have to keep defining it or is this the best place?
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
function toCurrency(num){
return "$" + num.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
}
export default new Vuex.Store({
state: {
price: 2
},
getters: {
numString: state => {
return toCurrency(state.funds)
}
},
mutations: {
decrementOne: state => {
state.num --;
alert("Price decremented to " + toCurrency(state.funds))
}
}
})
This sounds like a perfect case for Instance Variables. Essentially I would firstly setup an address such as this.$helperfunctions and then proceed to add all of my methods directly onto this instance member after which they will become available to the rest of your application.
import myObjectFullOfMethods from '../somewhere/foo/bar.js';
new Vue({
beforeCreate: function() {
this.$helperfunctions = new myObjectFullOfMethods();
}
})

How to create local helpers to tidy up Vue components

I have a Vue component that does a number of complex tasks in mounted(). These tasks include for example, initializing Bootstrap Date Pickers, Time Pickers, and Typeaheads.
At the moment all of this initialization code is in my mounted() method. In order to understand what's going on, the developer has to read through the code comments.
Ideally I would move sections of code to their own methods, and only have method calls in mounted(), something such as:
mounted () {
this.helpers.initDatePickers();
this.helpers.initTimePickers();
this.helpers.initTypeaheads();
}
How can I achieve this? I realise that I can put them in the methods object, but I would prefer to leave that for methods which can be accessed via declarations in templates.
Note that I am not asking how to share helper functions across components (or globally). I am merely asking how to create local functions in their own space, in order to clean up some longer methods.
You could create a mixin module which has generic initialization.
// DatePickerMixin.js
import whatever from 'specific-date-picker-stuff';
export default {
methods: {
initDatePickers() {
// initialization here
}
}
}
Then your component just uses the mixin modules.
<script>
import DatePickerMixin from './mixins/DatePickerMixin';
import TimePickersMixin from './mixins/TimePickersMixin';
export default {
mixins: [
DatePickerMixin,
TimePickersMixin
],
data() {/* ... */},
// etc.
}
</script>
You could wrap all of these in the same mixin as well.
And if you don't want to always set the mixins, there's global mixin.
import DatePickerMixin from './mixins/DatePickerMixin';
Vue.mixin(DatePickerMixin);
Use global mixins sparsely and carefully, because it affects every
single Vue instance created, including third party components.
As #EmileBergeron said, mixins are a good solution. You can also create plugins, which so happen to encompass mixins as well but much more. They allow you to extend the Vue constructor or add instances/methods directly to the Vue prototype.
Section on plugins from the documentation
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
Using your plugin is done by:
// calls `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
// pass options to your plugin
Vue.use(MyPlugin, { someOption: true })
Here's a small plugin I recycle exposing various string functions in the pluralize library:
import {plural, singular, camelCase} from 'pluralize'
PluralizePlugin.install = function (Vue, options) {
Vue.plural = plural
Vue.singular = singular
Vue.camelCase = camelCase
}
With it you can use this.singular(str), this.plural(str), etc. throughout your components. Pretty simple but convenient.

Categories

Resources