Vue使用插槽分发内容
- 1、简介
- 2、编译作用域
- 3、默认内容
- 4、命名插槽
- 5、作用域插槽
- 6、动态插槽名
1、简介
组件是当作自定义元素使用的,元素可以有属性和内容,通过组件定义的prop接收属性值,可以解决属性问题,那么内容呢?这可以通过<slot>
元素解决。此外,插槽(slot)也可以作为父子组件之间通信的另一种实现方式。
下面是一个简单的自定义组件。
Vue.component('greeting',{
template:'<div><slot></slot></div>'
})
在组件模板中,<div>
元素内部使用了一个<slot>
元素,可以把这个元素理解为占位符。
使用该组件的代码如下:
<greeting>Hello,Vue.js</greeting>
<greeting>
元素给出了内容,在渲染组件时,这个内容会置换组件内部的<slot>
元素。最终渲染结果如下:
<div>Hello,Vue.js</div>
2、编译作用域
如果想通过插槽向组件传递动态数据。例如:
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<greeting>Hello {{name}}</greeting>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app=Vue.createApp({ });
app.component('greeting',{
data(){
return{
name:'zhangsan'
}
},
template:'<div><slot>Hello Java</slot></div>'
})
app.mount('#app');
</script>
</body>
</html>
那么要清楚一点,name是在父组件的作用域下解析的,而不是在greeting组件的作用域下解析。换句话说,在greeting组件内部定义的name数据属性,在这里是访问不到的,name必须在父组件的data选项中。这就是编译作用域的问题。 总之,父组件模板中的所有内容都是在父级作用域内编译;子组件模板中的所有内容都是在子作用域内编译。 正确的代码如下:
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<greeting>Hello {{name}}</greeting>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app=Vue.createApp({
data(){
return{
name:'lisi'
}
}
});
app.component('greeting',{
template:'<div><slot>Hello Java</slot></div>'
})
app.mount('#app');
</script>
</body>
</html>
渲染结果:
3、默认内容
在组件内部使用<slot>
元素时,可以给该元素指定一个默认内容,以防止组件的使用者没有给该组件传递内容。例如,一个用于提交按钮的组件<submit-button>
的模板内容如下:
<button type="submit">
<slot>提交</slot>
</button>
在父级组件中使用<submit-button>
,但是不提供插槽内容。代码如下所示:
<submit-button></submit-button>
那么该组件的渲染结果如下:
代码语言:javascript复制<button type="submit">
提交
</button>
如果父级组件提供了插槽内容,代码如下所示:
代码语言:javascript复制<submit-button>注册</submit-button>
那么组件的渲染结果如下:
代码语言:javascript复制<button type="submit">
注册
</button>
4、命名插槽
在开发组件时,可能需要用到多个插槽。<slot>
元素有一个name属性,可以定义多个插槽。没有使用name属性的<slot>
元素具有隐含名称default。
在向命名插槽提供内容的时候,在一个<template>
元素上使用v-slot指令,并以v-slot参数的形式指定插槽的名称。
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"><!-- 可用#替换v-slot: -->
<base-layout>
<template #header>
<h1>这是页头部分</h1>
</template>
<template v-slot:default>
<section>这是一个正文段落</section>
<section>另一个正文段落</section>
</template>
<template v-slot:footer>
<h3>这是页脚部分</h3>
</template>
</base-layout>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app=Vue.createApp({});
app.component('base-layout',{
template:`
<div>
<header><slot name="header"></slot></header>
<main><slot></slot></main>
<footer><slot name="footer"></slot></footer>
</div>
`
});
app.mount('#app');
</script>
</body>
</html>
渲染结果:
要注意,v-slot指令只能在<template>
元素或组件元素上使用。在组件元素上使用有一些限制,见下一小节。
与v-bind和v-on指令一样,v-slot指令也有缩写语法,即用"#“号替换"v-slot”。
5、作用域插槽
前面介绍过,在父级作用域下,在插槽内容中是无法访问到子组件的数据属性的,但有时候需要在父级的插槽内容中访问子组件的数据,为此,可以在子组件的<slot>
元素上使用v-bind指令绑定一个prop。代码如下所示:
app.component('MyButton',{
data(){
return{
titles:{
login:'登录',
register:'注册'
}
}
},
template:`
<label><slot name="other" :label="titles"> {{labels.login}}</slot></label>
<button>
<slot :values="titles">
{{titles.login}}
</slot>
</button>
`
});
这个按钮的名称可以在“登录”和“注册”之间切换,为了让父组件可以访问titles,在<slot>
元素上使用v-bind指令绑定一个values属性,成为插槽prop,这个prop不需要在props选项中声明。
在父级作用域下使用该组件时,可以给v-slot指令一个值来定义组件提供的插槽prop的名字。代码如下所示:
<my-button>
<template v-slot:default="slotProps">
{{slotProps.values.register}}
</template>
</my-button>
因为<my-button>
组件内的插槽是默认插槽,所以这里使用其隐含的名字default,然后给出一个名字slotProps,这个名字可以随便取,代表的是包含组件内所有插槽prop的一个对象,然后就可以利用这个对象访问组件的插槽prop,values prop是绑定到titles数据属性上的,所以可以进一步访问titles的内容。最后的渲染结果为:<button>注册</button>
如果出现多个插槽,就应该始终为所有的插槽使用完整的基于<template>
的语法。代码如下所示:
<my-button>
<template v-slot:default="slotProps">
{{slotProps.values.register}}
</template>
<template v-slot:other="otherProps">
{{otherProps.label.register}}
</template>
</my-button>
作用域插槽的内部工作原理是将插槽内容包装到传递单个参数的函数中来工作。代码如下所示:
代码语言:javascript复制function(slotProps){
//插槽内容
}
这意味着v-slot实际上可以是任何能够作为函数定义的参数的JavaScript表达式。所以在支持ES6的环境下,可以使用解构语法提取特定的插槽prop。提取插槽prop的时候也可以重命名。代码如下所示:
代码语言:javascript复制<my-button>
<template v-slot:default="{values:titles}"><!-- 重命名 -->
{{titles.register}}
</template>
<template v-slot:other="{label}">
{{label.register}}
</template>
</my-button>
6、动态插槽名
动态指令参数也可以用在v-slot指令上,定义动态的插槽名。代码如下所示:
代码语言:javascript复制<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
dynamicSlotName需要在父级作用域下能够正常解析,如存在对应的数据属性或计算属性。如果是在DOM模板中使用,还要注意元素属性名的大小写问题。