Vue.js 组件化开发

       人面对复杂问题的处理方式:

  • 任何一个人处理信息的逻辑能力都是有限的
  • 所以,当面对一个非常复杂的问题时,我们不太可能—次性搞定一大堆的内容。
  • 但是,我们人有一种天生的能力,就是将问题进行拆解。如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。

       组件化也是类似的思想:

  • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
  • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

       组件化是Vue.js中的重要思想:

  • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
  • 任何的应用都会被抽象成一颗组件树。

在这里插入图片描述
       组件化思想的应用:

  • 有了组件化的思想,我们在之后的开发中就要充分的利用它。
  • 尽可能的将页面拆分成一个个小的、可复用的组件。
  • 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

1.1 组件化的实现和使用步骤

       组件的使用分成三个步骤:

  1. 创建组件构造器
  2. 注册组件
  3. 使用组件

在这里插入图片描述

<!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">
        <!-- 3、使用组件 -->
        <my-cpn></my-cpn>
        <my-cpn></my-cpn>
    </div>
    <script src="../js/vue.js"></script>
    <script>
        // 1、创建组件构造器
        const cpnC = Vue.extend({
            template: `
                <div>
                    <h2>我是标题</h2>
                    <p>我是内容,哈哈哈哈</p>
                    <p>我是内容,呵呵呵呵</p>
                </div>`,
        });
        // 2、注册组件
        Vue.component('my-cpn', cpnC);
        const app = new Vue({
            el: '#app', 
            data: {    
            }
        })
    </script>
</body>
</html>

在这里插入图片描述

组件注册步骤解析

  1. Vue.extend():

    • 调用Vue.extend()创建的是一个组件构造器。
    • 通常在创建组件构造器时,传入template代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML代码。
    • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
  2. Vue.component():

    • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
    • 所以需要传递两个参数:1、注册组件的标签名,2、组件构造器
  3. 组件必须挂在在某个Vue实例下,否则它不会生效

    • 下面的实例中,我们使用了三次<my-cpn></my-spn>
    • 但是第三次并没有生效

在这里插入图片描述

1.2 全局组件和局部组件

  • 全局组件:在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
  • 局部组件:局部组件只有在所注册的Vue实例中才能使用,并不能在其他未进行注册的Vue实例中使用。

<!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">
        <!-- 3、使用组件 -->
        <my-cpn></my-cpn>
        <cpn></cpn>
    </div>
    <hr>
    <div id="app2">
        <my-cpn></my-cpn>
        <cpn></cpn>
    </div>
    <script src="../js/vue.js"></script>
    <script>
        // 1、创建组件构造器
        const cpnC = Vue.extend({
            template: `
                <div>
                    <h2>我是标题</h2>
                    <p>我是内容,哈哈哈哈</p>
                    <p>我是内容,呵呵呵呵</p>
                </div>`,
        });
        // 2、注册组件(全局组件,意味着可以在多个 Vue 实例下面使用)
        Vue.component('my-cpn', cpnC);
        const app = new Vue({
            el: '#app', 
            data: {    
            },
            // 局部组件
            components: {
                cpn: cpnC
            }
        })
        const app2 = new Vue({
            el: '#app2', 
            data: {    
            }
        })
    </script>
</body>
</html>


       在上面的代码中,我们创建了两个Vue实例对象appapp2,同时我们使用注册了全局组件my-cpn以及在app内注册了局部组件cpn。我们在appapp2中使用上面的两个组件,可以看到局部组件cpn并没有在app2中生效,符合我们的预期。

在这里插入图片描述

1.3 父组件和子组件

       在前面我们看到了组件树:

  • 组件和组件之间存在层级关系
  • 而其中一种非常重要的关系就是父子组件的关系

       我们来通过代码看这种层级关系:


