Bootstrap

Vue学习笔记(二)组件化和模块化

前言

本章记录一下vue的组件化和模块化,这是vue比较重要的知识点。会在实际开发中大量的使用到,因此,会比较详细的记录。
如需转载,请标明出处。
若发现问题,还望指正。

组件化

什么是组件化

组件化是一种思想,将一个复杂的页面分解成多个小的组件,每个组件都是独立的个体,互不影响,这样管理和维护起来就非常的容易。
组件化也是vue.js中重要的思想。
1、它提供了一种抽象,我们开发出一个个的组件来构成我们的应用。
2、每一个应用都可以抽象成一颗组件树。

1、基础使用

分为三个步骤:创建组件构造器 -> 注册组件 -> 使用组件

    <div id="app">
        <!-- 步骤三:使用组件 -->
        <cpn></cpn>
    </div>
    <script src="../js/vue.js"></script>
    <script>
        // 步骤一:创建组件构造器
        const comp = Vue.extend({
            template: '<div>这是组件</div>'
        });
        // 步骤二:注册组件
        Vue.component('cpn', comp);
        const app = new Vue({
            el: '#app'
        });
    </script>

注意:前两个步骤必须放在创建vue实例之前,否者无效。
组件必须挂载在某个vue实例下,否者无效。

2、全局组件和局部组件

如上的使用方式即为全局组件的使用,它可以在任何的vue实例下使用,然后平时开发时通常都是使用局部组件。
局部组件的使用如下,在需要的vue中添加属性components,并进行注册,此时,只有在该vue实例里是可以使用该组件,其他地方皆不可使用。

    <div id="app">
        <!-- 步骤三:使用组件 -->
        <cpn></cpn>
    </div>
    <script src="../js/vue.js"></script>
    <script>
        // 步骤一:创建组件构造器
        const comp = Vue.extend({
            template: '<div>这是组件2</div>'
        });
        // 步骤二:注册组件
        // Vue.component('cpn', comp);
        const app = new Vue({
            el: '#app',
            components: {
                //cpn:组件的标签名
                //创建的组件名。步骤一的对象
                cpn: comp
            }
        });

3、语法糖和模板抽离

我们实际开发时是不会如上的方式去开发的,过于复杂了,我们会将代码进行简化,因为比较简单,此处直接上代码。

    <div id="app">
        <cpn></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件
        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            components: {
                cpn: {
                    template: '#mytemp'
                }
            }
        });
    </script>

模板抽离除了这种方式,还可以使用script标签的方式,此处不详细说明。
template标签里必须有且只有一个根标签

4、组件的data为什么是函数

本来是记住的,但是记录的时候就又忘记了,因此这里记录下来。
组件,是会复用的,如果将data设计成属性,那么,他们将指向同一个对象,一旦一个地方改变,复用的地方都会改变,这是有问题的。
如果设计成函数,函数是有作用域,因此每个组件都有自己的作用域,返回的都是自己的对象。不会影响其他组件的值。

5、父子组件

5.1 父子组件

父子组件即父组件和子组件,当一个组件在另一个组件里时,外层的组件即为里层组件的父组件,里层组件为子组件
vue实例为root组件,即根组件,也是它里面组件的父组件。

5.2 父子组件通信

5.2.1 父传子值

父传子值主要使用到了props属性,它是一个数组,里面每一个值都是字符串,如下。props里的值为val,在父组件里使用时,需要用到v-bind。

    <div id="app">
        <cpn :val='vals'></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件 {{val}}
        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                vals: '这是父传子值'
            },
            components: {
                cpn: {
                    template: '#mytemp',
                    props: ['val']
                }
            }
        });
    </script>
5.2.2 子传父值

需要注意两点:
1、按钮上的属性是@click,而非@onclick
2、this.$emit(‘bclick’, “hello”)需要加this

<div id="app">
        <cpn @bclick="btnClick"></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件
            <button @click="ccc">按钮</button>
        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                vals: '这是父传子值'
            },
            methods: {
                btnClick(val) {
                    console.log(val);
                }
            },
            components: {
                cpn: {
                    template: '#mytemp',
                    methods: {
                        ccc() {
                            this.$emit('bclick', "hello")
                        }
                    }
                }
            }
        });
    </script>
