首页 > 解决方案 > 为什么我的嵌套子组件不呈现视图?

问题描述

这是我的设置:

Vue 2.5.16 Veux Vue 路由器

我已经设置了一个简单的路由器视图,它在父组件中查找子组件,并且 url 结构是;

/文件夹/父-uuid/子-uuid

我在下面有一个父组件:

<template lang="html">
  <div>
    <!-- Articles -->
    <div class="flex-none w-100 mv3 gray"><p>Bookmarks({{ subFolders.contentDetails.articles.length }})</p></div>
    <div class="flex flex-row flex-wrap justify-start items-start">
      <div v-for="article in subFolders.contentDetails.articles" class="pointer article-item relative mb4">
        <a v-on:click.stop.prevent="checkFolder(article.uuid)" :class="[{highlight:selectedItems.includes(article.uuid)}, 'absolute w-100 h-100 left-0 top-0 highlight-area z-3']" href="#"><div class="absolute top-2" style="width: 18px;height: 18px;right: 3.05rem;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path v-if="selectedItems.includes(article.uuid)" fill="#ea792e" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/><path v-else fill="#ffffff" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/></svg></div></a>
        <a :class="[{active:selectedItems.includes(article.uuid)}, 'link']" v-bind:href="article.contentUrl">
          <div :class="[{active:selectedItems.includes(article.uuid)}, 'contentImage br3 overflow-hidden']">
            <img class="w-100" :src=article.titleImage data-flickity-lazyload="article.titleImage">
          </div>
          <div v-if="article.contentType == 'Behaviour'" class="black">
            <h4 class="f3">{{article.contentTitle}}</h4>
          </div>
          <div v-else class="black">
              <h4 class="f3">{{article.contentTitle}}</h4>
          </div>
          <div v-if="article.contentType == 'Behaviour'">
          </div>
          <div v-else class="content no-margin">
            <p class="f3">{{article.savedDate | moment("D MMM YYYY")}}</p>
          </div>
        </a>
      </div>
    </div>
    <!-- end v-if -->
    <div class="flex-none w-100 mt3 gray folders"><div class="ph5"><p>Subfolders({{ subFolders.subFolders.length }})</p></div></div>
    <div class="flex flex-row flex-wrap justify-start items-start">
      <div class="folder-item folder z-0 pointer">
        <div class="relative db h-100 bg-light-gray folder-new br3 mb4">       
          <div v-on:click="addFolder($event)" class="top aspect-ratio--object">
            <div class="dt w-100 h-100 no-margin showText pv6">
              <div class="dtc v-mid tc ">
                <svg class="dib v-mid icon-sprite" viewBox="0 0 38.89 38.89" width="40" height="40">
                 <circle cx="19.45" cy="19.45" r="19.45" fill="#ea792e"/><path stroke="#ffffff" stroke-width="3" d="M19.45 12.26v14.37M12.26 19.45h14.37" />
                </svg>
                <p>Create new folder</p>
              </div>
            </div>
          </div>
          <div class="bottom aspect-ratio--object">
            <div class="dt w-100 h-100 ">
              <div class="dtc v-mid tc no-margin showText pv6">
                <p>Explore the library and add to this folder</p>
                <form id="submit-folder" @submit.prevent="sendForm" class="mv3">
                  <input type="text" name="folderName" value="" placeholder="Add folder name in here..." v-model="folderName">
                  <input class="db center input-reset bg-orange b--none br2 white mv3 f3 pv3 ph4" style="font-family:'Open Sans',Helvetica Neue,sans-serif;" type="submit" name="Submit">
                </form>
              </div>
            </div>
          </div>
          <div style="visibility: hidden;" class="image-blocks br3 overflow-hidden relative flex flex-wrap">
            <div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-1.jpg" alt=""></div>
            <div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-2.jpg" alt=""></div>
            <div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-3.jpg" alt=""></div>
          </div>
        </div>
      </div>
      <div v-for="folder in subFolders['subFolders']" class="pointer folder folder-item relative">
        <a v-on:click.stop.prevent="checkFolder(folder.uuid)" :class="[{highlight:selectedItems.includes(folder.uuid)}, 'absolute w-100 h-100 left-0 top-0 highlight-area z-3']" href="#"><div class="absolute top-2" style="width: 18px;height: 18px;right: 3.05rem;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path v-if="selectedItems.includes(folder.uuid)" fill="#ea792e" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/><path v-else fill="#ffffff" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/></svg></div></a>
        <router-link :class="[{active:selectedItems.includes(folder.uuid)}, 'z-0 db relative link']" :to="`/folders/${folderUUID}/${folder.uuid}`">
          <div class="image-blocks br3 overflow-hidden relative">

              <div class="" v-for="image in folder.topThreeThumbnails">
                <img class="" :src=image>
              </div>

          </div>
        </router-link>
        <div :class="[{active:selectedItems.includes(folder.uuid)}, 'content']">
          <router-link :to="`/folders/${folder.uuid}`" :class="[{active:selectedItems.includes(folder.uuid)}, 'link black']">
            <h3>{{folder.folderName}}</h3>
            <p>{{folder.subFolderCount}} subfolders, {{folder.totalElements}} elements</p>
          </router-link>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
  name: 'subfolderListParent',
  data() {
    return {
      selected: [],
      selectedArticles: [],
      folder: '',
      folderName: '',
      folderUUID: this.$route.params.uuid
    }
  },
  mounted: function () {
    this.$store.dispatch('LOAD_SUBFOLDERS_LIST',this.$route.params.uuid)
    this.$store.dispatch('LOAD_FOLDERS_LIST')
    this.$store.state.selectedItems = [];
    this.$route.meta.title = this.$store.getters.openSubfolderTitle;
  },
  props: ['uuid','subfolderName'],
  computed: {
   ...mapState([
      'subFolders',
      'allFolders',
      'allArticles',
      'selectedItems',
      'selectedFolder'
    ]),
   ...mapGetters([
      'openSubfolderTitle',
    ])
  },
  methods: {
    checkFolder: function(item){
      this.$store.dispatch('SELECT_FOLDER',item)
    },
    addFolder: function(event){
      const target = event.currentTarget.parentNode,
            top    = target.children[0],
            bottom = target.children[1];
      if(bottom.classList.contains('active')){
        top.classList.remove('hidden');
        bottom.classList.remove('active');
      } else {
        top.classList.add('hidden');
        bottom.classList.add('active');
      }      
    },
    sendForm: function(event){
      this.$store.dispatch('CREATE_FOLDER',{'folder':this.folderName,'uuid':this.$route.params.uuid})
      this.folderName = '';
    },
    setTitle: function(){
      this.$route.meta.title = this.$store.getters.openSubfolderTitle;
    }
  },
  watch: {
    '$route': function(from, to) {
      this.$route.meta.title = this.$store.getters.openSubfolderTitle;
    }
  }
}
</script>