<!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">
        <cpn2></cpn2>
    </div>
    <script src="../js/vue.js"></script>
    <script>
        // 1、创建第一个组件构造器(子组件)
        const cpnC1 = Vue.extend({
            template: `
                <div>
                    <h2>我是标题1</h2>
                    <p>我是内容,哈哈哈哈</p>
                </div>`,
        });
        // 2、创建第二个组件构造器(父组件)
        const cpnC2 = Vue.extend({
            template: `
                <div>
                    <h2>我是标题2</h2>
                    <p>我是内容,呵呵呵呵</p>
                    <cpn1></cpn1>
                </div>`,
            components: {
                cpn1: cpnC1,
            },
        });
        const app = new Vue({
            el: '#app', 
            data: {    
            },
            components: {
                cpn2: cpnC2,
            }
        })
    </script>
</body>
</html>


在这里插入图片描述
在这里插入图片描述

1.4 注册组件语法糖

       通过语法糖的方式,我们可以跳过Vue.extend()方法,直接通过Vue.component()方法实现组件的注册。


<!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">
        <!-- 3、使用组件 -->
        <cpn1></cpn1>
        <cpn2></cpn2>
    </div>
    <script src="../js/vue.js"></script>
    <script>
        // 1、全局组件注册语法糖
        //  1.1 创建组件构造器
        // const cpn1 = Vue.extend({
        //     template: `
        //         <div>
        //             <h2>我是标题</h2>
        //             <p>我是内容,哈哈哈哈</p>
        //             <p>我是内容,呵呵呵呵</p>
        //         </div>`,
        // });
        //  1.2 注册组件
        // Vue.component('cpn1', cpn1);
        Vue.component('cpn1', {
            template: `
                <div>
                    <h2>我是标题</h2>
                    <p>我是内容,哈哈哈哈</p>
                    <p>我是内容,呵呵呵呵</p>
                </div>`,
        });
        const app = new Vue({
            el: '#app', 
            data: {    
            },
            components: {
                cpn2: {
                    template: `
                        <div>
                            <h2>我是标题</h2>
                            <p>我是内容,哈哈哈哈</p>
                            <p>我是内容,呵呵呵呵</p>
                        </div>`,
                }
            }
        })
    </script>
</body>
</html>

在这里插入图片描述

1.5 组件模板抽离的写法

       在IDE中,写组件的template时,由于其是一个字符串,所以没有代码提示,写起来非常不方便。并且template的代码直接耦合在Vue的代码中,非常的凌乱。为此,我们可以将组件的模板抽离出来,有两种抽离的方式。


<!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">
        <cpn1></cpn1>
        <hr>
        <cpn2></cpn2>
    </div>
    <!-- 1、第一种写法 -->
    <script type="text/x-template" id="cpn">
        <div>
            <h2>我是标题</h2>
            <p>我是内容,哈哈哈哈</p>
        </div>
    </script>
    <!-- 2、第二种写法 -->
    <template id="cpn2">
        <div>
            <h2>我是标题</h2>
            <p>我是内容,哈哈哈哈</p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        Vue.component('cpn1', {
            template: '#cpn',
        });
        Vue.component('cpn2', {
            template: '#cpn2',
        });
        const app = new Vue({
            el: '#app', 
            data: {    
            },
        })
    </script>
</body>
</html>


在这里插入图片描述
       通常,我们采用的是方式二。可以看到,组件模板抽离后的代码看起来非常简洁。

1.6 组件数据存放

       组件是一个单独功能模块的封装:

              这个模块有自己的 HTML 模板,也应该有属于自己的数据data

       组件中的数据是保存在哪里呢?顶层的Vue实例中吗?

              我们可以通过下面的代码测试,组件中能不能直接访问Vue实例中的data


<!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">
        <cpn></cpn>
    </div>
    <template id="a">
        <div>
            <h2>{{ message}}</h2>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        Vue.component('cpn', {
            template: '#a',
        });
        const app = new Vue({
            el: '#app', 
            data: {
            	message: 'Hello'
            },
        })
    </script>
</body>
</html>


在这里插入图片描述
       我们发现并不能访问,而且即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例会变的非常臃肿。

       结论:Vue 组件应该有自己保存数据地方

       组件自己的数据存放在哪里呢?

  • 组件对象也有一个data属性(也可以有methods属性)
  • 只是这个data属性必须是一个函数
  • 而且这个函数返回一个对象,对象内部保存着数据

