Vue 插槽

  • 环境准备(就一个 分类组件)

    ### Category.vue
    <template>
    	<div class="category">
    		<h3>xxx分类</h3>
    		<ul>
    			<li>11111</li>
    			<li>2222</li>
    			<li>3333</li>
    			<li>4444</li>
    		</ul>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Category'
    	}
    </script>
    
    <style scoped>
    	h3 {
    		color: orange;
    		text-align: center;
    	}
    	ul {
    		margin-left: 40px;
    	}
    	.category {
    		background: skyblue;
    		width: 200px;
    		height: 300px;
    	}
    </style>
    
    ### App.vue
    
    <template>
      <div class="container">
        <Category/> <!--使用3次-->
        <Category/>
        <Category/>
      </div>
    </template>
    
    <script>
    
    import Category from './components/slot/Category.vue'
    
    export default {
      name: 'App',
      components: {
    	  Category,
      }
      
    }
    </script>
    
    <style>
    	.container {
    		display: flex;
    		justify-content: space-around;
    		
    	}
    </style>
    
    
  • 把list数据放在App中,父组件向子组件传值,现在这么搞

    ### app.vue
    
    <template>
      <div class="container">
        <!--传两组数据: title && listData-->
        <Category title='foods' :listData="foods" />
        <Category title='films' :listData="films" />
        <Category title='games' :listData="games" />
      </div>
    </template>
    
    <script>
    
    import Category from './components/slot/Category.vue'
    
    export default {
      name: 'App',
      components: {
    	  Category,
      },
      data(){
    	  return {
    	  	  // 已准备好的数据
    		  foods:['banana','apple','pear'],
    		  films:['Speed Star','Harry Polly','Ship'],
    		  games:['cross fire','asktao','QQSpeed']
    	  }
      }
      
    }
    </script>
    
    <style>
    	......
    </style>
    
    ### Category.vue
    <template>
    	<!--渲染数据-->
    	<div class="category">
    		<h3>{{title}}</h3> 
    		<ul>
    			<li v-for="(item,index) in listData" :key="index">{{item}}</li>
    		</ul>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Category',
    		props:['title','listData'] // 接收
    	}
    </script>
    
    <style scoped>
    	......
    </style>
    
    
    - 至此,第一种效果完成,没什么可以讲的
    
  • 现在,改变需求,Foods列表项变成一张图片,Films列表项变成一个视频,Games不变

    可以这么干,添加一个判断条件即可

    ### Category.vue
    <template>
    	<div class="category">
    		<h3>{{title}}</h3>
    		<ul v-show="title !== 'Foods' && title !== 'Films' "> <!--添加判断-->
    			<li v-for="(item,index) in listData" :key="index">{{item}}</li>
    		</ul>
    		<!--两个判断-->
    		<img v-show="title == 'Foods' " src="https://img1.baidu.com/it/u=1294439722,116143144&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" >
    		<img  v-show="title == 'Films' " src="https://img2.baidu.com/it/u=2281104515,2554096595&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=714" >
    	</div>
    </template>
    
    
    
    
  • 如果项目需求少,这种方式没有问题.但缺点也很明显,一旦列表项多了,处理起来繁琐,得一个个去判断,很耗时间

    现在使用'Vue提供的插槽'来实现这一需求(插槽就是'占坑',等待组件的使用者[可以理解为父组件]填坑)

    ### App.vue
    
    <template>
      <div class="container">
    	
        <!--不再传listData-->
        <!-- <Category title='Foods' :listData="foods"> -->
        <!--Category组件里面包裹html元素,这里包裹图片-->
        <Category title='Foods'>
    		<img src="https://img1.baidu.com/it/u=1294439722,116143144&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" >
    	</Category>
    	
    	<!--这里包裹视频-->
        <Category title='Films'>
    		<video controls src="https://xxx.mp4"></video>
    	</Category>
    	
    	<!--这里渲染数据-->
        <Category title='Games'>
    		<ul>
    			<!-- <li v-for="(item,index) in listData" :key="index">{{item}}</li> -->
    			<li v-for="(item,index) in games" :key="index">{{item}}</li>
    		</ul>
    	</Category>
      </div>
    </template>
    
    <script>
    
    import Category from './components/slot/Category.vue'
    
    export default {
      name: 'App',
      components: {
    	  Category,
      },
      data(){
    	  return {
    		  foods:['banana','apple','pear'],
    		  films:['Speed Star','Harry Polly','Ship'],
    		  games:['cross fire','asktao','QQSpeed']
    	  }
      }
      
    }
    </script>
    
    <style>
    	.container {
    		display: flex;
    		justify-content: space-around;
    		
    	}
    	video {
    		width: 100%;
    	}
    </style>
    
    
    ### Category.vue
    <template>
    	<div class="category">
    		<h3>{{title}}</h3>
    		<!--如果父组件有传数据(html结构)过来,就展示数据;如果没传,就展示默认的数据-->
    		<!--占坑,等待接收数据-->
    		<slot>我是一些默认值(没传值的时候我就显示)</slot>
    		
    		
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Category',
    		// props:['title','listData'] // 不再接收 ListData
    		props:['title']
    	}
    </script>
    
    <style scoped>
    	......
    </style>
    
    
  • 至此,插槽的用途就是: 复用组件的时候,利用插槽可以渲染不同的数据结构(多数据的时候用起来很方便,数据少就使用If判断即可)