这是一个非常直接的模板,它在 vuex 等中查找一些数据并将其放入模板中。我遇到问题的地方是当我尝试从嵌套视图中加载子组件时。

路由器代码如下:

{
    path: '/folders/:uuid', 
    component: Folder,
    name: 'subfolderListParent', 
    props: true,
    meta: {
      bcDynamic: true,
      bcGetter: 'openSubfolderTitle', // <breadcrumb> will use this getter to get the user from vuex
      bcLinkText: state => state, // once we have the user, we use this function to format the LinkText dnynamically,
      bcLoadingText: 'Loading title...' // This will be shown while Data is loading
    },
    children: [
      {
        path: '/:parentUUID',
        props: true,
        components: {
          child: FolderChild
        },
        meta: {
          bcDynamic: true,
          bcGetter: 'openSubfolderTitle', // <breadcrumb> will use this getter to get the user from vuex
          bcLinkText: state => state, // once we have the user, we use this function to format the LinkText dnynamically,
          bcLoadingText: 'Loading title...' // This will be shown while Data is loading
        },
      }
    ]
  },

全部渲染的应用程序代码如下:

<template>
<div id="app" style="clear:both;min-height:100vh;" class="flex-l flex-row flex-wrap items-stretch">
  <transition name="slide-fade">
    <div class="fixed right-2 br2 ph4 pv3 bg-orange white z-3" v-on:click="$store.state.noteText.note = !$store.state.noteText.note" v-bind:key="$store.state.noteText.note" v-if="$store.state.noteText.note === true">
      <p class="white ma0 pa0 dib v-mid">{{$store.state.noteText.note}}</p>
    </div>
  </transition>
  <MainNav/>
    <div v-if="$route.path === '/from-canvas8'"></div>
    <div class="flex-none" v-else>
      <FolderActions/>
    </div>
  <div class="dib w-75-l w-100 v-top ph5 appWrapper">
    <router-view></router-view>   
  </div>
