Vue插槽的认识和基本使用


写在前面:
加入我们的需求是设置一个通用性的按钮,按钮的内容是使用的时候来决定,我们可能会通过一个属性来设置按钮的文本,示例代码如下:

<div id="app">
        <my-com value="注册"></my-com>
        <my-com value="提交"></my-com>
        <my-com value="重置"></my-com>
    </div>

    <template id="mycom">
        <button>{{value}}</button>
    </template>
    <script>
        let MyCom = ({
            props:["value"],
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

此时我们在调用子组件的时候,设置不同的 value 值就会有不同的按钮.
在页面中的显示结果如下图所示:
在这里插入图片描述
上面的方法虽然也可以满足我们的需求,但其实通过我们学习html ,我们其实更习惯下面的这种写法:

    <my-com>注册</my-com>
    <my-com>提交</my-com>
    <my-com>重置</my-com>

想要这么写,我们就必须使用到 slot 插槽.在此之前,我们先来看一下编译作用域

1.编译作用域

通过前面的学习,其实我们已经了解到父子组件都有自己不同的模板和独立的作用域,每一个组件在实例化的时候作用域都是独立的.

那么我们动一动我们可爱的小脑瓜,就会想到假如我们在父组件中使用子组件时,子组件中嵌套的内容会在哪个作用域里编译呢??

那我们就来看看下面的例子:

 <div id="app">
        <my-com>
            {{msg}}
        </my-com>
    </div>

    <template id="mycom">
        <button>{{msg}}</button>
    </template>
    <script>
        let MyCom = ({
            data(){
                return{
                    msg:"hello world"
                }
            },
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                msg:"你好,世界"
            },
            components:{
                MyCom,
            }
        })
    </script>

此时组件中嵌套的 msg 究竟是在父组件中的作用域中编译还是在子组件作用域编译呢?这涉及到 msg 使用的是父组件的数据还是子组件的数据.

答案当然就是父组件中的数据了,但是现在我们还没有办法演示这段代码的结果,等后面我们介绍完插槽,再来看看这里的结果会是怎样.

组件作用域简单的来说就是:

  1. 父组件模板的内容在父组件中编译
  2. 子组件模板中的内容在子组件中编译

所以就会有一个比较常见的错误,父组件模板内使用内将一个指令绑定到子组件内部的属性/方法
示例代码如下:

   <div id="app">
        <my-com v-show = "isShow"></my-com>
    </div>

    <template id="mycom">
        <button>按钮</button>
    </template>
    <script>
        let MyCom = ({
            data(){
                return{
                   isShow:true,
                }
            },
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

报错信息如下图:
在这里插入图片描述
这样做会报错的原因是: isShow 是我们定义在子组件中的数据,父组件中并不能直接找到子组件的数据.

如果需要绑定到子组件作用域内的数据,就可以将指令绑定在子组件的根节点上.
如下:

<div id="app">
        <my-com></my-com>
    </div>
    <template id="mycom">
        <button  v-show = "isShow">按钮</button>
    </template>
    <script>
        let MyCom = ({
            data(){
                return{
                   isShow:false,
                }
            },
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

这样做就不会报错.

自己可以慢慢体会一下这两种写法,通过上面的例子,我们就可以对 编译作用域 有一点点的了解.

那么想想我们开篇提出的问题,在父组件模板中使用子组件时,子组件中嵌套的内容是属于父组件的编译,但是我们希望这个内容能在子组件中使用,此时我们就需要一种方式来混合父组件的内容与子组件自己的模板,这个过程被称为内容分发.

2.插槽的基本使用

原来我们在组件里面是不能写内容的,因为写了之后也会被模板整体替换,现在有了插槽之后,我们就可以在组件里定义内容了.

2.1 插入基本的文本内容

还是上面的例子,我们子组件中嵌套的文本在子组件模板中使用,我们就需要在子组件模板中使用插槽

 <div id="app">
        <my-com>按钮</my-com>
        <my-com>{{value}}</my-com>
    </div>
    <template id="mycom">
        <button >
            <slot></slot>
        </button>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                value:"注册"
            },
            components:{
                MyCom,
            }
        })
    </script>

使用插槽之后的结果:
在这里插入图片描述
通过这种方法,我们就可以很方便的在父组件中,通过slot插槽向子组件中分发内容
那么我们想想,既然可以插入文本内容,那是不是也可以在里面插入 DOM 标签呢?

2.2 插入DOM标签

示例代码如下:

 <div id="app">
        <my-com>
            <h3>你好啊!我是依古比古</h3>
        </my-com>
        <my-com>
            <h2>你好,我是玛卡巴卡</h2>
        </my-com>
    </div>
    <template id="mycom">
       <div>
           <slot></slot>
       </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

结果如下图:
在这里插入图片描述
通过上面的这段代码,我们发现插槽不仅仅可以插入文本内容,还可以插入 DOM 标签.
插槽的更多作用我们往下面继续看.

3.插槽的默认内容

我们并不是每一次使用子组件的时候都会给里面插入内容,如果我们不给里面插入内容,那么里面将会是什么也没有,所以我们需要设置默认内容.

如果我们需要在使用子组件时未插入内容时. 显示一段默认的内容,我们就可以将默认的内容嵌套在slot 标签中

这就是插槽的备用内容, 备用内容在子组件的作用域内编译. 并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。

还是使用按钮的那个例子,看下面的代码:

 <div id="app">
        <my-com></my-com>
        <my-com>
            {{value}}
        </my-com>
    </div>
    <template id="mycom">
        <button>
            <slot>我是默认内容</slot>
        </button>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                value:"注册"
            },
            components:{
                MyCom,
            }
        })
    </script>