5.2.3 父访问子

父组件访问子组件的内容,如方法、属性等。
children:

children这个很少使用,通常我们获取子组件的内容不使用它,一般在获取所有子组件时才使用

<div id="app">
        <button @click="btnClick">父访问子</button>
        <cpn></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件
        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                vals: 'parent'
            },
            methods: {
                btnClick(val) {
                    console.log(this.$children[0].va);
                    this.$children[0].ccc();
                }
            },
            components: {
                cpn: {
                    template: '#mytemp',
                    data() {
                        return {
                            va: 'son'
                        }
                    },
                    methods: {
                        ccc() {
                            console.log("this is child");
                        }
                    }
                }
            }
        });
    </script>
son
this is child

这里打印出this.$children[0]的值可见子组件的内容。如下
在这里插入图片描述

refs:
既然不适用children,那么肯定有其他的代替,没错,就是refs,它的使用可以规避掉children的弊端(children需要下标去获取对应的组件,一旦动态改变的组件的顺序等就无法准确获取到相应的子组件)。
它的使用需要在父组件的子组件标签中添加ref=“”的属性,类似于id的意思,需要唯一。然后再通过this.$refs获取对应的子组件。

<div id="app">
        <button @click="btnClick">按钮</button>
        <cpn></cpn>
        <cpn ref="aa"></cpn>
        <cpn ref="bb"></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件

        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                vals: 'parent'
            },
            methods: {
                btnClick() {
                    this.$refs.aa.c1();
                }
            },
            components: {
                cpn: {
                    template: '#mytemp',
                    data() {
                        return {
                            va: 'son'
                        }
                    },
                    methods: {
                        c1() {
                            console.log("子组件1");
                        },
                        c2() {
                            console.log("子组件2");
                        },
                        c3() {
                            console.log("子组件3");
                        }
                    }
                }
            }
        });
    </script>
5.2.4 子访问父

子组件访问父组件的内容,如方法、属性等。
子访问父可以使用 p a r e n t ,它的使用与 parent,它的使用与 parent,它的使用与children类似。这里就不再赘述。并且它使用的很少,因为如果使用了它就意味着于父组件的耦合度高了,不利于开发。
还有另一个属性$root,它可以获取到顶级父元素,也就是vue实例的内容。不过它同样使得的比较少,此处也不再多说。

6、插槽slot

6.1 插槽的基本使用

6.1.1 插槽的使用

以前的代码是不具备扩展的,但是有了插槽就不一样了,可以很好的扩展,也可以很好的自定义。
在子组件需要扩展的地方使用上slot,然后在父组件的子组件标签中写入需要的内容即可。

<div id="app">
        <cpn><button>按钮</button></cpn>
        <cpn><span>嘿嘿</span></cpn>
        <cpn><i>hhha</i></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件
            <slot></slot>
        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            components: {
                cpn: {
                    template: '#mytemp'
                }
            }
        });
    </script>
6.1.2 插槽的默认值

如果插槽里有很多地方都需要使用相同的内容,此时我们就可使用默认值,那么不使用默认值的地方加入新内容即可,其他需要就可以不写内容了。

<div id="app">
        <cpn></cpn>
        <cpn><span>嘿嘿</span></cpn>
        <cpn><i>hhha</i></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件
            <slot><button>按钮</button></slot>
        </div>
    </template>
6.1.3 多个值替换插槽

如果需要插入多个标签的内容,也是可以的,他会直接将这些内容整体替换。

<div id="app">
        <cpn></cpn>
        <cpn>
            <span>嘿嘿</span>
            <b>呵呵呵呵呵呵</b>
            <span>哈哈</span>
        </cpn>
        <cpn><i>hhha</i></cpn>
    </div>
    <template id="mytemp">
        <div>
            我是组件
            <slot><button>按钮</button></slot>
        </div>
    </template>

6.2 具名插槽

