带插槽的动态组件

时间:2017-09-28 09:12:09

标签: vue.js vuejs2

如何在父组件中使用动态组件的命名槽?

滑块组件采用动态幻灯片组件数组:

<slider :slides="slides" />

每张幻灯片都有一个名为的插槽,其中包含滑块使用的内容:

<template>
<div class="slide">
  <div slot="main">Slide 1 Main</div>
  <div slot="meta">Slide 1 Meta</div>
</div>
</template>

滑块现在应该使用这些插槽,如下所示:

<template>
<div class="slider">
  <div class="slider__slide" v-for="slide in slides">
    <component :is="slide">
      <div class="slider__slide__main">
        <slot name="main" /><!-- show content from child's slot "main" -->
      </div>
      <div class="slider__slide__meta">
        <slot name="meta" /><!-- show content from child's slot "meta" -->
      </div>
    </component>
  </div>
</div>
</template>

但是<component>忽略了它的内部内容,因此忽略了插槽。

例:
https://codepen.io/anon/pen/WZjENK?editors=1010

如果这不可能,是否有另一种方法可以创建一个滑块,从幻灯片组件中获取HTML内容而不关心其内容?

2 个答案:

答案 0 :(得分:2)

通过将main / meta部分拆分为自己的组件,您可以相对轻松地使用渲染功能将它们分割成您想要的部分。

console.clear()

const slide1Meta = {
  template:`<div>Slide 1 Meta</div>` 
}
const slide1Main = {
  template: `<div>Slide 1 Main</div>`
}
const slide2Meta = {
  template:`<div>Slide 2 Meta</div>` 
}
const slide2Main = {
  template: `<div>Slide 2 Main</div>`
}

Vue.component('slider', {
  props: {
    slides: {
      type: Array,
      required: true
    }    
  },
  render(h){
    let children = this.slides.map(slide => {
      let main = h('div', {class: "slider__slide__main"}, [h(slide.main)])
      let meta = h('div', {class: "slider_slide_meta"}, [h(slide.meta)])
      return h('div', {class: "slider__slide"}, [main, meta])
    })
    return h('div', {class: "slider"}, children)
  }
});


