My website overwrites a specific DOM instead of asking for a page load. Sometimes, inside the DOM there will be some Vue.js components (all are single page components), which will be overwritten. Most of them reside in an app created for the sole purpose of building the component.
Two questions:
do I need to destroy those components? If YES: how?
Is there any better solution to this approach?
There is a destroy command to erase components:
Completely destroy a vm. Clean up its connections with other existing vms, unbind all its directives, turn off all event listeners.
Triggers the beforeDestroy and destroyed hooks.
https://v2.vuejs.org/v2/api/#vm-destroy
But if you only want the remove/hide the components, which are inside of one or many instances (i.e. the ones that are mounted via .$mount() or el: "#app" ) you should try to remove the components via v-if or hide via v-show.
In the instance template this will look like:
<my-component v-if="showMyComponent"></my-component>
<my-component v-show="showMyComponent"></my-component>
The v-show will just set display:none while v-if will remove the component from the DOM in the same way as $destroy() does. https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
You must use a local data prop and use in in directives v-show and v-if
<div id="application">
<vm-test v-if="active"></vm-test>
<div #click="toggle()">toggle</div>
</div>
Vue.component('vm-test', {
template: '<div>test</div>'
});
var vm = new Vue ({
el: "#application",
data: function() {
return {
active:true
}
},
methods: {
toggle: function() {
this.active = !this.active;
}
}
})
https://jsfiddle.net/99yxdacy/2/
Related
vuetify: ^2.5.10
vuex: ^3.6.2
vue: ^2.6.14
I have an application developed in v-tabs. In the child component in the first of these tabs, I have a
<v-file-input
v-model="file"
outlined
:rules="selector_rules"
:clearable="false"
:show-size="1000">
</v-file-input>
<v-btn color="primary" #click="commitData" class="mr-2">Commit</v-btn>
with
commitData() {
this.$store.commit('setFile', this.file)
},
The store gets correctly set because the following computed property in the parent component
computed: {
...mapGetters({
file: 'getFile',
}),
},
seems to work: I watch it in this way:
file: {
deep: true,
immediate: true,
handler() {
console.log("Received file: " + this.file);
}
},
The surprising thing, at least to me, is that the very same watch above, implemented in another component in the second tab, does not work until I visit (i.e., switch to) the second panel. However, the computed property works in this component because in the template I can see the new property.
After the first visit to the tab, the watch continues to work without problems.
Is there a reason for that and a way to avoid visiting it to make the watch work?
v-tabs loads content only on visit. Which means component will mount only when it's active.
You can use eager prop on your v-tab-item components to make sure they're always loaded and mounted.
API Docs reference: https://vuetifyjs.com/en/api/v-tab-item/#props-eager
I am curious if it is better to include methods within loops instead of using v-if. Assume the following codes work (they are incomplete and do not)
EX: Method
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<span v-on:click="insertPrompt($event)">
{{ d }}
</span>
</div>
</div>
</template>
<script>
export default {
data() {
data:[
.....
]
},
methods:{
insertPrompt(e){
body.insertBefore(PROMPT)
}
}
}
</script>
The DOM would be updated via the insertPrompt() function which is just for display
EX: V-IF
//Parent
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<child v-bind:data="d"/>
</div>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data() {
data:[
.....
]
},
}
</script>
//Child
<template>
<div>
<span v-on:click="display != display">
{{ d }}
</span>
<PROMPT v-if="display"/>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data(){
return {
display:false
}
},
props: {
data:{
.....
}
},
}
</script>
The PROMPT is a basic template that is rendered with the data from the loop data click.
Both methods can accomplish the same end result. My initial thought is having additional conditions within a loop would negatively impact performance?!?!
Any guidance is greatly appreciated
Unless you are rendering really huge amounts of items in your loops (and most of the times you don't), you don't need to worry about performance at all. Any differences will be so small nobody will ever notice / benefit from having it a tiny touch faster.
The second point I want to make is that doing your own DOM manipulations is often not the best idea: Why do modern JavaScript Frameworks discourage direct interaction with the DOM
So I would in any case stick with the v-if for conditionally rendering things. If you want to care about performance / speed here, you might consider what exactly is the way your app will be used and decide between v-if and v-show. Citing the official documentation:
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
v-if is also lazy: if the condition is false on initial render, it
will not do anything - the conditional block won’t be rendered until
the condition becomes true for the first time.
In comparison, v-show is much simpler - the element is always rendered
regardless of initial condition, with CSS-based toggling.
Generally speaking, v-if has higher toggle costs while v-show has
higher initial render costs. So prefer v-show if you need to toggle
something very often, and prefer v-if if the condition is unlikely to
change at runtime.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
There are numerous solutions to solving this issue, but let's stick to 3. Options 2 and 3 are better practices, but option 1 works and Vue was designed for this approach even if hardcore developers might frown, but stick yoru comfort level.
Option 1: DOM Manipulation
Your data from a click, async, prop sets a condition for v-if or v-show and your component is shown. Note v-if removes the DOM element where v-show hides the visibility but the element is still in the flow. If you remove the element and add its a complete new init, which sometimes works in your favor when it come to reactivity, but in practice try not to manipulate the DOM as that will always be more expensive then loops, filters, maps, etc.
<template >
<div>
<div v-for="(d, i) in getData"
:key="i">
<div v-if="d.active">
<child-one></child-one>
</div>
<div v-else-if="d.active">
<child-two></child-two>
</div>
</div>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
data: [],
}
},
computed: {
getData() {
return this.data;
},
},
mounted() {
// assume thsi woudl come from async but for now ..
this.data = [
{
id: 1,
comp: 'ChildOne',
active: false
},
{
id: 2,
comp: 'ChildTwo',
active: true
},
];
}
}
</script>
Option 2: Vue's <component> component
Always best to use Vue built in component Vue’s element with the is special attribute: <component v-bind:is="currentTabComponent"></component>
In this example we pass a slug or some data attribute to activate the component. Note we have to load the components ahead of time with the components: {}, property for this to work i.e. it has to be ChildOne or ChildTwo as slug string. This is often used with tabs and views to manage and maintain states.
The advantage of this approach is if you have 3 form tabs and you enter data on one and jump to the next and then back the state / data is maintained, unlike v-if where everything will be rerendered / lost.
Vue
<template >
<div>
<component :is="comp"/>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
props: ['slug'],
data() {
return {
comp: 'ChildOne',
}
},
methods: {
setComponent () {
// assume prop slug passed from component or router is one of the components e.g. 'ChildOne'
this.comp = this.slug;
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
Option 3: Vue & Webpack Async and Dynamic components.
When it comes to larger applications or if you use Vuex and Vue Route where you have dynamic and large number of components then there are a number of approaches, but I'll stick to one. Similar to option 2, we are using the component element, but we are using WebPack to find all Vue files recursively with the keyword 'module'. We then load these dynamically / asynchronous --- meaning they will only be loaded when needed and you can see this in action in network console of browser. This means I can build components dynamically (factory pattern) and render them as needed. Example, of this might be if a user adds projects and you have to build and config views dynamically for projects created e.g. using vue router you passed it a ID for a new project, then you would need to dynamically load an existing component or build and load a factory built one.
Note: I'll use v-if on a component element if I have many components and I'm unsure the user will need them. I don't want to maintain state on large collections of components because I will end up memory and with loads of observers / watches / animations will most likely end up with CPU issues
<template >
<div>
<component :is="module" v-if="module"/>
</div>
</template>
<script>
const requireContext = require.context('./', true, /\.module\.vue$/);
const modules = requireContext.keys()
.map(file =>
[file.replace(/(.*\/(.+?)\/)|(\.module.vue$)/g, ''), requireContext(file)]
)
.reduce((components, [name, component]) => {
// console.error("components", components)
components[name] = component.default || component
return components
}, {});
export default {
data() {
return {
module: [],
}
},
props: {
slug: {
type: String,
required: true
}
},
computed: {
getData() {
return this.data;
},
},
methods: {
setModule () {
let module = this.slug;
if (!module || !modules[module]) {
module = this.defaultLayout
}
this.module = modules[module]
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
My initial thought is having additional conditions within a loop would negatively impact performance?
I think you might be confused by this rule in the style guide that says:
Never use v-if on the same element as v-for.
It's only a style issue if you use v-if and v-for on the same element. For example:
<div v-for="user in users" v-if="user.isActive">
But it's not a problem if you use v-if in a "child" element of a v-for. For example:
<div v-for="user in users">
<div v-if="user.isActive">
Using v-if wouldn't have a more negative performance impact than a method. And I'm assuming you would have to do some conditional checks inside your method as well. Remember that even calling a method has some (very small) performance impact.
Once you use Vue, I think it's a good idea not to mix it up with JavaScript DOM methods (like insertBefore). Vue maintains a virtual DOM which helps it to figure out how best to update the DOM when your component data changes. By using JavaScript DOM methods, you won't be taking advantage of Vue's virtual DOM anymore.
By sticking to Vue syntax you also make your code more understandable and probably more maintainable other developers who might read or contribute to your code later on.
I would like to have two components, one for displaying a value, and one for changing it with a text field. I can't get this to work? Is there another way of doing this?
I get this error message:
"Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "forpris""
Vue.component('prislapp-forpris', {
props: ['forpris'],
template: '<div class="prislappForpris">[[ forpris ]],-</div>',
delimiters: ['[[',']]']
});
Vue.component('input-forpris', {
props: ['forpris'],
template: '<input type="text" v-model="forpris" />'
});
var app = new Vue({
el: '.previewPage',
data: {
lapp: {
id: 1,
forpris: 30595
}
}
});
It's all about v-model directly mutating the forpris prop. As the warning states, you should avoid to mutate a prop from a component.
Rationale behind the warning
The reason is that allowing child component to modify props that belong to their parents make programs more error prone and difficult to reason about.
Instead, the idea behind Vue and other component oriented architectures and frameworks is that child components emit events to their parents, and then the parents change their own state, which in turn modify the child component via events from their children.
This ensures that the component passing down the props have full control of the state and may, or may not, allow the desired state changes that come via props.
How to fix your code to avoid the warning
v-model is syntax sugar over a :value and an #input on the input element. A really good read to understand how v-model innerly works is this article.
What you should do, is to drop v-model on the input for this:
template: '<input type="text" :value="forpris" #input="$emit('input', $event)" />'
This will set forpris as the value of the input (as v-model was already doing), but, instead of automatically modifying it, now the component will emit an input event when the user writes in the input.
So you now need to listen for this event in the parent and react accordingly. Now from your code is not absolutely clear who is rendering the two component, I guess the rendering comes from the .previewPage element in the Vue template, so the Vue instance is the parent component here.
You don't show the html of that template, but I guess it is something like the following:
<div class="previewPage">
<prislapp-forpris :forpriss="lapp.forpris" />
<input-forpris :forpriss="lapp.forpris" />
</div>
You now should listen to the #input event in the input-forpriss component:
<div class="previewPage">
<prislapp-forpris :forpriss="lapp.forpris" />
<input-forpris :forpriss="lapp.forpris" #input="handleInput" />
</div>
So, whenever we receive an #input event, we call the handleInput method. We also need to add such method to the Vue instance:
var app = new Vue({
el: '.previewPage',
data: {
lapp: {
id: 1,
forpris: 30595
}
},
methods: {
handleInput(value){
console.log(value); // now I'm not 100% sure if this
// is the value or a DOM event, better check
this.lapp.forpriss = value;
},
}
});
I opened a similar topic a few days ago, where I was suggested to use beforeRouteLeave within the route component definition.
However, I'm creating a Vue component and I won't have control over how developers wants to define their route components. Therefore, I need a way to fire an event within my own component and don't rely on external route components.
When changing from one route to another, the beforeDestroy gets fired after the DOM structure changes.
I've tried using beforeUpdate and updated events on my component definition, but none seems to fire before the DOM changes.
import Vue from 'vue'
import MyComponent from '../myComponent/' // <-- Need to fire the event here
import router from './router'
Vue.use(MyComponent)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
}).$mount('#app')
In the Vue instance lifecycle, the hook beforeDestroy gets called once the DOM has changed.
You are most likely looking for a beforeUnmount hook, which would be in-between mounted and beforeDestroy, but that is not available:
However, you could take advantage of JavaScript hooks. There is a JavaScript hook called leave, where you can access the DOM before it changes.
leave: function (el, done) {
// ...
done()
},
For this to work, you would need to wrap your element in a <transition> wrapper component.
ie.
<transition
:css="false"
#leave="leave"
>
<div>
<!-- ... -->
</div>
</transition>
...
methods: {
leave(el, done) {
// access to DOM element
done()
}
}
I have a Vue app that conditionally displays sets of the same component, I'd like to tie in to the created or mounted methods, but Vue keeps re-using earlier components and not triggering the methods. I've tried wrapping the components in keep-alive tags to no effect.
The code below shows what you would expect on the page: changing someVariable to 'test1' or 'test2' shows the relevant components, but the created/mounted methods only fire a maximum of 2 times (e.g. changing someVariable to 'test1' logs creation and mounting of 1 component with the label type1, then changing it to 'test2' only logs 1 more component with the label type3).
Currently using v2.1.10
HTML
<div id="app">
<div v-if="someVariable === 'test1'">
<keep-alive>
<test-component data-type="type1"></test-component>
</keep-alive>
</div>
<div v-if="someVariable === 'test2'">
<keep-alive>
<test-component data-type="type2"></test-component>
</keep-alive>
<keep-alive>
<test-component data-type="type3"></test-component>
</keep-alive>
</div>
</div>
JS/Vue
Vue.component('test-component', {
props: ['data-type'],
template: '<div><p>In the component: {{ dataType }}</p></div>',
created: function () {
console.log('component created: ', this.dataType);
},
mounted: function () {
console.log('component mounted: ', this.dataType);
}
});
var app = new Vue({
el: '#app',
data: {
someVariable: ""
}
});
You should use a watcher on your someVariable instead of trying to hook on created and mounted hooks.
Components are created, and mounted the first time they are visible (rendered). There are NO "shown" or "hidden" hooks.
See https://v2.vuejs.org/v2/guide/computed.html#Watchers:
watch: {
someVariable: function (newValue) {
// test newValue and do what you have to do!
}
}
For your specific example removing keep-alive from the second if should do the trick https://jsfiddle.net/z11fe07p/464/
An interesting thing is that vue seems to re-use the previously rendered component. So if you have one component in the first if when switching to the next if with 2 components it will re-use one component and create a new one for the second component from the block. When getting back to the first if block it will re-use one of the 2 already rendered components.
As mentioned above, a watcher is more suited for such cases, thus getting you rid of handling logic in places where you don't have full control. You can see this tip right here in the docs https://v2.vuejs.org/v2/api/#updated