This may not be the Vue way to do things, but in React if I wanted to make a FancyList component I could do something like this
const FancyList : React.SFC<{},{}> ({children}) => (
<ul>
{...children}
</ul>
);
const FancyListItem : React.SFC<{text: string}, {}> ({children}) => <li>{...children}</li>
const App = React.SFC<{},{}> () => (
<FancyList>
<FancyListItem>foo</FancyListItem>
<FancyList>
);
This would render a <ul> with <li> children. I am trying to do something similar in Vue, but passing children around doesn't seem to be the thing to do, at least not from what I can see in the documentation. Here is what I have tried in the parent component. I have registered my FancyListItem component to the parent, and to the root. In the parent component the template looks like this
<ul>
<li v-for child in $root.$children>
</ul>
or this
<ul>
<li v-for child in children>
</ul>
In the root the template looks like this
<FancyList>
<FancyListItem>Some Text</FancyListItem>
<FancyListItem>Some Other Text</FancyListItem>
</FancyList>
In either case I can't get anything to render to the ul.
You are right, that's not the Vue way.
In vue you would use slots:
This allows you to compose components like this:
<navigation-link url="/profile">
Your Profile
</navigation-link>
Then in the template for <navigation-link>, you might have:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
https://v2.vuejs.org/v2/guide/components-slots.html
The Vue way of doing this is:
fancyListItem.vue:
<template>
<li>
<slot></slot>
</li>
</template>
fancyList.vue:
<template>
<ul>
<slot></slot>
</ul>
</template>
Root:
<template>
<fancy-list>
<fancy-list-item v-for="item in items" :key="item.id">
{{ item.text }}
</fancy-list-item>
</fancy-list>
</template>
<script>
export default {
components: {
FancyList: ()=>import('fancyList.vue'),
FancyListItem: ()=>import('fancyListItem.vue')
},
data(){
return {
items: [
//...items
]
}
}
}
</script>
Related
I have the following vue3 code:
<template>
{{click}}
<ol>
<li v-for="item in items" :key="item" v-html="item"></li>
</ol>
</template>
<script setup>
const click = ref();
const items = [
'<p>text</p><a #click="click+1">click</a>',
'<p>text2</p><p><router-link :to="{name: \'home\'}">home</router-link></p>'
];
</script>
How can I get the #click and router-link component to be compiled/rendered correctly in the output?
Vue does not render the string content to html. Does anyone have an idea how I can get this to work? I don't wont to hard code the li-items.
Your code is a big red flag. Thats not the way how to work with vue.js
Here an example how it could be done:
<template>
<ol>
<li v-for="item in items" :key="item.link" >
<router-link :to="item.link">{{ item.name }}</router-link>
</li>
</ol>
</template>
<script setup>
const click = ref();
const items = ref([
{
name: "Link1",
link: "/home"
},
{
name: "Link2",
link: "/login"
}
]);
</script>
I have no idea what this code is supposed to do #click="click+1"
Also, the v-html attribute isnt a attribute that is common used. Because its also dangerous and leads to XSS vulnerabilities if you are not carefully.
I am unable to get the customer 'dad' in my store using getters. But, it shows on the screen the objects like this [ "dad" ]. How am I supposed to correct this?. I am a newbie to vue.js
<template>
<div class="hello">
<p>{{ GET_CUSTOMERS }}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed:{
...mapGetters([
'GET_CUSTOMERS'
])
}
</script>
My store
state: {
customers: ['dad']
],
getters: {
GET_CUSTOMERS(state){
return state.customers;
}
}
Your customers data is an array. When you render an array directly in a template like {{ ['dad'] }}, Vue will render it in the DOM as [ "dad" ].
I take it you want to render just one customer? You can render the first customer like this:
{{ GET_CUSTOMERS[0] }}
To render each customer, you will need to loop over each customer using v-for:
<ul>
<li v-for="(customer, i) of GET_CUSTOMERS" :key="i">
{{ customer }}
</li>
</ul>
Or, if you prefer, you can just join each customer into a single string with each customer separated by a comma:
{{ GET_CUSTOMERS.join(', ') }}
So I have a page rendering a v-list based on an array like so :
<v-list-tile v-for="item in array">
{{item}}
</v-list-tile>
and a dialog with a v-text-field :
<v-dialog>
<v-text-field v-model="myInput">
</v-text-field>
</v-dialog>
For now it's pretty normal.
But with a performance test, I saw that for every event triggered by a change on myInput model (like a key press) the v-for is also triggered re-rendering the list when they are actually not related.
On my huge array, it's a serious problem and make the UI really laggy. I think it's a normal behavior for a vuejs application, but I was wondering if I could precisely tell wish element to check for re-rendering.
I tried some v-if statements but it didn't do the trick.
I hope that there is an answer to that, i guess i'm missing something.
If you want to test what i'm talking about here is a ready to go html file, please debug it with your debug console, you will see a [vue warn] message of the duplicated key attesting of the fact that the v-for is indeed called for every key press.
Imagine now if the array (here items) is way bigger than that, and wrapped into complex components, making that call is just too heavy on performance when we are just aiming to change the "myInput" value.
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{data}}
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
<input v-model="data"></input>
</div>
</body>
<script>
new Vue({
el: '#app',
data: () => ({
data: '',
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
</script>
</html>
Here's a codepen showing the inner loop in its own component
Codepen.io
I've added Date.now() after items[x].message list items to show when the list is being rerendered.
In case codepen ever goes down:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
Main vue: {{data}}
<loop-component :data="loopdata"></loop-component>
<input v-model="data"></input>
<input v-model="loopdata"></input>
</div>
<script>
Vue.component('loop-component', {
props: ['data'],
data() {
return {
items: [
{message: 'Foo'},
{message: 'Bar'}
]
}
},
template: `
<div>
Loop component: {{ data }}
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.message + ' Date.now(): ' + Date.now() }}
</li>
</ul>
</div>
`
});
let app = new Vue({
el: '#app',
data: () => ({
data: '',
'loopdata': '',
items: [
{message: 'Foo'},
{message: 'Bar'},
]
}),
});
</script>
</body>
</html>
Try using .lazy modifier to sync after change events.
<input v-model.lazy="data"></input>
https://v2.vuejs.org/v2/guide/forms.html#lazy
EDIT
#IVO GELOV is right, when a component changes, this re-render. The solution is split your component into several child components.
https://v2.vuejs.org/v2/guide/reactivity.html
This is a code using slots to make it look like your example.
HTML
<div id="app">
<new-component>
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
</new-component>
</div>
Javascript
Vue.component('new-component', {
data: () => {
return {
data: ''
}
},
template: `
<div>
<div>{{ data }}</div>
<slot></slot>
<input v-model="data"></input>
</div>`
})
new Vue({
el: '#app',
data: () => ({
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
Since Vue 2.0+ whenever a change is detected - the whole component is re-rendered. If you want to avoid that - split your component into several child components.
Your example does not prove your point - the fact that there is a warning about duplicate keys inside the v-for does not mean that v-for is re-evaluated on each keypress. To confirm my statement - just change your code like this:
<li v-for="(item,idx) in items" :key="idx">
Now there is no warning.
I have the following code for a Vue.js component:
var list = Vue.component('list', {
props: ['items', 'headertext', 'placeholder'],
template: `
<div class="col s6">
<div class="search">
<searchbox v-bind:placeholder=placeholder></searchbox>
<ul class="collection with-header search-results">
<li class="collection-header"><p>{{ headertext }}</p></li>
<collection-item
v-for="item in items"
v-bind:texto="item.nome"
v-bind:link="item.link"
v-bind:key="item.id"
>
</collection-item>
</ul>
</div>
</div>`,
methods: {
atualizaBibliografiasUsandoHipotese: function (value) {
var query = gql`query {
allHipotese(nome: "${value}") {
id
nome
bibliografiasDestaque {
id
nome
link
descricao
}
}
}`;
client.query({ query }).then(function(results) {
this.items = results['data']['allHipotese'][0]['bibliografiasDestaque'];
}.bind(this));
},
}
});
And outside the component, in another javascript file, I call the atualizaBibliografiasUsandoHipotese method to make a query to the backend (GraphQL), and update the component. This works fine, however I get a warning message from Vue saying
[Vue warn]: 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: "items"
I made several attempts to fix this warning, but none of them worked. Anyone can help me on how can I fix this warning? Thanks
As mentioned, you should not mutate props, instead you can do this:
var list = Vue.component('list', {
props: ['items', 'headertext', 'placeholder'],
template: `
<div class="col s6">
<div class="search">
<searchbox v-bind:placeholder=placeholder></searchbox>
<ul class="collection with-header search-results">
<li class="collection-header"><p>{{ headertext }}</p></li>
<collection-item
v-for="item in myItems"
v-bind:texto="item.nome"
v-bind:link="item.link"
v-bind:key="item.id"
>
</collection-item>
</ul>
</div>
</div>`,
data: ()=> { return {
myItems: this.items
}
},
methods: {
atualizaBibliografiasUsandoHipotese: function (value) {
// Your Code
this.myItems = results['data']['allHipotese'][0]['bibliografiasDestaque'];
}.bind(this));
},
});
Notice that v-for is now looping over items in myItems.
This is what you are looking for:
https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
It even says:
This means you should not attempt to mutate a prop inside a child
component. If you do, Vue will warn you in the console.
I have a categories array, which is loaded once (in created hook) and then it is static all the time. I render these array values in a component template.
<template>
<ul>
<li v-for="item in myArray">{{ item }}</li>
</ul>
</template>
My data property looks (it does not include myArray - I don't want reactive binding):
data() {
return {
someReactiveData: [1, 2, 3]
};
}
My create hook:
created() {
// ...
this.myArray = ["value 1", "value 2"];
// ...
}
Problem is, that Vue throws error - I can't use myArray in a template, because this variable is not created when the DOM is created - mounted.
So how to do this? Or where can be stored component constants?
Vue sets all the properties in the data option to setters/getters to make them reactive. See Reactivity in depth
Since you want myArray to be static you can create it as a custom option which can be accessed using vm.$options
export default{
data() {
return{
someReactiveData: [1, 2, 3]
}
},
//custom option name myArray
myArray: null,
created() {
//access the custom option using $options
this.$options.myArray = ["value 1", "value 2"];
}
}
you can iterate over this custom options in your template as follows:
<template>
<ul>
<li v-for="item in $options.myArray">{{ item }}</li>
</ul>
</template>
Here is the fiddle
Actually, setting properties on this in created() should work out of the box:
<template>
<div id="app">
<ul>
<li v-for="item in myArray" :key="item">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "app",
created() {
this.myArray = [
'item 1',
'item 2'
];
}
};
</script>
will render
<div id="app">
<ul>
<li>
item 1
</li>
<li>
item 2
</li>
</ul>
</div>
Demo here: https://codesandbox.io/s/r0yqj2orpn .
I prefer using static data (non reactive) like this:
Create a mixin (i name it static_data.js) with the follow content
import Vue from 'vue'
Vue.prototype.$static = {}
export default {
beforeCreate () {
const vue_static = this.$options.static
const vue_static_destination = this.$static || this
if (vue_static && typeof(vue_static) === 'function') {
Object.assign(vue_static_destination, vue_static.apply(this))
} else if (vue_static && typeof(vue_static) === 'object') {
Object.assign(vue_static_destination, vue_static)
}
}
}
In your components where you want to use static data you can do:
import use_static_data from '#mixins/static_data'
export default {
mixins: [use_static_data],
static: () => ({
static_value: 'Vue is awesome'
}),
created () {
console.log(this.$static.static_value); // Vue is awesome
}
}
There is also a package vue-static
Credits here.
If you want to keep it in data, the proper way is using Object.freeze(), as described in the documentation:
The only exception to this being the use of Object.freeze(), which
prevents existing properties from being changed, which also means the
reactivity system can’t track changes.
You can try this line of code. You can copy an object and remove the reactivity.
var newObj = JSON.parse(JSON.stringify(obj));
<template>
<div id="app">
<ul>
<li v-for="item in myArray" :key="item">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "app",
data () {
this.myArray = [
'item 1',
'item 2'
];
return {}
}
};
</script>