显示的结果如下图:
在这里插入图片描述
这样做就算子组件没有给我们分发内容,我们也可以使用默认内容,不至于让显示的内容为空.

4.具名插槽

4.1 不使用具名插槽的问题

在我们使用组件的时候,有时候需要在不同的位置插入不同的内容,只有一个插槽很可能不能满足我们的需求,看示例:

我们的需求是:

  1. 子组件是一篇文章
  2. 父组件在调用子组件的时候需要给子组件插入标题,正文,时间等信息.

示例代码如下:

 <div id="app">
        <my-com>
            <h2>标题~~标题</h2>
            <p>希望第二天的我</p>
            <p>仍然对不同充满宽容</p>
            <p>继续对未知饱含敬畏</p>
            <span>2020612</span>
        </my-com>

    </div>
    <template id="mycom">
        <div class = "article">
            <div class="title">
                <slot></slot>
            </div>
            <div class="content">
                <slot></slot>
            </div>
            <div class="time">
                <slot></slot>
            </div>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                value:"注册"
            },
            components:{
                MyCom,
            }
        })
    </script>

在这里插入图片描述
这个时候我们可以看到,我们要插入的内容被重复插入了三次,此时每一个插槽都插入了所有的内容,不符合我们的预期.

那么我们要怎样才能将分发的内容制定到具体的插槽上呢???

这个时候就需要具名插槽

4.2 使用具名插槽

<slot> 元素有一个特殊的 name 属性, 使用 name 来进一步配置如何分发内容,每个插槽都可以有具体的名字.具名插槽将匹配内容片段中有对应的 slot 元素.

没有使用 name 属性的 slot 插槽被称为 匿名插槽,也可以称之为 默认插槽,我们在子组件中仍然可以有一个额匿名插槽,作为找不到匹配内容片段的备用插槽,若果没有默认插槽,这些找不到匹配的内容片段就会被抛弃.

看下面的示例代码:

   <div id="app">
        <my-com>
            <h2 slot = "title">标题~~标题</h2>
            <p>希望第二天的我</p>
            <p>仍然对不同充满宽容</p>
            <p>继续对未知饱含敬畏</p>
            <span slot = "time">2020612</span>
        </my-com>

    </div>
    <template id="mycom">
        <div class = "article">
            <div class="title">
                <slot name = "title"></slot>
            </div>
            <div class="content">
                <slot >这里是默认内容</slot>
            </div>
            <div class="time">
                <slot name = "time"></slot>
            </div>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

在这里插入图片描述
可以看到,完全就是我们想要的效果.分发的内容都正确的插到了插槽上面.

过上面的例子我们就知道了,slot 如果没有显示的使用 name 属性指定插槽的名字,那么slot默认有个名字default,默认插槽,如果在分发内容时,没有指定插槽,所有的内容都将默认插到默认插槽上.

5.作用域插槽

5.1 作用域插槽的理解和基本使用

通过前面的学习,我们已经知道,插槽的内容是最后在子组件模板上渲染的,那么就会在有些时候在分发内容中使用子组件中才会有的数据,这样的话我们该怎么办呢???

这个时候就要用到作用域插槽了.

作用域插槽是一种特殊类型的插槽,用作一个(能被传递数据的)可重用模板来代替已经渲染好的元素.

简而言之,就是利用slot 标签将子组件的数据传递到分发内中上,就像prop传递数据给组件一样

在父级中,具有特殊特性 slot-scope<template> 元素必须存在,表示它是作用域插槽的模板。slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 props 对象:

当我们使用作用域插槽后,slot-scope 属性接受的 props 是一个对象,我们来验证一下:

   <my-com>
      <template slot-scope="props">
        {{props}}
     </template>
   </my-com>

页面的显示结果如下图:
在这里插入图片描述
下面我们就来看看作用域插槽的使用案例:

 <div id="app">
        <my-com>
        <!-- slot-scope 不能直接在组件上使用,只能在DOM元素上使用-->
            <tempalte slot-scope="props">
                {{props.msg}}
                {{props.value}}
            </tempalte>
        </my-com>
    </div>

    <template id="mycom">
        <div>
            <slot :msg = "msg" :value="value"></slot>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
            data(){
                return{
                    msg:"今天天气真不错",
                    value:"你的衣服真好看"
                }
            }
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })

在 2.5.0+,slot-scope 能被用在任意元素或组件中而不再局限于 <template>