具名插槽演示(上面的基础上,取个名字而已,以便插入不同的数据结构)

### Category.vue
<template>
	<div class="category">
		<h3>{{title}}</h3>
		<!--就加了一个name属性-->
		<slot name="center">我是一些默认值(没传值的时候我就显示center)</slot>
		<slot name="footer">我是一些默认值(没传值的时候我就显示footer)</slot>
		
	</div>
</template>

### App.vue
<template>
  <div class="container">
	  
    <Category title='Foods'>
    	<!--把img标签结构插入center插槽-->
		<img slot="center" src="https://img1.baidu.com/it/u=1294439722,116143144&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" >
		<!--把a标签结构插入footer插槽-->
		<a slot="footer" href="">美食连接</a>
	</Category>
	
	<!--以下同理-->
    <Category title='Films'> 
		<video slot="center" controls src="...mp4"></video>
		<a slot="footer" href="">电影连接</a>
		<a slot="footer" href="">yyy连接</a>
	</Category>
	
    <Category title='Games'>
		<ul slot="center">
			<!-- <li v-for="(item,index) in listData" :key="index">{{item}}</li> -->
			<li v-for="(item,index) in games" :key="index">{{item}}</li>
		</ul>
		<a slot="footer" href="">游戏连接</a>
	</Category>
  </div>
</template>

  • 往一个插槽插入多个数据结构,可以这么搞

    - 写法一:多次使用
    
    ### App.vue
    ......
    <Category title='Films'>
    		......
    		<!--使用多次-->
    		<a slot="footer" href="">电影连接</a>
    		<a slot="footer" href="">yyy连接</a>
    </Category>
    
    - 写法二: 使用 <template slot>
    ......
    <Category title='Films'>
    		<template slot="footer"> <!--slot属性写在template里-->
    			<a href="">电影连接</a>
    			<a href="">yyy连接</a>
    		</template>
    </Category>
    
    - 写法三: 使用<template v-slot>,这是新的写法
    ......
    <Category title='Films'>
    		<!-- <template slot="footer"> -->
    		<template v-slot:footer>
    			<a href="">电影连接</a>
    			<a href="">yyy连接</a>
    		</template>
    </Category>
    

插槽作用域

  • 环境准备(Category渲染三个list,数据源要求在Category中,要求三个list的css样式不一样)
### App.vue

<template>
  <div class="container">
	  
	<!--渲染三组list-->  
	<Category title='Games'></Category>
	
	<Category title='Games'></Category>
	
	<Category title='Games'></Category>
	
  </div>
</template>

<script>

import Category from './components/slot/Category.vue'

export default {
  name: 'App',
  components: {
	  Category,
  },
  data(){
	  return {
		  // 不再返回数据
	  }
  }
  
}
</script>

<style scoped>
	.container {
		display: flex;
		justify-content: space-around;
		
	}
	
</style>