<!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">
        <cpn></cpn>
    </div>
    <template id="a">
        <div>
            <h2>{{ title }}</h2>
            <p>我是内容,哈哈哈哈</p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        Vue.component('cpn', {
            template: '#a',
            data() {
                return {
                    title: 'Hello'
                }
            }
        });
        const app = new Vue({
            el: '#app', 
            data: {    
            },
        })
    </script>
</body>
</html>


在这里插入图片描述

1.7 父子组件通信

       子组件是不能引用父组件或者Vue实例的数据的。

       但是,在开发中,往往一些数据确实需要从上层产地到下层:

  • 比如在一个页面中,我们从服务器请求到了很多的数据
  • 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示
  • 这个时候,并不会让子组件再次发送要给网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)

       如果进行父子组件间的通信呢?官方提到

  • 通过props向子组件传递数据
  • 通过事件向父组件发送消息
    在这里插入图片描述

       Vue实例同样是组件,所以Vue实例与子组件通信和父组件与子组件通信的过程是一样的。

父组件向子组件传递数据

       在子组件中,使用选项props来声明需要从父组件接收到的数据。

       props的值有两种方式:

  • 方式一:字符串数组,数组中的字符串就是传递时的名称。
  • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。

<!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">
       <cpn :cmovies="movies" :cmessage="message"></cpn>
       <!-- 如果要传递的是 data 属性,则必须用 v-bind -->
       <cpn cmovies="movies" cmessage="message"></cpn>
    </div>
    <template id="a">
        <div>
            {{cmessage}}
            <ul>
                <li v-for="movie in cmovies">{{movie}}</li>
            </ul>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const cpn = {
            template: '#a',
            props: ['cmovies', 'cmessage'],
        }
        const app = new Vue({
            el: '#app', 
            data: {
                message: 'Hello',
                movies: ['a', 'b', 'c', 'd']   
            },
            components: {
                cpn
            }
        })
    </script>
</body>
</html>


在这里插入图片描述
       在上面,我们的props选项是使用一个数组。

       我们说过,处理数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。

       验证都支持如下数据类型:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
<!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">
       <cpn :cmovies="movies" ></cpn>
       <!-- 错误语法:必须用 v-bind -->
       <!-- <cpn cmovies="movies" cmessage="message"></cpn> -->
    </div>
    <template id="a">
        <div>
            {{cmessage}}
            <ul>
                <li v-for="movie in cmovies">{{movie}}</li>
            </ul>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const cpn = {
            template: '#a',
            // props: ['cmovies', 'cmessage'],
            props: {
                // 1、类型检查
                // cmovies: Array,
                // cmessage: String
                // 2、提供一些默认值
                cmessage: {
                    type: String,
                    default: 'aaaa',
                    required: false
                },
                cmovies: {
                    type: Array,
                }
            }
        }
        const app = new Vue({
            el: '#app', 
            data: {
                message: 'Hello',
                movies: ['a', 'b', 'c', 'd']   
            },
            components: {
                cpn
            }
        })
    </script>
</body>
</html>

子组件向父组件传递数据

       props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。

       我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。

       什么时候需要自定义事件呢?

  • 当子组件需要向父组件传递数据时,就要用到自定义事件了。
  • 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。

       自定义事件的流程︰

  • 在子组件中,通过$emit('事件名', [参数])来触发事件。
  • 在父组件中,通过v-on来监听子组件事件。
<!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">
       <cpn v-on:itemclick="cpnClick"></cpn>
    </div>
    <!-- 子组件模板 -->
    <template id="a">
        <div>
            <button v-for="item in categories" @click="btnClick(item)">
                {{item.name}}
            </button>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const cpn = {
            template: '#a',
            data() {
                return {
                    categories: [
                        {id: 1, name: '热门推荐'},
                        {id: 2, name: '手机数码'},
                        {id: 3, name: '家用家电'},
                        {id: 4, name: '电脑办公'},
                    ]
                }
            },
            methods: {
                btnClick(item) {
                    // 产生事件:自定义事件
                    this.$emit('itemclick', item);
                }
            }
        }
        const app = new Vue({
            el: '#app', 
            data: {
            },
            components: {
                cpn
            },
            methods: {
                cpnClick(item) {
                    console.log(item)
                }
            }
        })
    </script>
