作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。
<body>
<div id='app'>
<!-- 在根组件内是可以访问到根组件的数据的 -->
<!-- {{kobe.name}}->{{kobe.num}} -->
<!-- <cpn>{{kobe.name}}->{{kobe.num}}</cpn> -->
<!-- 在根组件中不能访问到子组件里面的数据 -->
<!-- {{allen.name}}->{{allen.num}} -->
<!-- <cpn>{{allen.name}}->{{allen.num}}</cpn> -->
<cpn></cpn>
</div>
<template id="demo">
<div>
<!-- 子组件可以访问到子组件的数据 -->
<h1>{{allen.name}}->{{allen.num}}</h1>
<!-- 同理slot中也可以访问到子组件里面的内容 -->
<slot>{{allen.name}}->{{allen.num}}</slot>
<!-- slot中也不能访问到根组件里面的数据 -->
<slot>{{kobe.name}}->{{kobe.num}}</slot>
</div>
</template>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
kobe: {
name: 'bryent',
num: 24
}
},
components: {
cpn: {
template: '#demo',
data() {
return {
allen: {
name: 'iverson',
num: 3
}
}
}
}
}
})
</script>
</body>
但是我们是想让父组件中可以访问到子组件的内容,比如这样:
<div id='app'>
<cpn>
<!-- 想让根组件可以访问到子组件信息 -->
<h2>allen:{{allen.name}}->{{allen.num}}</h2>
</cpn>
</div>
但是我们访问不到,如果想把这个数据给到子组件的slot,那么我们可以用props
<!-- 在父级作用域中,我们可以给 v-slot 带一个值来定义我们提供的插槽 prop 的名字 -->
<cpn>
<template v-slot:default='demo_allen'>
{{demo_allen}}
</template>
</cpn>
<!-- 绑定在 <slot> 元素上的特性被称为插槽 prop。 -->
<slot :my_allen='allen'>111</slot>
此时我们的{{demo_allen}}反馈的就是子组件里的allen的信息
一个不带 name 的 出口会带有隐含的名字“default”。
<slot :my_allen='allen'></slot>
<slot name='default' :my_allen='allen'></slot>
作用域插槽之独占默认插槽的缩写语法
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把v-slot 直接用在组件上:
<cpn>
<template v-slot:default='demo_allen'>
{{demo_allen}}
</template>
</cpn>
===>
<cpn>
<template v-slot='demo_allen'>
{{demo_allen}}
</template>
</cpn>
不带参数的v-slot 被假定对应默认插槽
<slot name='default' :my_allen='allen'>111</slot>
<!-- 再把tmac传给模板,模板都能接收到,因为name都是default -->
<slot name='default' :my_tmac='tmac'>111</slot>
<template v-slot:default='demo_allen'>
<h1>{{demo_allen}}</h1>
</template>
只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:,如果我们想和命名插槽一样不同的插槽对应不同的模板,我们也可以做name的设置
<!-- 插槽 -->
<slot name='default1' :my_allen='allen'>111</slot>
<slot name='default2' :my_tmac='tmac'>111</slot>
<!-- 插槽模板 -->
<template v-slot:default1='demo_allen'>
<h1>{{demo_allen}}</h1>
</template>
<template v-slot:default2='demo_tmac'>
{{demo_tmac}}
</template>
slot的数据传递其实也是通过prop传递的,只是我们没有去写slot的prop数据,父组件和子组件都是相对的
在我们的案例中,cpn是app的子组件,但是同时我们也可以把cpn看成slot的父组件
<body>
<div id='app'>
<cpn>
<!-- 在名称都是default的情况下,demo_allen写或不写没有意义,会用demo_tmac统一做数据接收 -->
<!-- <template v-slot:default='demo_allen'>
<h1>{{demo_allen}}</h1>
</template>
<template v-slot:default='demo_tmac'>
{{demo_tmac}}
</template> -->
<!-- 想让不同的模板接收不同的slot传来的数据 -->
<template v-slot:default1='demo_allen'>
<h1>{{demo_allen}}</h1>
</template>
<template v-slot:default2='demo_tmac'>
{{demo_tmac}}
</template>
</cpn>
<cpn :info='kobe'></cpn>
</div>
<template id="demo">
<div>
<!-- <slot :my_allen='allen'></slot>
<slot :my_tmac='tmac'></slot> -->
<slot name='default1' :my_allen='allen'></slot>
<slot name='default2' :my_tmac='tmac'></slot>
<!-- <div>{{info}}</div> -->
</div>
</template>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
kobe: {
name: 'bryent',
num: 24
}
},
components: {
cpn: {
props: {
info: {
type: String
}
},
template: '#demo',
data() {
return {
allen: {
name: 'iverson',
num: 3
},
tmac: {
name: 'tracy',
num: 1
}
}
}
}
}
})
</script>
</body>
我们要把cpn的数据传给slot,实际上就是把数据传给子组件,父传子:
父组件app->通过:info:kobe->子组件cpn->prop中的info来接收->在demo模板中就可以使用{{info}}
父组件cpn->通过:my_allen=‘allen’->子组件slot->子组件通过v-slot:default1='demo_allen’来接收->在demo_allen模板中使用
作用域插槽之解构插槽Prop
<cpn>
<!-- {"my_allen": { "name": "iverson", "num": 3 }} -->
<template v-slot:default1='demo_allen'>
<h1>未解构:{{demo_allen}}</h1>
<h1>未解构:{{demo_allen.my_allen.name}}</h1>
</template>
<template v-slot:default2='{my_tmac}'>
<h1>解构:{{my_tmac.name}}</h1>
</template>
</cpn>
<cpn :info='kobe'></cpn>
</div>
<template id="demo">
<div>
<slot name='default1' :my_allen='allen'></slot>
<slot name='default2' :my_tmac='tmac'></slot>
</div>
</template>
属性可以绑定多个,只需设置不同的bind即可
<body>
<div id='app'>
<cpn>
<template v-slot:default2="{my_tmac,vc}">
<h1>解构:tmac:{{my_tmac}}</h1>
<h1>解构:vc:{{vc}}</h1>
</template>
</cpn>
<cpn :info='kobe'></cpn>
</div>
<template id="demo">
<div>
<slot name='default2' :my_tmac='tmac' :vc='vince'></slot>
</div>
</template>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
kobe: {
name: 'bryent',
num: 24
}
},
components: {
cpn: {
template: '#demo',
data() {
return {
allen: {
name: 'iverson',
num: 3
},
tmac: {
name: 'tracy',
num: 1
},
vince: {
name: 'carter',
num: 15
}
}
}
}
}
})
</script>
</body>
简写
作用域插槽之具名插槽的缩写跟 v-on->@ 和 v-bind->:一样,v-slot也有缩写,即把参数之前的所有内容(v-slot:) 替换为字符#
<body>
<div id='app'>
<cpn v-slot:xxx='{give_msg}'>{{give_msg}}</cpn>
<cpn #xxx='{give_msg}'>{{give_msg}}</cpn>
<cpn v-slot:header>
<h1>789</h1>
</cpn>
<cpn #header>
<h1>abc</h1>
</cpn>
</div>
<template id="demo">
<div>
<div>123{{msg}}</div>
<slot name='xxx' :give_msg='msg'></slot>
<slot name='header'></slot>
</div>
</template>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: "#demo",
data() {
return { msg: '爱老虎油' }
}
}
}
})
</script>
</body>
动态组件上使用keep-alive
我们之前曾经在一个多标签的界面中使用 is 特性来切换不同的组件:
<body>
<div id='app'>
<button v-for='tab in tabs' :key='tab' @click=fn(tab)>{{tab}}</button>
<!-- <component is='tab_home'></component> -->
<component :is='now_tab'></component>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
tabs: ['home', 'corp', 'foods'],
now_tab: 'tab_corp'
},
methods: {
fn(tab) {
this.now_tab = 'tab_' + tab
}
},
components: {
tab_home: {
template: '<div>家的组件</div>'
},
tab_corp: {
template: '<div>公司的组件</div>'
},
tab_foods: {
template: '<div>食堂的组件</div>'
}
}
})
</script>
</body>
当在这些组件之间切换的时候,我们有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。
<body>
<div id='app'>
<button v-for='tab in tabs' :key='tab' @click=fn(tab)>{{tab}}</button>
<!-- <component is='tab_home'></component> -->
<component :is='now_tab'></component>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
tabs: ['home', 'corp', 'foods'],
now_tab: 'tab_corp'
},
methods: {
fn(tab) {
this.now_tab = 'tab_' + tab
},
},
components: {
tab_home: {
template: '<div>家的组件</div>'
},
tab_corp: {
template: '<div>公司的组件<div @click="color_change">我的颜色</div></div>',
methods: {
color_change(e) {
e.target.style.color = 'red'
}
}
},
tab_foods: {
template: '<div>食堂的组件</div>'
}
}
})
</script>
</body>
你会注意到,如果你在公司组建里点击div将颜色变为红色,当我们切换到另外一个组件之后,然后再切换回公司组件,div的文字颜色是会复原成默认颜色,.这是因为你每次切换新标签的时候,Vue 都创建了一个新的组件实例。
重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 元素将其动态组件包裹起来。
<keep-alive>
<component :is='now_tab'></component>
</keep-alive>
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
<script>
Vue.component('cpn', (resolve, reject) => {
setTimeout(() => {
resolve({
template: '<div>123</div>'
})
}, 1000)
})
const app = new Vue({
el: '#app',
})
</script>
如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是为了演示用的,如何获取组件取决于你自己
我们在通过一个更直观的例子了解一下:
app.vue组件
<template>
<div>
<h1>我是app.vue</h1>
<button @click="fn">按钮</button>
<div v-if="show">
<Main />
</div>
</div>
</template>
<script>
import Main from "./main";
export default {
data() {
return {
show: false,
};
},
methods: {
fn() {
this.show = !this.show;
},
},
components: {
Main,
},
};
</script>
<style>
</style>
main.vue组件
<template>
<div>
<h2>我是子组件</h2>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
以上我们把main作为组件传入app中使用,用v-if来决定main组件是否显示,默认为false模拟不需要展示,此时我们打开网页:
我们发现不管是显示或者影藏,我们的页面都没有重新加载main这个组件,说明在app在加载显示的时候我们的main组件已经加载进去了,不需要重新加载了,但是有些时候这个子组件如果不是用户点击,我们是不需要打开的,并且也就不需要加载,而不是,不管用户看不看,我们都加载,这样是很浪费资源的.
此时我们可以使用异步加载,只有在用户需要查看时,我们才选择加载main组件
components: {
Main: () => import("./main.vue"),
},
此时我们可以发现在不展示时,同样没有多余的加载项,但是当我们点击按钮改为显示事,新加载了一个0.js,这个js文件就是我们的main组件
这里的异步组件工厂函数也可以返回一个如下格式的对象:
const asyncmain = () => ({
component: import("./main"),
// 异步组件加载过程中的loading组件
loading: Loading,
// 加载失败时的组件
error: Error,
// 展示加载时组件的延时时间,默认是200ms
delay: 200,
// 如果提供了超时时间,且组件加载也超时了
// 则使用加载失败时使用的组件,默认值是Infinity
timeout: 3000,
});