也就是说我们还能像下面这样写:

    <my-com>
         <div slot-scope = "props">
            <h2>{{props.msg}}</h2>
            <h3>{{props.value}}</h3>
        </div>
    </my-com>

结果如下图:
在这里插入图片描述

5.2 作用域插槽也可以解构使用

既然我们前面已经提到了props 中接收的数据是对象,那么我们就可以通过解构来使用数据,如下:

<my-com>
   <div slot-scope = "{msg,value}">
      <h2>{{msg}}</h2>
      <h3>{{value}}</h3>
   </div>
</my-com>

页面中的结果如下:
在这里插入图片描述
但是非常遗憾的是,作用域插槽和具名插槽的用法会在不久的将来被废弃.
那么之前我们提到的问题难道就没有办法解决了吗?
当然不是的,我们需要继续往下看.

6.v-slot 命令

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。

6.1 使用v-slot解决具名插槽问题

Vue 实现了一套内容分发的 API ,将 <slot>元素作为承载分发内容的出口。

在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称.

继续使用之前文章的案例:

  <div id="app">
        <my-com>
            <h2 v-slot:title>标题~~标题</h2>
            <p>希望第二天的我</p>
            <p>仍然对不同充满宽容</p>
            <p>继续对未知饱含敬畏</p>
            <span v-slot:time>2020612</span>
        </my-com>

    </div>
    <template id="mycom">
        <div class = "article">
            <div class="title">
                <slot name = "title"></slot>
            </div>
            <div class="content">
                <slot >这里是默认内容</slot>
            </div>
            <div class="time">
                <slot name = "time"></slot>
            </div>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

结果如下:
在这里插入图片描述
现在<template>元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot<template> 中的内容都会被视为默认插槽的内容。

如果你希望更明确一些,仍然可以在一个 中包裹默认插槽的内容:

 <template v-slot:default>
 </template>

6.2 使用v-slot处理作用域插槽问题

绑定在<slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的v-slot 来定义我们提供的插槽 prop 的名字:

<div id="app">
    <!-- 使用组件 -->
    <my-child >
        <template v-slot:default="props">
            <button>{{ props.text }}</button>
        </template>
    </my-child>
</div>

<!-- 组件模板 -->
<template id="mychild">
    <div>
        <slot :text="text"></slot>
    </div>
</template>

<script>
    //  组件选项对象
    let MyChild = {
        template: `#mychild`,
        data(){
            return {
                text: "提交"
            }
        }
    };

    //  实例中注册组件
    const vm = new Vue({
        el:"#app",
        components: {
            MyChild
        }
    })

</script>

显示结果:
center

6.3 作用域插槽的特殊使用

在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

<div id="app">
     <!-- 使用组件 -->
     <my-child v-slot:default="props">
            <button>{{ props.text }}</button>
     </my-child>
</div>

这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:

<div id="app">
     <!-- 使用组件 -->
     <my-child v-slot="props">
            <button>{{ props.text }}</button>
     </my-child>
</div>

这用这种简单语法的情况就是在组件中只有一个默认插槽,一但有多个插槽,请使用完整的语法

7.动态插槽

动态参数也可以用在v-slot上,用来定义动态的插槽名.
示例:

<div id="app">
    <!-- 使用组件 -->
    <my-child >
        <template v-slot:[head]>
            <h2>这是一篇介绍vue插槽的文章</h2>
        </template>
        <p>这是文章的第一段</p>
        <p>这是文章的第二段内容</p>
        <p>这是文章的第三段内容</p>
        <template v-slot:[food]>
            <span>202051</span>
        </template>
    </my-child>
</div>
<!-- 组件模板 -->
<template id="mychild">
    <div class="article">
        <div class="title">
            <slot name="title">这里是标题内容的插槽</slot>
        </div>
        <div class="contont">
            <slot>这里是默认插槽</slot>
        </div>
        <div class="time">
            <slot name="time">这里是时间的插槽</slot>
        </div>
    </div>
</template>

<script>

    //  组件选项对象
    let MyChild = {
        template: `#mychild`
    };

    //  实例中注册组件
    const vm = new Vue({
        el:"#app",
        data:{
            head:"title",
            food:"time"
        },
        components: {
            MyChild
        }
    })
</script>

8.具名插槽的缩写

v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header可以被重写为 #header

<div id="app">
    <!-- 使用组件 -->
    <my-child >
        <template #title>
            <h2>这是一篇介绍vue插槽的文章</h2>
        </template>
        <p>这是文章的第一段</p>
        <p>这是文章的第二段内容</p>
        <p>这是文章的第三段内容</p>
        <template #time>
            <span>202051</span>
        </template>    
    </my-child>
</div>

然而,和其它指令一样,该缩写只在其有参数的时候才可用。这意味着以下语法是无效的:

<my-child >
    <!-- 这种写法无效 -->
    <template #="props">
        <h2>这是一篇介绍vue插槽的文章</h2>
    </template>
</my-child>

如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:

<my-child >
    <!-- 这种写法有效,因为有指令参数 -->
    <template #deatule="props">
        <h2>这是一篇介绍vue插槽的文章</h2>
    </template>
</my-child>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章