I'm trying to write a custom ponent. And hope I can use it like this
let app = new Vue({
el:'#app',
template:`
<tab>
<tab-item name='1'>
<h1> This is tab item 1</h1>
</tab-item>
<tab-item name='2'>
<h2> This is tab item 2</h2>
</tab-item>
</tab>`,
ponents:{
tab,
tabItem
}
})
Everything goes fine until you click the button. I got an error from console:
[Vue warn]: You may have an infinite update loop in a ponent render function.
found in
---> <Tab>
<Root>
I've tried many ways to solve this problem, however, failure always won the debugging petition.
How can I beat this problem?
Here is my code:
let tabItem = {
props:{
name:{
type: String,
required: true
}
},
render(h){
let head = this.$slots.head || ''
let body = this.$slots.default
let tail = this.$slots.tail || ''
return h('div', [
h('div', head),
h('div', body),
h('div', tail)])
}
}
let tab = {
data(){
return {
items:'',
currentView:0
}
},
methods:{
handleTabClick(item){
return ()=>{
let index = this.items.indexOf(item)
this.currentView = this.items[index]
}
},
extractProps(vnode){
return vnodeponentOptions.propsData
}
},
render(h){
this.items = this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
let headers = this.items.map( item => {
let name = this.extractProps(item).name
return h('button', {
on:{
click: this.handleTabClick(item)
}
}, name)
})
let head = h('div', headers)
this.currentView = this.items[0]
return h('div',[head, this.currentView])
}
}
Or any other ways to implement this ponent?
Thanks a lot for helping me out from the hell.
Thanks for your reply my friends. I'm pretty sure that I get an infinite loop error from the console and my code doesn't work as expected. I don't think using vnode
is a good way to implement this ponent too. However, this is the best solution I can figure out.
This ponent -- tab
should detect its child whose name is tabItem
, which is also a ponent. And tab
can extract some data from tabItem
. In my case, tab
will extract the name
property of tabItemn
, which will be used to generate the buttons for switching content. Click the button can switch to the relevant content, which is the body of tabItem
. In my code, it's currenView
.
Like a famous UI library, Element, its tab
ponent can be used like this:
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
<el-tab-pane label="Role" name="third">Role</el-tab-pane>
<el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
</el-tabs>
I need to implement one ponent like this but mine will be more simple. For learning how to do it, I read its source code. Maybe there's not a good way to filter child ponents. In the source, they use this to filter the el-tab-pane
ponent:
addPanes(item) {
const index = this.$slots.default.filter(item => {
return item.elm.nodeType === 1 && /\bel-tab-pane\b/.test(item.elm.className);
}).indexOf(item.$vnode);
this.panes.splice(index, 0, item);
}
Source Code
I know that I can use $children
to access its child ponents but doing so doesn't guarantee the order of the child ponents, which is not what I want. Because the order of switching button is important. Detail messages about vnode
are not contained in the doc. I need to read the source.
Therefore, after reading the source of Vue, I wrote my code like this then I got my problem.
I finally didn't solve this bug and admit that using this kind of rare code sucks. But I don't know other solutions. So I need you guys help.
Thanks.
I'm trying to write a custom ponent. And hope I can use it like this
let app = new Vue({
el:'#app',
template:`
<tab>
<tab-item name='1'>
<h1> This is tab item 1</h1>
</tab-item>
<tab-item name='2'>
<h2> This is tab item 2</h2>
</tab-item>
</tab>`,
ponents:{
tab,
tabItem
}
})
Everything goes fine until you click the button. I got an error from console:
[Vue warn]: You may have an infinite update loop in a ponent render function.
found in
---> <Tab>
<Root>
I've tried many ways to solve this problem, however, failure always won the debugging petition.
How can I beat this problem?
Here is my code:
let tabItem = {
props:{
name:{
type: String,
required: true
}
},
render(h){
let head = this.$slots.head || ''
let body = this.$slots.default
let tail = this.$slots.tail || ''
return h('div', [
h('div', head),
h('div', body),
h('div', tail)])
}
}
let tab = {
data(){
return {
items:'',
currentView:0
}
},
methods:{
handleTabClick(item){
return ()=>{
let index = this.items.indexOf(item)
this.currentView = this.items[index]
}
},
extractProps(vnode){
return vnode.ponentOptions.propsData
}
},
render(h){
this.items = this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
let headers = this.items.map( item => {
let name = this.extractProps(item).name
return h('button', {
on:{
click: this.handleTabClick(item)
}
}, name)
})
let head = h('div', headers)
this.currentView = this.items[0]
return h('div',[head, this.currentView])
}
}
Or any other ways to implement this ponent?
Thanks a lot for helping me out from the hell.
Thanks for your reply my friends. I'm pretty sure that I get an infinite loop error from the console and my code doesn't work as expected. I don't think using vnode
is a good way to implement this ponent too. However, this is the best solution I can figure out.
This ponent -- tab
should detect its child whose name is tabItem
, which is also a ponent. And tab
can extract some data from tabItem
. In my case, tab
will extract the name
property of tabItemn
, which will be used to generate the buttons for switching content. Click the button can switch to the relevant content, which is the body of tabItem
. In my code, it's currenView
.
Like a famous UI library, Element, its tab
ponent can be used like this:
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
<el-tab-pane label="Role" name="third">Role</el-tab-pane>
<el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
</el-tabs>
I need to implement one ponent like this but mine will be more simple. For learning how to do it, I read its source code. Maybe there's not a good way to filter child ponents. In the source, they use this to filter the el-tab-pane
ponent:
addPanes(item) {
const index = this.$slots.default.filter(item => {
return item.elm.nodeType === 1 && /\bel-tab-pane\b/.test(item.elm.className);
}).indexOf(item.$vnode);
this.panes.splice(index, 0, item);
}
Source Code
I know that I can use $children
to access its child ponents but doing so doesn't guarantee the order of the child ponents, which is not what I want. Because the order of switching button is important. Detail messages about vnode
are not contained in the doc. I need to read the source.
Therefore, after reading the source of Vue, I wrote my code like this then I got my problem.
I finally didn't solve this bug and admit that using this kind of rare code sucks. But I don't know other solutions. So I need you guys help.
Thanks.
Share Improve this question edited Feb 8, 2018 at 7:09 Tomasz Mularczyk 36.2k19 gold badges118 silver badges174 bronze badges asked Feb 7, 2018 at 14:26 ucagucag 4872 gold badges8 silver badges24 bronze badges 2- For sure you have an infinite loop. Your render function always calls itself. What are you trying to do, and why do you need slots, vnode and hand-rolled render functions (which are exotic, potentially plicated and rarely used) ? – bbsimonbb Commented Feb 7, 2018 at 15:08
- @bbsimonbb yeah, I also think this sucks, but I can't find another way to do it.. I add more information. – ucag Commented Feb 8, 2018 at 6:43
1 Answer
Reset to default 6You shouldn't change your data in render function, this is wrong
this.items = this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
because it will keep re-rendering, here is a working example for your code, I simply removed items
property from data and added new items
puted property which returns tab-item
s nodes.
let tab = {
data(){
return {
currentView:0
}
},
methods:{
handleTabClick(item){
return ()=>{
let index = this.items.indexOf(item)
this.currentView = this.items[index]
}
},
extractProps(vnode){
return vnode.ponentOptions.propsData
}
},
puted: {
items(){
return this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
}
},
render(h){
let headers = this.items.map( item => {
let name = this.extractProps(item).name
return h('button', {
on:{
click: this.handleTabClick(item)
}
}, name)
})
let head = h('div', headers)
this.currentView = this.items[0]
return h('div',[head, this.currentView])
}
}
let tabItem = {
name:"tab-item",
props:{
name:{
type: String,
required: true
}
},
render(h){
let head = this.$slots.head || ''
let body = this.$slots.default
let tail = this.$slots.tail || ''
return h('div', [[
h('div', head),
h('div', body),
h('div', tail)]])
}
}
let app = new Vue({
el:'#app',
template:`
<tab>
<tab-item name='1'>
<h1> This is tab item 1</h1>
</tab-item>
<tab-item name='2'>
<h2> This is tab item 2</h2>
</tab-item>
</tab>`,
ponents:{
tab,
tabItem
}
})
<script src="https://cdn.jsdelivr/npm/[email protected]/dist/vue.min.js"></script>
<div id="app"></div>