Я пытаюсь написать пользовательский компонент. И надеюсь, что я смогу использовать его так
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
}
})
Все идет хорошо, пока вы не нажмете кнопку. Я получил сообщение об ошибке с консоли:
[Vue warn]: You may have an infinite update loop in a component render function.
found in
---> <Tab>
<Root>
Я пробовал много способов решить эту проблему, однако неудача всегда выигрывала конкурс отладки.
Как я могу победить эту проблему?
Вот мой код:
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])
}
}
Или любые другие способы реализации этого компонента?
Большое спасибо за то, что помогли мне выйти из ада.
Спасибо за ваш ответ, мои друзья. Я уверен, что я получаю бесконечную ошибку цикла от консоли, и мой код работает не так, как ожидалось. Я не думаю, что использование vnode
- хороший способ реализовать этот компонент. Однако это лучшее решение, которое я могу найти.
Эта tab
компонент должна определять своего дочернего tabItem
, имя которого - tabItem
, который также является компонентом. И tab
может извлечь некоторые данные из tabItem
. В моем случае, tab
извлечет name
свойство tabItemn
, который будет использоваться для создания кнопок для переключения контента. Нажмите кнопку, чтобы перейти к соответствующему контенту, который является телом tabItem
. В моем коде это currenView
.
Как и в известной библиотеке пользовательского интерфейса, 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>
Мне нужно реализовать один такой компонент, но мой будет более простым. Чтобы узнать, как это сделать, я прочитал его исходный код. Может быть, нет хорошего способа фильтровать дочерние компоненты. В источнике они используют это для фильтрации компонента 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);
}
Я знаю, что я могу использовать $children
для доступа к своим дочерним компонентам, но это не гарантирует порядок дочерних компонентов, чего я не хочу. Потому что порядок переключения очень важен. Подробные сообщения о vnode
не содержатся в документе. Мне нужно прочитать источник.
Поэтому, прочитав исходный код Vue, я написал свой код, как это, тогда у меня возникла проблема.
Я, наконец, не решил эту ошибку и признал, что использование такого редкого кода отстой. Но я не знаю других решений. Поэтому мне нужны вы, ребята, помогите.
Благодарю.
Вы не должны изменять свои данные в функции рендеринга, это неправильно
this.items = this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
потому что он будет продолжать повторный рендеринг, вот рабочий пример для вашего кода, я просто удалил свойство items
из данных и добавил новое вычисляемое свойство items
которое возвращает узлы tab-item
.
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
}
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<div id="app"></div>
this.currentView = this.items[0]
касается изменения содержимого при нажатии кнопок, вам нужно изменить this.currentView = this.items[0]
, изменить индекс для динамического выбора необходимого содержимого,