说简单点,就是给插槽取个名字,使用的时候指明替换哪一个,例如导航组件部分,html内容部分如下。

    <div id="app">
        <cpn><button slot="center">替换</button></cpn>
    </div>
    <template id="mytemp">
        <div>
            <slot name="left"><button></button></slot>
            <slot name="center"><button></button></slot>
            <slot name="right"><button></button></slot>
        </div>
    </template>
6.3 作用域插槽

**slot-scope: **被废弃了,但是也能使用
组件是有作用域的,父子之间也是可以进行通信的。
使用插槽的时候也会涉及到作用域。当父组件需要使用子组件的值时,需要将值抛出,父组件通过slot-scope进行获取。下面的方式可能有些过时,如下。

<div id="app">
        <cpn></cpn>
        <cpn><!--这种方式最后一项会多一个分隔符出来-->
            <div slot-scope="slot2">
                <span v-for="item in slot2.data">{{item}} </span>
            </div>
        </cpn>
        <cpn><!--join()就可以规避上面那种的问题-->
            <div slot-scope="slot3">
                <span>{{slot3.data.join(" - ")}}</span>
            </div>
        </cpn>
    </div>
    <template id="mytemp">
        <div>
            <slot :data="comics">
                <ul>
                    <li v-for="item in comics">{{item}}</li>
                </ul>
            </slot>
        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            components: {
                cpn: {
                    template: '#mytemp',
                    data() {
                        return {
                            comics: ['斗破苍穹', '斗罗大陆', '灵笼', '完美世界', '雾山五行', '吞噬星空']
                        }
                    }
                }
            }
        });
    </script>

在这里插入图片描述
**v-slot:**新版本使用

<div id="app">
        <cpn>
            <template v-slot:default="slot2"><!--此处必须是template,否者无效-->
                <span>{{slot2.data}} </span>
            </template>
        </cpn>
    </div>
    <template id="mytemp">
        <div>
            <slot :data="comics">
                <ul>
                    <li v-for="item in comics">{{item}}</li>
                </ul>
            </slot>
        </div>
    </template>

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

	<div id="app">
        <cpn v-slot:default="slot2">
            <span>{{slot2.data}} </span>
        </cpn>
    </div>

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

	<div id="app">
        <cpn v-slot="slot2">
            <span>{{slot2.data}} </span>
        </cpn>
    </div>

只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:也就是简写前的方式

模块化

背景:JavaScript设计初期只是实现简单的页面交互逻辑,当时的js代码直接写在script标签里。但是随着各方面的发展,很多后端的业务逻辑交给了前端完成,比如表单验证等,特别是随着ajax的广泛应用,前端的代码量迅速增长,使得不能使用原来的方式,这时,大量的js代码放入了js文件中,通过外部引入进html中,但是也会出现一些问题,比如命名问题,数据共享等,模块化就应运而生。

什么是前端模块化

将一个复杂的程序依据一定的规范封装成多个部分(文件),每个部分的内部数据和实现都是独立的,对外暴露一部分内容供其他部分调用。

es5模块化解决方案

在es5(ECMAScript第五个版本)中,为了解决作用域的问题,很多时候我们都是通过闭包的形式。但是,使用闭包也会有它的问题,比如,一个js文件确实需要用到另一个js里的变量、函数等。不重写几乎是用不到的。因为此时每个js都是一个独立的作用域,无法相互使用。
我们会通过返回对象、再调用的方式去解决。如下。
a.js

var module1 = (function () {
    let obj = {}
    let name = '张三'

    obj.name = name

    return obj
})()

c.js

(function () {
    console.log(module1.name);
})()

此处需要注意的是module1 不能重复,每一个模块使用唯一的名称

es6模块化解决方案

到了es6(ECMAScript第六个版本),自带了模块化。只需在html引用js时添加属性type=“module”,并在js使用export和import完成。

	<script src="./a.js" type="module"></script>
    <script src="./c.js" type="module"></script>

a.js

let name = '张三'
export {
    name
}

c.js

import { name } from './a.js'
console.log(name);

export不仅可以导出变量,还可以将函数、类等导出,并且还能不在最后导出,声明时就导出。如下

export function sum(num1,num2){
    return num1 + num2
}

联系题

1、完成如下功能。
在这里插入图片描述
2、加强功能,未完待续

;