</body>
</html>

       上面代码的含义如下:

  1. 父组件监听itemclick事件,该事件产生时,执行cpnClick方法
  2. 点击子组件内部的按钮时,执行btnClick方法
  3. btnClick方法内部,产生itemclick事件
  4. 由此便完成了子组件向父组件的数据传递

在这里插入图片描述

1.8 组件访问

       有时候,我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。

  • 父组件访问子组件:使用$children$refs
  • 子组件访问父组件:使用$parent

父组件访问子组件

children

  • this.$children是一个数组类型,它包含所有子组件对象
<!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">
        <cpn></cpn>
        <button @click="btnClick">点击</button>
    </div>
    <template id="cpn">
        <div>
            <h2>我是子组件</h2>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
            },
            methods: {
                btnClick() {
                    console.log(this.$children);
                    this.$children[0].showMessage();
                }
            },
            components: { 
                cpn: {
                    template: '#cpn',
                    methods: {
                        showMessage() {
                            console.log("showMessage")
                        }
                    }
                }
            },
        })
    </script>
</body>
</html>

在这里插入图片描述
       可以看到,我们通过this.$children成功访问了cpn组件,并且调用了cpn所拥有的methods。当然也可以访问cpn的其他属性。

refs

       通过this.$children存在这样一个问题,便是我们要访问一个组件时,可能并不知道其索引,需要进行遍历查找。

       而this.$refs的作用相当于给组件一个id,这样我们便能根据这个id直接找到对应的组件了。


<!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">
        <cpn ref="aaa"></cpn>
        <button @click="btnClick">点击</button>
    </div>
    <template id="cpn">
        <div>
            <h2>我是子组件</h2>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
            },
            methods: {
                btnClick() {
                    console.log(this.$refs);
                    console.log(this.$refs.aaa);
                    this.$refs.aaa.showMessage();
                }
            },
            components: { 
                cpn: {
                    template: '#cpn',
                    methods: {
                        showMessage() {
                            console.log("showMessage")
                        }
                    }
                }
            },
        })
    </script>
</body>
</html>


在这里插入图片描述

子组件访问父组件

  • this.$parent获得父组件
  • this.$root获得根组件

<!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">
        <cpn></cpn>
    </div>
    <template id="cpn">
        <div>
            <h2>我是子组件</h2>
            <button @click="btnClick">点击</button>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
            },
            methods: {
                showMessage() {
                    console.log("showMessage")
                }
            },
            components: { 
                cpn: {
                    template: '#cpn',
                    methods: {
                        btnClick() {
                            console.log(this.$parent);
                            this.$parent.showMessage();

                            // 访问根组件
                            console.log(this.$root);
                        }
                    }
                }
            },
        })
    </script>
</body>
</html>


在这里插入图片描述

1.9 插槽

       slot翻译为插槽:

  • 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。口插槽的目的是让我们原来的设备具备更多的扩展性。
  • 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。

       组件的插槽︰

  • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
  • 让使用者可以决定组件内部的一些内容到底展示什么。

       例子∶移动网站中的导航栏。

  • 移动开发中,几乎每个页面都有导航栏。
  • 导航栏我们必然会封装成一个插件,比如nav-bar组件。一旦有了这个组件,我们就可以在多个页面中复用了。

       如何去封装这类的组件呢?

  • 它们也很多区别,但是也有很多共性。
  • 如果,我们每一个单独去封装一个组件,显然不合适∶比如每个页面都返回,这部分内容我们就要重复去封装。
  • 但是,如果我们封装成一个,好像也不合理∶有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。

       如何封装合适呢?抽取共性,保留不同

  • 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
  • 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
  • 是搜索框,还是文字,还是菜单。由调用者自己来决定。

       这就是为什么我们要学习组件中的插槽slot的原因。

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>
    <!-- 
        1、插槽的基本使用 <slot></slot>
        2、插槽的默认值 <slot>button</slot>
        3、如果有多个值,同时放入到组件进行替换时,一起作为替换元素
     -->
    
    <div id="app">
        <cpn><button>按钮</button></cpn>
        <cpn><div>哈哈哈</div></cpn>
        <cpn><a href="">百度</a></cpn>
        <cpn></cpn>
    </div>
    <template id="cpn">
        <div>
            <h2>我是组件</h2>
            <div>我是组件,哈哈哈</div>
            <slot></slot>
            <!-- 可以给 slot 默认值,如果父组件没有插入,则使用默认值 -->
            <!-- <slot><button>按钮</button></slot> -->
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
            },
            components: {
                cpn: {
                    template: '#cpn',
                }
            }
        })
    </script>