new Vue({
  el: '#app',
  data: {
    slides: [
      {meta: slide1Meta, main: slide1Main}, 
      {meta: slide1Meta, main: slide2Main}
    ]
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
  <slider :slides="slides" />
</div>

<script type="text/x-template" id="slide1-template">
  <div class="slide">
    <div slot="main">Slide 1 Main</div>
    <div slot="meta">Slide 1 Meta</div>
  </div>
</script>

<script type="text/x-template" id="slide2-template">
  <div class="slide">
    <div slot="main">Slide 2 Main</div>
    <div slot="meta">Slide 2 Meta</div>
  </div>
</script>

答案 1 :(得分:1)

实际上,动态component元素中的广告位确实可以工作。我一直在尝试解决相同的问题,并在lovely little example的CodePen上找到了Patrick O'Dacre。帕特里克(Patrick)在他的代码中做了很多有用的评论,在此我逐字粘贴以便于后人。我省略了在CodePen上可以找到的CSS。

const NoData = {
  template: `<div>This component ignores the data completely. <p>But there are slots!</p><slot></slot> <slot name="namedSlot"></slot></div>`
  // In this component, I just ignore the props completely
}

const DefaultMessage = {
  template: `<div>This component will show the default msg: <div>{{parentData.msg}}</div>`,
  // this component won't have posts like the Async Component, so we just ignore it
  props: ['parentData']
}

const CustomMessage = {
  template: `<div>This component shows a custom msg: <div>{{parentData.msg}}</div>`,
  // this component won't have posts like the Async Component, so we just ignore it
  props: ['parentData']
}

const Async = {
  template: `
      <div>
        <h2>Posts</h2>
        <p>{{parentData.msg}}</p>
        <section v-if="parentData.posts.length > 0">
          <ul>
            <li class="postInfo" v-for="post in parentData.posts">
              <div class="postInfo__title">
                <strong>Title:</strong> {{post.title}}
              </div>
            </li>
          </ul>
        </section>
      </div>
  `,
  props: ['parentData']
}

/* Children should only affect parent properties via an EVENT (this.$emit) */
const ChangeMessage = {
  template: `
    <div>
      <p>Type here to change the message from the child component via an event.</p>
      <div><input type="text" v-model="message" @input="updateDateParentMessage" /></div>
    </div>
  `,
  data() {
    return {
      // initialize our message with the prop from the parent.
      message: this.parentData.msg ? this.parentData.msg : '' 
    }
  },
  props: ['parentData'],
  /* Need to watch parentData.msg if we want to continue
  to update this.message when the parent updates the msg */
  watch: {
    'parentData.msg': function (msg) {
      this.message = msg  
    }
  },
  methods: {
    updateDateParentMessage() {
      this.$emit('messageChanged', this.message)
    }
  }
}


const Home = {
  template: `
    <section>
      <div class="wrap">
        <div class="right">
          <p><strong>Change the current component's message from the Home (parent) component:</strong></p>
          <div><input type="text" v-model="dataForChild.msg" /></div>
          <p><strong>Important!</strong> We do not change these props from the child components. You must use events for this.</p>
        </div>
      </div>
      <div class="controls">
        <button @click="activateComponent('NoData')">No Data</button>
        <button @click="activateComponent('DefaultMessage')">DefaultMessage</button>

        <button @click="activateComponent('CustomMessage', {posts: [], msg: 'This is component two'})">CustomMessage</button>

        <button @click="getPosts">Async First</button>
        <button @click="activateComponent('ChangeMessage', {msg: 'This message will be changed'})">Change Msg from Child</button>
        <button @click="deactivateComponent">Clear</button>
      </div>
      <div class="wrap">

        <div class="right">
          <h2>Current Component - {{currentComponent ? currentComponent : 'None'}}</h2>

          <!-- ATTN: Uncomment the keep-alive component to see what happens 
              when you change the message in ChangeMessage component and toggle
              back and forth from another component. -->

          <!-- <keep-alive> -->
            <component :is="currentComponent" 
                       :parentData="dataForChild" 
                        v-on:messageChanged="updateMessage">
              <div class="slotData">This is a default slot</div>
              <div slot="namedSlot" class="namedSlot">This is a NAMED slot</div>
              <div slot="namedSlot" class="namedSlot"><p>Here we pass in the message via a slot rather than as a prop:</p>{{dataForChild.msg}}</div>
            </component>
          <!-- </keep-alive> -->
        </div>
      </div>
    </section>
  `,
  data() {
    return {
      currentComponent: false,
      /* You don't NEED to put msg and posts here, but
      I prefer it. It helps me keep track of what info
      my dynamic components need. */
      dataForChild: {
        // All components:
        msg: '', 

        // Async Component only
        posts: [] 
      }
    }
  },
  methods: {
    /**
     * Set the current component and the data it requires 
     *
     * @param {string} component The name of the component
     * @param {object} data The data object that will be passed to the child component
     */
    activateComponent(component, data = { posts: [], msg: 'This is a default msg.'}) {

      this.dataForChild = data

      this.currentComponent = component
    },
    deactivateComponent() {
      this.dataForChild.msg = ''
      this.currentComponent = false
    },
    /* Hold off on loading the component until some async data is retrieved */
    getPosts() {
      axios.get('https://codepen.io/patrickodacre/pen/WOEXOX.js')
        .then( resp => {
        const posts = resp.data.slice(0, 10) // get first 10 posts only.

        // activate the component ONLY when we have our results
        this.activateComponent('Async', {posts, msg: `Here are your posts.`})
      })
    },
    /** 
     * Update the message from the child
     *
     * @listens event:messageChanged
     * @param {string} newMessage The new message from the child component
     */
    updateMessage(newMessage) {
      this.dataForChild.msg = newMessage
    }
  },
  // must wire up your child components here
  components: {
    NoData,
    CustomMessage,
    DefaultMessage,
    Async,
    ChangeMessage
  }
} 


const routes = [
  { path: '/', name: 'home', component: Home}
]

const router = new VueRouter({
  routes
})

const app = new Vue({
  router
}).$mount("#app")

html

<div id="app">
  <h1>Vue.js Dynamic Components with Props, Events, Slots and Keep Alive</h1>
  <p>Each button loads a different component, dynamically.</p>
  <p>In the Home component, you may uncomment the 'keep-alive' component to see how things change with the 'ChangeMessage' component.</p>

  <nav class="mainNav">
  </nav>
  <!-- route outlet -->
  <!-- component matched by the route will render here -->
  <section class="mainBody">
    <router-view></router-view>
  </section>

</div>