Vue渲染顺序

时间:2018-09-20 01:34:02

标签: javascript vue.js vuejs2 vue-component

我正在使用vue创建选项卡组件,并注意到以下行为。

如果我这样创建我的组件:

<tabs-component>
  <tab-component :title="1">
    <h1>1</h1>
    <p>Lorem ipsum dolor sit amet.</p>
  </tab-component>
  <tab-component :title="2">
    <h1>2</h1>
    <p>Lorem ipsum dolor sit amet.</p>
  </tab-component>
</tabs-component>

救生钩按以下顺序触发:

  • 创建标签选项卡
  • 创建了标签组件
  • 已安装标签组件
  • 已安装标签组件

但是,这似乎是正确的行为,如果我不是在手动声明插槽,而是使用v-for指令,假设我正在处理来自以下服务的某些数据:

<tabs-component>
  <tab-component v-for="(type, index) in beerTypes" :key="type.slug" :title="type.slug">
    <h1>{{ index }}</h1>
    <p>Lorem ipsum dolor sit amet.</p>
  </tab-component>
</tabs-component>

救生钩按以下顺序触发:

  • 创建标签表组件
  • 已安装标签表组件
  • 创建了标签组件
  • 已安装标签组件

v-for指令似乎与生命挂钩的触发顺序混乱。这引起了我的麻烦,因为我在已安装的救生钩上触发了selectTab方法来选择所选的默认选项卡,但在触发时尚未安装选项卡。

有人知道我如何以正确的顺序正确地监听事件,或者等待子组件准备好以选择默认面板吗?

这是我的其余代码供参考:

App.vue

<template>
  <main>
    <h1>Discover beer styles</h1>
    <tabs-component>
      <tab-component v-for="(type, index) in beerTypes" :key="type.slug" :title="type.slug">
        <h1>{{ index }}</h1>
        <p>Lorem ipsum dolor sit amet.</p>
      </tab-component>
    </tabs-component>
  </main>
</template>

<script>
  import axios from 'axios';
  import TabsComponent from './components/tabs/Tabs.vue';
  import TabComponent from './components/tabs/Tab.vue';

  export default {
    components: {
      'tabs-component': TabsComponent,
      'tab-component': TabComponent,
    },

    data() {
      return {
        beerTypes: null,
        selectedType: null
      }
    },

    filters: {
      capitalize: function(value) {
        if (!value) return '';

        return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
      }
    },

    mounted() {
      axios.get('/data/data.json')
        .then((response) => {
          this.beerTypes = response.data;
          this.selectedType = this.beerTypes[0].slug;
        })
        .catch((error) => console.log(error));
    },

    methods: {
      selectActiveType: function(selected) {
        this.selectedType = selected;
      }
    }
  }
</script>

Tabs.vue

<template>
  <div class="tabs-component">
    <div>
      <ul>
        <li v-for="(tab, index) in tabs" :class="{'is-active': tab.isActive}" :key="index">
          <a href="#" @click="selectTab(index)">{{ tab.title }}</a>
        </li>
      </ul>
    </div>
    <div>
      <slot/>
    </div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        tabs: [],
        activeTabIndex: 0
      }
    },

    created() {
      this.tabs = this.$children;
    },

    mounted() {
      this.selectTab(this.activeTabIndex);
    },

    methods: {
      selectTab(index) {
        this.tabs.forEach(function(tab) {
          tab.isActive = (this.tabs.indexOf(tab) === index);
        }.bind(this));
      }
    }
  };
</script>

Tab.vue

<template>
  <section v-show="isActive">
    <slot/>
  </section>
</template>

我很高兴有人能抽出时间来帮助我。

谢谢!

1 个答案:

答案 0 :(得分:1)

我认为使用Vue: provide/inject是这里的一种解决方案。

对于您的情况,如果添加了新的<tab-component>(您说啤酒类型将从后端服务进行更新),除非在hook = {{内获取最新的<tabs-component>,否则this$children不会知道1}}。

如果使用提供/插入,我们可以让updated()将其实例注册到<tab-component>的一个数据属性中。这样<tabs-components>就不必在意添加新标签了,并且可以直接访问<tabs-component>的实例。

此外,您还应该实现一个<tab-component>,实例销毁后,将其从_unRegister的data属性中删除,下面的演示使用hook = {<tabs-component>来实现。

下面是一个dmeo:

beforeDestory
Vue.productionTip = false
Vue.component('tabs-component', {
  template: `  <div class="tabs-component">
    <div>
      <h3>Selected Tab: {{activeTabIndex}}</h3>
      <ul>
        <li v-for="(tab, index) in tabs" :class="{'is-active': tab.isActive}" :key="index">
          <a href="#" @click="selectTab(index)">{{ tab.title }}</a>
        </li>
      </ul>
    </div>
    <div>
      <slot/>
    </div>
  </div>`,
  provide () {
    return {
      _tabs: this
    }
    },
    data() {
      return {
        tabs: [],
        activeTabIndex: 0
      }
    },
    mounted() {

    },

    methods: {
      selectTab(selectedIndex) {
        this.activeTabIndex = selectedIndex
        this.tabs.forEach((tab, tabIndex) => {
          tab.isActive = selectedIndex === tabIndex
        })
      },
      _registerTab(tab) {
        this.tabs.push(tab)
        this.selectTab(this.activeTabIndex)
      },
      _unRegisterTab(unRegTab) {
        this.tabs = this.tabs.filter(tab=>{
          return unRegTab !== tab
        })
        this.selectTab(0)
      }
    }
})

Vue.component('tab-component', {
  template: `  <section v-show="isActive" :title="title">
    <slot/>
  </section>`,
	inject: {
    _tabs: {
      default () {
        console.error('tab-component must be child of tab-components')
      }
		}
  },
  props: ['title'],
  mounted () {
    this._tabs._registerTab(this)
  },
  beforeDestroy(){
    this._tabs._unRegisterTab(this)
  },
  data () {
    return {
      isActive: false
    }
  }
})

new Vue({
  el: '#app',
  data () {
    return {
      beerTypes: []
    }
  },
  methods: {
    pushData () {
      setTimeout(()=>{
        this.beerTypes.push({'slug': 'test' + this.beerTypes.length})
      }, 1000)
    },
    popData () {
      this.beerTypes.pop()
    }
  }
})
.is-active {
  background-color: green;
}

并在Vue指南中的提供/注入上注意此注释:

  

注意:Provide和Inject绑定不是反应性的。这是   故意的。但是,如果您向下传递观察对象,则属性   在那个物体上确实保持反应性。