The situation
In my Vue app, I have a Vue component which mounts an svg, which I have defined with a few props. These are a combination of reactive and non-reactive data.
The reactive data that we need is percData, which therefore sits in the data(){} object.
We also have colours, width, height, and scale, which are not reactive and never will be. I don't call these in the <template> block, and I don't plan for them to change. These are currently declared with const, and are not within the export defautl{} block scope.
The question(s)
Where is the best place to be for these const declarations?
What scope are these const-declared variables currently in?
More generally, how does scope work for the <script> tag in Vue in a multi-component app? Is each script/component a separate scope from the global one? And where is the global scope?
My possible understanding
As per this thread and this thread, I think the best place for my const would be in a separate file, from which I would then import them in my mySvgComponent component. Is this the correct approach?
My code
<template>
<div></div>
</template>
<script>
import { mySvgComponent} from '../mySvgComponent'
import { select } from 'd3'
const [colour1, colour2, colour3, colour4] = ['#000000', '#111111', '#222222', '#3333'];
const width = 135
const height = 135
const scale = .75
export default {
name:'mySvgComponent',
data(){
return{
percData: null
}
},
props: {
summary: Object
},
methods: {
percSetup(summary) {
return this.percData = [
{ colour: colour1, perc: +summary.colour1Percentage.toFixed(2)},
{ colour: colour2, perc: +summary.colour2Percentage.toFixed(2)},
{ colour: colour3, perc: +summary.colour3Percentage.toFixed(2)},
{ colour: colour4, perc: +summary.colour4Percentage.toFixed(2)}
]
}
},
async mounted() {
this.percSetup(this.$props.summary)
const svg =
select('div')
.append('svg')
.call(mySvgComponent(this.percData)
.width(width)
.height(height)
.scale(scale))
}
}
</script>
<style></style>
Related threads and why I don't think they answer my question:
How to set a component non-reactive data in Vue 2?, How to make a template variable non-reactive in Vue, How could I use const in vue template?. I don't call my const variables in my <template> tag, and I don't need it to be responsive.
What is the best way to create a constant, that can be accessible from entire application in VueJs ?. Maybe I don't understand this fully. Why would I need to run a method() to return my const variables?
Vue SFC is syntax sugar, it's necessary to understand what code it's compiled to in order to use it effectively.
The result of SFC script syntax is ES module with roughly the same content as the body of <script> block, with name and render options being added by the compiler. Then general modular JavaScript practices are applicable. The constants can remain in current module if they don't take much lines and aren't reused in other modules, and can be moved to a separate module otherwise. In case the constants are supposed to be used in a template, they can be returned from data or setup function.
The result of SFC script setup syntax is ES module with the whole block being moved to generated setup function, with the exception of imports. In this case it's inefficient to declare constants in this block because they will be created on each component instantiation, this may be a reason to move them to a separate module, although this can be considered preliminary optimization.
Considering the above, the scope of Vue SFC modules works exactly like it's expected from ESM because this is what they are compiled to.
Vue composition API provides markRaw function to additionally prevent constant objects from being made reactive when they are used inside reactive ones like data, in case this is unwanted. The same is done for options API with Object.freeze in linked question.
TL;DR: the code in the question is ok, it's correct to use the constants like that in this case.
In Vue.js, you can set non-reactive (i.e., constant) variables by declaring them in the data object as a function that returns an object, rather than declaring them directly as properties of the data object. This ensures that the variables are only set once during the initialisation of the component and cannot be modified later. Here's an example:
data: function () {
return {
nonReactiveConst: 'This is a non-reactive const variable'
}
}
Another way to create a non-reactive variable in Vue is to use the Vue.observable() function to create an observable object, and then assign the non-reactive variable as a property of that object.
const state = Vue.observable({
nonReactiveConst: 'This is a non-reactive const variable'
});
You can then access the non-reactive variable inside the component's template using state.nonReactiveConst.
It's important to note that using this method, you can still mutate properties of the object, but you can't reassign the whole object.
Related
const app = createApp({
data() {
return {
some_id: 0
}
}
})
I have an autocomplete on a field.
When a label is selected, I want to pass the id to a Vue app.
onSelectItem: ({label, value}) => {
app.some_id = value;
}
This worked in an old v2 version of Vue.js.
Now, I can't even call the methods of the Vue app from other JavaScript functions.
What is the best solution?
There are certain circumstances where you may need to access and mutate, change the instance's data.
This was easier in Vue JS 2, but Vue JS 3 has become more encapsulated. However it does not mean mutating state from outside is impossible. You can read about it here
Supposing that you are using Vue with build steps, which covers most cases, you will have something like this:
const app = createApp({
data() {
return {}
},
})
.mount('#app');
Now if you head to browser console and type app, it will be null because it is limited to the scope of compiled .js files.
But if you attach app to a global object, document for example:
document.app = createApp({
data() {
return {}
},
})
.mount('#app');
Now if you type app in the console, it will no longer be null, but the Vue instance. From there, you can access the instance's data as well as mutate it via app.$data property.
Now what if the instance has components and you want to mutate their $data? In previous versions, it was possible to access children via $children property. But now in order to access children, you have to give each of them a ref, then access via their ref name. For example:
app.$refs.alertComponent.$data.message = "New message!"
I'm curious about passing props into setup and what are best practices to update variables/templates based on property changes.
I'm curious about reactive and computed.
For example:
setup(props) {
// Setup global config settings
const config = computed(() => {
return {
// See if the component is disabled
isDisabled: props.disabled, // (1)
// Test for rounded
isRounded: props.rounded // (2)
}
})
return { config }
}
Should config.isDisabled and config.isRounded be wrapped in their own computed function as they are both different and independent? However, it is easy to just stick them into one big function. What is best practice in this regard?
Does the entire config function evaluate once a single property changes within the function or can it recognize the change and update what is required?
Per docs, reactive is deeply reactive and used for objects, however, I've noticed it doesn't update to property changes. Therefore, I've been treating it more like data in Vue 2. Am I missing something or is this correct treatment?
You do not have to wrap props with computed at all, as they should be already reactive and immutable.
You also do not have to return config from your setup function as all props passed to your component should be automatically exposed to your template.
The computed function is evaluated only once and then Vue3 uses Proxy to observe changes to values and update only what's required. If you need to run a function every time a property changes you can use watchEffect.
Vue3 reactive is actually deep and works fine on objects. It should track all changes, unless you are trying to change the original object (the target of reactive function).
Reading the composition api documentation for Vue 3, I didn't quite understand how the new Composition API works. Could you please explain where the data() function has gone? And if it is no longer used what to use instead?
Updated 23.10.2021: The documentation in the link has been updated and expanded to include a mention of the data() in the Composition API introduction, so this question is now deprecated.
Under the new Composition API, all of the variables that you previously defined in data() are just returned from your setup() function as normal variables with reactive values. For example, a Vue 2.0 component that had a data function like so:
data() {
return {
foo: 1,
bar: { name: "hi" }
}
}
becomes this setup() function in Vue 3:
setup() {
const foo = ref(1);
const bar = reactive({ name: "hi" });
return { foo, bar }
}
The ref helper wraps a non-object value for reactivity, and reactive wraps an object. This is exposing the underlying principles of Vue more clearly than the old way, where the wrapping happened "magically" behind the scenes, but will behave the same otherwise. What I like about it personally is that your setup() function can build up your object on the go while keeping related things together, allowing it to tell a cohesive story and not require jumping around to different sections.
The composition is the new feature comes with Vue 3 and as a plugin for Vue 2, it doesn't replace the old option api but they could be used together in the same component.
The composition api compared to option api :
Gather the logic functionalities into reusable pieces of logic.
Use one option which the setup function which is executed before the component is created, once the props are resolved, and serves as the entry point for composition API's.
Define your old data option as ref or reactive properties
computed and watch is defined as : watch(...,()=>{...}) or computed(()=>{...})
Methods defined as plain javascript functions.
setup option used instead of created hook and it has as parameters the props and context
Hooks like mounted could be used as onMounted(()=>{...}), learn more
With script setup syntax you could declare your reactive data using ref, reactive and computed ...
<script setup >
import { ref, reactive, computed } from 'vue'
const isActive = ref(false)
const user = reactive({ firstName: 'John', lastName: 'Doe', age: 25 })
const fullName = computed(() => user.firstName + ' ' + user.lastName)
</script>
I wanted my directive to work as v-if since in my directive I have to check access rights and destroy the element if it does not have access.
Here is my code
Vue.directive('access', {
inserted: function(el, binding, vnode){
//check access
if(hasAccess){
vnode.elm.parentElement.removeChild(vnode.elm);
}
},
});
vue file
<my-component v-access='{param: 'param'}'>
The issue is that i'm applying this directive to a component, it's removing the component but not the execution of functions called by the created/mounted hook.
In the component(my-component) there are functions in mounted/created hook. The execution of these functions are done and I don't want these functions to be executed. Is there a way to stop execution of the mounted/created events?
It is impossible to replicate the behavior of v-if in a custom directive. Directives cannot control how vnodes are rendered, they only have an effect on the DOM element it is attached to. (v-if is special, it's not actually a directive but instead generates conditional rendering code when the template is compiled.)
Though I would avoid doing any of the following suggestions if possible, I'll provide them anyway since it's close to what you want to do.
1. Extend the Vue prototype to add a global method
You definitely need to use v-if to do the conditional rendering. So all we have to do is come up with a global helper method which calculates the access permission.
Vue.prototype.$access = function (param) {
// Calculate access however you need to
// ("this" is the component instance you are calling the function on)
return ...
}
Now in your templates you can do this:
<my-component v-if="$access({ param: 'param' })">
2. Define global method in the root component
This is basically the same as #1 except instead of polluting the Vue prototype with garbage, you define the method only on the root instance:
new Vue({
el: '#app',
render: h => h(App),
methods: {
access(param) {
return ...
}
}
})
Now in your templates you can do this:
<my-component v-if="$root.access({ param: 'param' })">
Now it's clearer where the method is defined.
3. Use a global mixin
This may not be ideal, but for what it's worth you can investigate the viability of a global mixin.
4. Use a custom component
You can create a custom component (ideally functional but it needn't be) that can calculate access for specific regions in your template:
Vue.component('access', {
functional: true,
props: ['param'],
render(h, ctx) {
// Calculate access using props as input
const access = calculateAccess(ctx.props.param)
// Pass the access to the default scoped slot
return ctx.scopedSlots.default(access)
}
})
In your templates you can do this:
<access :param="param" v-slot="access">
<!-- You can use `access` anywhere in this section -->
<div>
<my-component v-if="access"></my-component>
</div>
</access>
Since <access> is a functional component, it won't actually render it's own component instance. Think of it more like a function than a component.
A bit overkill for your situation, but interesting nonetheless if you ever have a more complicated scenario.
I need to declare a global variable that can be accessed by all components in my Vue.js app. All components should be able to change its value. I am using CLI template.
Any suggestions?
Thank you
Dhiaa Eddin Anabtawi
In general in vue it's not possible/recommended to directly change a property in a parent scope. The way communication happens is by passing props to child components and to send back events (possibly with values) to parents (see Passing Data to Child Components with Props and Sending Messages to Parents with Events).
If you want to have easily accessible global state, a clean way to do it is to use a "store" as described in the State Management chapter of the vue guide:
var store = {
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
this.state.message = newValue
},
clearMessageAction () {
this.state.message = ''
}
}
...
var vm = new Vue({
...
data: {
sharedState: store.state
}
...
})
then you can use this.$root.$data.sharedState to access the global state, for exampe using this.$root.$data.sharedState.message to read the message or store.setMessageAction() to modify it (assuming you are importing the store symbol).
That said, at this point, you would be much better served by using vuex, the official solution for centralized state management, which is easier, cleaner and less error-prone.