</body>
</html>


在这里插入图片描述
       在上面的代码中,我们在组件中使用<slot></slot>创建插槽,当我们使用该组件时,组件标签内的内容会自动替换掉<slot></slot>

具名插槽

       当子组件的功能复杂时,子组件的插槽可能并非是一个。

  • 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
  • 那么,外面在给插槽插入内容时,如何区分插入的是哪—个呢?
  • 这个时候,我们就需要给插槽起一个名字

       如何使用具名插槽呢?

  • 非常简单,只要给slot元素一个name属性即可<slot name='myslot'> </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">
        <cpn>
            <span slot="center">标题</span>
        </cpn>
    </div>
    <template id="cpn">
        <div>
            <slot name="left"><span>左边</span></slot>
            <slot name="center"><span>中间</span></slot>
            <slot name="right"><span>右边</span></slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
            },
            components: {
                cpn: {
                    template: '#cpn',
                }
            }
        })
    </script>
</body>
</html>


在这里插入图片描述
       在上面的代码中,我们在组件中创建了三个具名插槽,分别为leftcenterright.

       我们使用该组件时,通过slot="center"指定要将内容插入到center的插槽。

作用域插槽

       在了解作用域插槽之前,我们需要西安了解一个概念:编译作用域

       官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:

       我们来考虑下面的代码是否最终是可以渲染出来的︰

  • <my-cpn v-show="isShow"></my-cpn>中,我们使用了isShow属性。
  • isShow属性包含在组件中,也包含在Vue实例中。

       答案︰最终可以渲染出来,也就是使用的是Vue实例的属性。

       为什么呢?

  • 官方给出了一条准则∶父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
  • 而我们在使用<my-cpn v-show="isShow"></my-cpn>的时候,整个组件的使用过程是相当于在父组件中出现的。
  • 那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
  • 因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。

<!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">
        <cpn v-show="isShow"></cpn>
    </div>
    <template id="cpn">
        <div>
            <h2>我是组件</h2>
            <div>我是组件,哈哈哈</div>
            <button v-show="isShow">按钮</button>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
                isShow: true
            },
            components: {
                cpn: {
                    template: '#cpn',
                    data() {
                        return {
                            isShow: false
                        }
                    }
                }
            }
        })
    </script>
</body>
</html>


在这里插入图片描述

       作用域插槽是slot—个比较难理解的点,而且官方文档说的又有点不清晰。

       这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会︰

  • 父组件替换插槽的标签,但是内容由子组件来提供

       我们先提一个需求︰

  • 子组件中包括一组数据:比如: pLanguages: ['JavaScript', 'Python', 'Swift', 'Go','C++']
  • 需要在多个界面进行展示:
    • 某些界面是以水平方向展示的
    • 某些界面是以列表形式展示的
    • 某些界面直接展示一个数组
  • 内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
  • 利用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">
        <cpn></cpn>
        <cpn>
            <!-- 目的是获取子组件中的 pLanguages -->
            <template slot-scope="slot">
                <span v-for="item in slot.data">{{item}} - </span>
            </template>
        </cpn>
    </div>
    <template id="cpn">
        <div>
            <h2>我是组件</h2>
            <slot :data="pLanguages">
                <ul>
                    <li v-for="item in pLanguages">
                        {{item}}
                    </li>
                </ul>
            </slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
            },
            components: {
                cpn: {
                    template: '#cpn',
                    data() {
                        return {
                            pLanguages: ['Python', 'Java', 'Go', 'C++']
                        }
                    }
                }
            }
        })
    </script>
</body>
</html>

在这里插入图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值