### Category
<template>
	<div class="category">
		<h3>{{title}}</h3>
		<!--渲染数据-->
		<ul>
			<li v-for="(game,index) in games" :key="index">{{game}}</li>
		</ul>
		
		
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title'],
		data(){
			  return {
				  games:['cross fire','asktao','QQSpeed'] // 数据放在这
			  }
		}
	}
</script>

<style scoped>
	h3 {
		font-weight: bold;
		background: orange;
		text-align: center;
	}
	ul {
		margin-left: 40px;
	}
	img {
		width: 100%;
	}
	.category {
		background: skyblue;
		width: 200px;
		height: 300px;
	}
	a {
		display: inline-block;
		width: 168px;
		text-align: center;
	}
	video {
		width: 100%;
	}
</style>


  • 直接上代码
### Cagegory.vue
<template>
	<div class="category">
		<h3>{{title}}</h3>
		<!--把games数据传给组件使用者(父组件)-->
		<slot :games="games">我是默认值</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title'],
		data(){
			  return {
				  games:['cross fire','asktao','QQSpeed'] // 数据源
			  }
		}
	}
</script>

<style scoped>
	......
</style>

### App.vue

<template>
  <div class="container">
	
	<Category title='Games'>
	    <!--想要接收数据,必须使用template包裹起来-->
	    <!--使用scope属性来接收子组件传过来的数据-->
	    <!--该数据是一个对象(字典)-->
		<template scope="dataObj">
			<!-- {{dataObj}} -->
			<!--dataObj.games对应子组件的list-->
			<ul>
				<li v-for="(game,index) in dataObj.games" :key="index">{{game}}</li>
			</ul>
		</template>
	</Category>
	
	<Category title='Games'>
	    <!--也可以使用es6的写法-->
		<template scope="{games}">
			<ol>
				<!--上面是遍历dataObj.games,这里遍历games即可-->
				<li v-for="(game,index) in games" :key="index">{{game}}</li>
			</ol>
		</template>
		
	</Category>
	
	<Category title='Games'>
		<!--还可以这么写,新的写法-->
		<template slot-scope="{games}">
			<h4 v-for="(game,index) in games" :key="index">{{game}}</h4>
		</template>
	</Category>
	
  </div>
</template>

<script>

import Category from './components/slot/Category.vue'

export default {
  name: 'App',
  components: {
	  Category,
  },
 
}
</script>

<style scoped>
	......
	
</style>

插槽小结

  • 作用: 让父组件可以向子组件指定位置插入html结构,也是组件之间的一种通信方式

    适用于父组件==>子组件

  • 分类: 默认插槽/具名插槽/作用域插槽

  • 使用方式1: 默认插槽

    - 父组件中:
    	<Category>
    		<div>html结构</div>
    	</Category>
    	
    - 子组件中:
    
    	<template>
    		<div>
    			<!--定义插槽-->
    			<slot>插槽默认内容</slot>
    		</div>
    	</template>
    
  • 使用方式2:具名插槽

    - 父组件中:
    	<Category>
    		<template slot="center">
                <div>html结构</div>
            </template>
    	</Category>
    	
    - 子组件中:
    
    	<template>
    		<div>
    			<!--定义插槽-->
    			<slot name="center">插槽默认内容</slot>
    		</div>
    	</template>
    
  • 使用方式3:作用域插槽

    - 理解: 数据在组件的自身,但根据数据生成的结构 需要组件使用者来决定(父组件决定).
      例如: games数据在Cagegory组件中,但使用数据所遍历出来的结构由App组件决定
    
    ### App.vue
    <Category title='Games'>
    		<template scope="dataObj">
    			<ul>
    				<li v-for="(game,index) in dataObj.games" :key="index">{{game}}</li>
    			</ul>
    		</template>
    	</Category>
    	
    	<Category title='Games'>
    		<template scope="{games}">
    			<ol>
    				<li v-for="(game,index) in games" :key="index">{{game}}</li>
    			</ol>
    		</template>
    		
    	</Category>
    	
    ### Category.vue
    <template>
    	<div class="category">
    		<h3>{{title}}</h3>
    		<slot :games="games">我是默认值</slot>
    	</div>
    </template>