Vue 中的无限更新循环

uca*_*cag 2 javascript vue.js vue-component

我正在尝试编写一个自定义组件。希望我能像这样使用它

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>`,
    components:{
        tab,
        tabItem
    }
})
Run Code Online (Sandbox Code Playgroud)

一切顺利,直到您单击按钮。我从控制台收到一个错误:

[Vue warn]: You may have an infinite update loop in a component render function.

found in

---> <Tab>
       <Root>
Run Code Online (Sandbox Code Playgroud)

我尝试了很多方法来解决这个问题,然而,失败总是赢得了调试竞赛。

我怎样才能解决这个问题?

这是我的代码:

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.componentOptions.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])
    }
}
Run Code Online (Sandbox Code Playgroud)

或者任何其他方式来实现这个组件?

非常感谢你帮助我走出地狱。

谢谢朋友们的回复。我很确定我从控制台收到一个无限循环错误并且我的代码没有按预期工作。我也不认为 usingvnode是实现这个组件的好方法。但是,这是我能想到的最佳解决方案。

此组件 -tab应检测其名称为 的子tabItem组件,它也是一个组件。并且tab可以从中提取一些数据tabItem。就我而言,tab将提取 的name属性tabItemn,该属性将用于生成用于切换内容的按钮。点击按钮可以切换到相关内容,即正文tabItem。在我的代码中,它是currenView.

就像著名的 UI 库Element 一样,它的tab组件可以这样使用:

<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>
Run Code Online (Sandbox Code Playgroud)

我需要实现一个这样的组件,但我的会更简单。为了学习如何做到这一点,我阅读了它的源代码。也许没有过滤子组件的好方法。在源代码中,他们使用它来过滤el-tab-pane组件:

  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);
  }
Run Code Online (Sandbox Code Playgroud)

源代码

我知道我可以$children用来访问它的子组件,但这样做并不能保证子组件的顺序,这不是我想要的。因为切换按钮的顺序很重要。vnode文档中不包含有关的详细消息。我需要阅读源代码。

因此,在阅读了 Vue 的源代码后,我像这样编写了我的代码,然后我遇到了我的问题。

我终于没有解决这个错误并承认使用这种罕见的代码很糟糕。但我不知道其他解决方案。所以我需要你们的帮助。

谢谢。

Moh*_*_PH 6

你不应该在渲染函数中改变你的数据,这是错误的

this.items = this.$slots.default.filter( node => {
  return /tab-item/.test(node.tag)
})
Run Code Online (Sandbox Code Playgroud)

因为它会不断重新渲染,这是您的代码的一个工作示例,我只是items从数据中删除了属性并添加了items返回tab-items 节点的新计算属性。

this.items = this.$slots.default.filter( node => {
  return /tab-item/.test(node.tag)
})
Run Code Online (Sandbox Code Playgroud)
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.componentOptions.propsData

        }
    },
    computed: {
    	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>`,
    components:{
        tab,
        tabItem
    }
})
Run Code Online (Sandbox Code Playgroud)