</div>
</template>

<script>
import MainNav from './components/MainNav.vue'
import FolderList from './components/FolderList.vue'
import FolderActions from './components/FolderActions.vue'
import ScrapbookHome from './components/ScrapbookHome.vue'
import { mapState } from 'vuex'
export default {
  components: {
    MainNav,
    FolderList,
    FolderActions,
    ScrapbookHome
  },
  name: 'scrapbook',
  data () {
    return {
      welcomeMessage: 'Story Navigation'
    }
  },
  methods: {

    updateScroll: function(){
      const nav = document.querySelector('.scrapbook-actions');
      const navTop = nav.offsetTop;

      function stickyNavigation() {

        if (window.scrollY >= navTop) {
          // nav offsetHeight = height of nav
          nav.classList.add('fixed');
        } else {
          nav.classList.remove('fixed');
        }
      }

      window.addEventListener('scroll', stickyNavigation); 
    },

  },
  mounted: function () {
    // this.$store.dispatch('LOAD_FOLDERS_LIST')
    //this.setNavHeight()
    this.$store.dispatch('GET_USER')
  },
  watch: {
    '$route': function(from, to) {
      //this.updateScroll()
      //this.setNavHeight()
    }
  }
} 
</script>

由于某种原因,<router-view />不会显示此子组件。

任何人都可以帮助我了解为什么这行不通吗?

标签: javascriptvue.jsvuejs2vue-componentvue-router

解决方案


当您嵌套路由时,每个路由部分都将安装在父路由的组件中。在您的情况下Folder,将安装在App并将FolderChild安装在Folder. 据我所知,嵌套路由总是如此,即使您没有为特定的子路由定义组件。据我所知,没有类似于将组件安装在适用的祖先而不是直接父级中的失败选项。

有两种方法可以解决这个问题。最简单的方法是不使用子路由,除非您在每个子路由周围都有共享样板。你会得到类似下面的东西。您可以使用注释在一定程度上组织您的路线。

export default [
  {
    // some other routes 
  },

  // Folder routes
  {
    path: '/folders/:uuid/:parentuuid',
    component: FolderChild,
    name: 'folderchild'
  },
  {
    path: '/folders/:uuid',
    component: Folder,
    name: 'folders'
  }
];

另一种方法是仍然使用嵌套路由,但是通过在父路由中放置一个具有单个路由视图的虚拟组件,您没有其他要渲染的内容。

// DummyView.vue
<template>
  <router-view />
</template>

<script>
// Used to fill parent routes that need to have children mounted in them
export default {
  name: 'dummy-view'
}
</script>

然后,您将“默认”路由移动到具有空路径的子路由,并使用此虚拟视图呈现父路由,并将两者都安装FolderFolderChild此虚拟视图中。

export default [
  {
    path: "/folders/:uuid",
    component: DummyView,
    children: [
      {
        path: "",
        component: Folder
      },
      {
        path: ":parentUUID",
        component: FolderChild
      }
    ]
  }
];

当您在一个组件中有多个(命名的)视图时,此解决方案将变得特别可怕,所有这些视图都需要为子视图填充。好的部分是,如果您有共享逻辑,您可以使用这些步骤逐步构建您想要的视图,而无需求助于这些仅用于安装子组件的地方的虚拟组件。

编辑 Vue 模板


推荐阅读