[Vue] 组件化开发

组件化开发

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

  • 任何人在面对复杂问题的处理方式

    • 任何一个人的处理信息的逻辑能力都是有限的
    • 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大推的内容
    • 但是,我们可以将问题进行拆解
  • 组件化开发也是类似的思想

    • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得十分复杂,而且不利于后续的管理以及拓展
    • 但如果我们将一个页面查分成若干个小的功能块,每个功能块完成属于自己的独立的功能,那么之后整个页面的管理和维护就变得非常容易了
    • vue的组件化思想,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
    • 任何的应用都会被抽象成一棵组件树

在这里插入图片描述

  • 组件的使用分成三个步骤:
    • 创建组件构造器
    • 注册组件
    • 使用组件

在这里插入图片描述

  • 对于Vue.extend()
    • 调用这个方法创建的是一个组件构造器
    • 通常在创建构造器的地方,传入template代表我们自定义组件的模板
    • 模板就是HTML代码
    • 事实上,上图的写法基本上不用了,我们使用他的语法糖
  • Vue.component()
    • 调用这个方法是将组件构造器注册为一个组件,并且给他起一个组件的标签名称
    • 需要传递两个参数: 1.注册组件的标签名 2. 组件构造器
  • 组件必须挂载在某个Vue实例下,否则他不会生效
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <!--    我想将下面这段代码封装起来-->
    <!--    <h2>我是标题</h2>-->
    <!--    <p>我是内容qwq</p>-->
    <!--    <p>我是内容23333</p>-->
    <my-cpn></my-cpn>
</div>

<!--下面这个在区域外,不会生效-->
<my-cpn></my-cpn>

<script src="../js/vue.js"></script>
<script>
    //创建构造器对象
    const cpnC = Vue.extend({
        template: `<div>
        <h2>我是标题</h2>
        <p>我是内容qwq</p>
        <p>我是内容23333</p>
        </div>`
    })
    //注册组件
    Vue.component('my-cpn', cpnC)

    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: { //用于定义数据
        }
    })
</script>
</body>
</html>

1.2 全局组件和局部组件

全局组件: 组件可以在多个Vue实例中使用

上个案例的代码创建的组件就是全局组件

局部组件: 只能在指定的Vue实例里面使用

注意: 其实在开发中我们一般只创建一个Vue实例

Vue实例中,除了我们之前学习的data,computed,methods属性之外,现在再加上一个属性,components属性,在这里面定义的组件只能在这个Vue实例中使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <cpn></cpn>
</div>

<script src="../js/vue.js"></script>
<script>
    const cpnC = Vue.extend({
        //这里的``符号是es6特有的,这个符号括起来的字符串允许换行
        template: `<div><h2>就是这么自信!</h2></div>`
    })

    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: { //用于定义数据

        },
        components: {
            //key是标签名, value是组件构造器
            cpn: cpnC
        }
    })
</script>
</body>
</html>

1.3 父组件和子组件的区别

子组件就是在父组件里面注册的组件

除非子组件也在实例中注册了,不然子组件是无法单独引用的

不多bb,直接上例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!--    cpn2里面cpn1注册了,所以可以显示, cpn2是父组件,cpn1是子组件-->
    <cpn2></cpn2>

    <!--    下面这个是不会显示,会报错的,因为儿子在Vue实例里面没有注册-->
    <cpn1></cpn1>

</div>

<script src="../js/vue.js"></script>
<script>
    const cpnC1 = Vue.extend({
        template: `<div><h2>我是儿子!</h2></div>`,

    });
    const cpnC2 = Vue.extend({
        template: `<div>
                        <h2>我是亲爹!</h2>
                        <cpn1></cpn1>
                   </div>`,
        components: {
            cpn1: cpnC1
        }
    });

    //这个其实也是一个组件,这个是最顶部的组件,叫做Root组件
    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: {},
        components: {
            cpn2: cpnC2,
        }
    })
</script>
</body>
</html>

当然,要是父组件中没有注册子组件,那么要想父组件中的子组件能够正常显示,就必须在Root组件中声明子组件

1.4 注册组件的语法糖写法

注册全局组件的语法糖

Vue.component('cpn1', {
            template: `
        <div>
        <h2>我是你爹</h2>
        </div>
        `
})

//直接这样写组件就注册好了,可以直接在Vue实例中调用

第二个参数其实是调用了extend()方法的,只不过被隐藏了

注册局部组件的语法糖

 components: {
            'cpn1': {
                template: `
                           <div>
                               <h2>我是你爹</h2>
                           </div>
`                                            `
            }

1.5 组件模板抽离的写法

如果像上面那样将HTML模板写在了JS里面,那么其实就是很不好看,所以我们要抽离

第一种: 将模板定义在同一文件但是不同script域中 (不常用,不好用)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <cpn1></cpn1>
</div>



<!--分离的模板写在这里-->
<script type="text/x-template"  id="cpn">
    <div>
        <h2>我是你爹</h2>
    </div>
</script>

<script src="../js/vue.js"></script>

<script>
    let app = new Vue({
        el: '#app',
        components: {
            'cpn1': {
                template: '#cpn'
            }
        }
    })
</script>
</body>
</html>

第二种: 使用<template>标签 (推荐使用)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <cpn1></cpn1>
</div>



<!--分类的模板写在这里-->
<template  id="cpn">
    <div>
        <h2>我是你爹</h2>
    </div>
</template>

<script src="../js/vue.js"></script>

<script>
    let app = new Vue({
        el: '#app',
        data: {},
        components: {
            'cpn1': {
                template: '#cpn'
            }
        }
    })
</script>
</body>
</html>

1.6 组件的Data为什么需要是一个function

组件数据的存放

  • 组件对象也有一个data属性(里面还可以有prop,methods等) (和Vue实例相似的原因就是因为,组件对象原型指向Vue实例)
  • 只不过这个data属性必须是一个函数,这个函数返回一个对象,返回的就是保存着的数据

为什么必须是一个函数

因为我们的组件是要复用的,我们在不同的页面也能会有多个这个组件,为了保证data的数据不共享,所以我们将data作为一个函数对象返回,这样,每一个组件实例所拥有的data对象都是相互独立的,他们的数据也是独立的,不会相互影响

我们可以想象,每个组件实例指向一个data对象, 每个return的到的data对象所处的推空间的地址是不一样的,所以他们是独立的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
<!--    这三个组件实例的数据是相互独立的-->
    <cpn1></cpn1>

    <cpn1></cpn1>

    <cpn1></cpn1>
</div>


<!--分类的模板写在这里-->
<template id="cpn">
<!-- 注意内容一定要包一个div作为根元素-->
    <div>
        <h2>计数器{{count}}</h2>
        <button @click="increase">+</button>
        <button @click="decrease">-</button>
    </div>
</template>

<script src="../js/vue.js"></script>

<script>
    let app = new Vue({
        el: '#app',
        data: {},
        components: {
            'cpn1': {
                template: '#cpn',
                data() {
                    return {
                        count: 0
                    }
                },
                methods: {
                    increase() {
                        this.count++;
                    },
                    decrease() {
                        this.count--;
                    }
                }
            }
        }
    })
</script>
</body>
</html>

1.7 父子组件通信

1.7.1 父传子props

父组件中data的数据正常下是子组件是无法调用的,所以我们可以用props

第一种写法就是props后面写一个数组 props: ['新的变量名1','新的变量名2'] 可读性比较差,比较少用

第二种写法就是使用对象的写法

支持以下类型

在这里插入图片描述

下面我演示了一个父传子传子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <cpn :movies="movies"></cpn>
</div>


<template id="cpn">
    <cpn1 :movies="movies"></cpn1>
</template>

<template id="cpn1">
    <div>
        <ul>
            <li v-for="item in movies">{{item}}</li>
        </ul>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>

    const cpn1 = Vue.component('cpn1', {
        template: "#cpn1",
        props: {
            movies: Array
        }
    })

    const cpn = {
        template: `#cpn`,
        props: {
            //这种是简写的方法
            //movies: Array

            movies: {
                //我们在这里可以定义很多东西
                type: Array,
                //对象是一个对象2或者数组的时候,默认值必须是一个函数
                default() {
                    return ["我是你爹"]
                }
            }
        },
        component: {
            cpn1
        }
    }

    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: { //用于定义数据
            movies: ['海王', '海贼王', '海尔兄弟']
        },
        components: {
            cpn
        }
    })
</script>
</body>
</html>

1.7.2 props中的驼峰标识

如果在props中取名是用驼峰命名法取名,那么在v-bind的时候就会有问题, **直接写驼峰标识系统就不认,将大写的那个地方换成小写,同时前面加上一个- 符号就可以了 **

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
<!--    下面这样写是错误的,vue不支持-->
<!--    <cpn :cInfo="info"></cpn>-->
<!--    把驼峰转化为这个就支持了-->
    <cpn :c-info="info"></cpn>
</div>

<template id="cpn">
    <h2>{{cInfo}}</h2>
</template>

<script src="../js/vue.js"></script>
<script>

    const cpn = {
        template: '#cpn',
        props: {
            cInfo: {
                type: Object,
                default() {
                    return { name: '你爹'};
                }
            }
        }
    }

    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: { //用于定义数据
            info: {
                name: 'father',
                age:20,
                height: 1.88
            }
        },
        components: {
            cpn
        }
    })
</script>
</body>
</html>

1.7.3 子传父(自定义事件)

我们需要通过自定义事件来完成子组件中的data传递到父组件

  • 我们之前学习的v-on不仅能够用于监听DOM事件,还可以用于组件间的自定义事件

在子组件中

通过$emit()来触发事件

在父组件中

通过v-on来监听子组件事件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
<!--    没有脚手架的时候,命名时不能写驼峰,他不认-->
    <cpn @btn-click="cpnClick">
    </cpn>
</div>

<template id="cpn">
    <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: '#cpn',
        data(){
            return {
                categories: [{
                    id: 1,
                    name: '热门推荐'
                },{
                    id: 2,
                    name: '手机数码'
                },{
                    id: 3,
                    name: '家用家电'
                },{
                    id: 4,
                    name: '电脑办公'
                }],
                id: -1
            }
        },
        methods: {
            btnClick(item){
                //自定义事件传递到父组件
                this.$emit('btn-click',item.id);
            }
        }
    }

    let app = new Vue({
        el: '#app',
        data: { 
            info: {
                name: 'father',
                age:20,
                height: 1.88
            }
        },
        components: {
            cpn
        },
        methods: {
            //这里就是我们在父组件中获取子组件中data的方法的定义
            cpnClick(e) {
                console.log(e);
            }
        }
    })
</script>
</body>
</html>

1.7.4 子父双向绑定案例

注意: 我们在子组件中,不要修改传来的父组件的值,这样是不对的,我们要通过计算属性,或者一个新的data属性来接受这个值,然后再对计算属性或者data修改就行了

我们这里要做一个计时器的案例,组件里面只有两两个按钮,总数在总页面中创建

所以我们在总页面中创建一个num1, 将这个初值传进子组件,然后子组件中定义一个num2,num2的初值等于num1,然后子组件中的两个按钮控制num2的值的修改,将num2的值实时传回父组件,并显示

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <cpn @btn-click="cpnClick" :num1="num1">
  </cpn>
  <h2>总数: {{num1}}</h2>
</div>

<template id="cpn">
  <div>
    <button @Click="btnClickAdd">+</button>
    <button @Click="btnClickDec">-</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  const cpn = {
    template: '#cpn',
    data(){
      return {
        num2: this.num1
      }
    },
    props: {
      num1: Number
    },
    methods: {
      btnClickAdd(){
        //自定义事件传递到父组件
        this.num2++;
        this.$emit('btn-click',this.num2);
      },
      btnClickDec(){
        //自定义事件传递到父组件
        this.num2--;
        this.$emit('btn-click',this.num2);
      }
    }
  }

  let app = new Vue({
    el: '#app',//用于挂载要管理的元素
    data: { //用于定义数据
      num1: 0
    },
    components: {
      cpn
    },
    methods: {
      cpnClick(e) {
        this.num1 = e;
      }
    }
  })
</script>
</body>
</html>

1.7.5 watch属性

除了data,properties,components等属性之外,还有一种特别好用的属性,watch,它可以监听data中数据的改变,然后做出响应的行为

et app = new Vue({
    el: '#app',//用于挂载要管理的元素
    data: { //用于定义数据
      num1: 0
    },
    components: {
      cpn
    },
    methods: {
      cpnClick(e) {
        this.num1 = e;
      }
    },
    watch: {
        //XXX就是你想要监听的变量名,下面这个newValue就是watch监听到的新改变的值,oldValue懂得都懂
        XXX(newValue,oldValue) {
            方法体
        }
    }
  })

1.7.6 父访问子 — children-refs

有时候,我的父组件不只是想要和子组件传递数据,有时候我想要调用子组件里面的方法,那么我就用上面这个进行访问

父组件访问子组件

  1. 使用$children (返回所有的子组件对象的数组) (较少使用)
  2. 使用$refs (返回指定的子组件对象,需在组件的标签上面加上ref属性 ) (如果直接this. r e f s , 那 么 他 会 返 回 所 有 的 带 有 r e f 属 性 的 组 件 的 对 象 的 对 象 , ∗ ∗ 所 以 我 们 一 般 使 用 的 时 候 都 是 ‘ t h i s . refs,那么他会返回所有的带有ref属性的组件的对象的对象 , **所以我们一般使用的时候都是`this. refs,ref,使this.refs.xxx`xxx就是我们在组件标签上面定义的ref的名字 **)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <cpn @btn-click="cpnClick" :num1="num1">
  </cpn>
  <cpn @btn-click="cpnClick" :num1="num1" ref="aaa">
  </cpn>
  <cpn @btn-click="cpnClick" :num1="num1" ref="bbb">
  </cpn>
  <h2 @click="btnClick">总数: {{num1}}</h2>
</div>

<template id="cpn">
  <div>
    <button @Click="btnClickAdd">+</button>
    <button @Click="btnClickDec">-</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  const cpn = {
    template: '#cpn',
    data(){
      return {
        num2: this.num1
      }
    },
    props: {
      num1: Number
    },
    methods: {
      btnClickAdd(){
        //自定义事件传递到父组件
        this.num2++;
        this.$emit('btn-click',this.num2);
      },
      btnClickDec(){
        //自定义事件传递到父组件
        this.num2--;
        this.$emit('btn-click',this.num2);
      }
    }
  }

  let app = new Vue({
    el: '#app',//用于挂载要管理的元素
    data: { //用于定义数据
      num1: 0
    },
    components: {
      cpn
    },
    methods: {
      cpnClick(e) {
        this.num1 = e;
      },
      btnClick() {
        //这里打印的就是子组件数组(包含了所有的子组件)
        console.log(this.$children);
        //这个不行,这个只能打印指定的子组件数组,当指定了具体哪一个的时候,直接返回一个组件对象
        console.log(this.$refs.aaa);
      }
    }
  })
</script>
</body>
</html>

1.7.7 子访问父 ---- parent-root

开发中不建议使用,了解即可 一个是访问父组件,一个是访问根组件

1.8 slot插槽

1.8.1 slot插槽的基本使用

  • 组件的插槽

    • 组件的插槽也是为了让我们封装i起来的组件更加具有拓展性
    • 让使用者可以决定组件内部的一些内容到底展示什么
  • 怎样封装合适呢?

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

插槽的基本使用

  1. 在template里面加上slot标签

  2. 想要slot有默认值,就在slot标签里面加上一些标签

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <!--    有了插槽之后,我们就可以在组件标签里面定义我们想要的内容-->
        <cpn>
            <button>按钮</button>
        </cpn>
        <cpn>我是你爹</cpn>
        <cpn></cpn>
    </div>
    
    <template id="cpn">
        <div>
            <h2>我是组件</h2>
            <p>哈哈哈哈</p>
            <!--下面这里我们使用插槽,父组件在子组件里面还可以加内容-->
            <slot>
    <!--            这里也可以写东西,这个是默认值,意思是我们在引用组件的时候不额外加东西的时候,这里面的标签就会显示-->
                <button>我是默认值</button>
            </slot>
        </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
        const cpn = {
            template: '#cpn'
        }
    
    
        let app = new Vue({
            el: '#app',//用于挂载要管理的元素
            data: { //用于定义数据
                message: '我是你爹!'
    
            },
            components: {
                cpn
            }
        })
    </script>
    </body>
    </html>
    

1.8.2 具名插槽的使用

当我们定义了多个插槽的时候,如果没有命名,那么我们在自定义组件的时候,我们自定义的内容就会在所有插槽里面显示出来,这不合理,所以我们要给插槽命名

**那么怎么起名呢? 给slot标签一个name属性 , 在自定义的时候,标签加上slot 属性 **

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <!--    如果不命名,那么就会实现所有插槽都会变成我是你爹-->
    <cpn>
        我是你爹
        <span slot="aa">我是自定义的1</span>
        <span slot="bb">我是自定义的2</span>
        <span slot="cc">我是自定义的3</span>
    </cpn>
<!--    页面显示: 我是自定义的1 我是自定义的2 我是自定义的3 我是你爹-->
</div>

<template id="cpn">
    <div>
        <!--下面这里我们使用插槽,父组件在子组件里面还可以加内容-->
        <slot name="aa"><p>我是1</p></slot>
        <slot name="bb"><p>我是2</p></slot>
        <slot name="cc"><p>我是3</p></slot>
        <slot><p>我是默认</p></slot>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    const cpn = {
        template: '#cpn'
    }


    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: { //用于定义数据
            message: '我是你爹!'

        },
        components: {
            cpn
        }
    })
</script>
</body>
</html>

1.8.3 编译作用域的概念

  • 父组件模板中的所有东西都会在父级作用域内编译子组件模板的所有东西都会在子级作用域内编译
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

<div id="app">
    <!--    这里使用的isShow是Vue实例的isShow,所以显示了-->
    <cpn v-show="isShow"></cpn>
</div>

<template id="cpn">
    <div>
        <h2>哈哈哈我是组件</h2>
        <!--    按钮使用的isShow是组件的isShow,所以不显示-->
        <button v-show="isShow">就这?</button>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: { //用于定义数据
            isShow: true
        },
        components: {
            cpn: {
                template: "#cpn",
                data() {
                    return {
                        isShow: false
                    }
                }
            }
        }
    })
</script>
</body>
</html>

注意: 凡是在div#app 里面写的所有的数据,都是引用Vue实例的数据

1.8.4 作用域插槽

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

总结一句话就是: 父组件替换插槽的标签,但是内容由子组件来提供。

核心代码: v-scpoe = "自定义的引用时的名字"v-slot:组件的名字 = "自定义的引用时的名字"

现在我有一个需求

在这里插入图片描述

<!DOCTYPE html>
<html lang="en" xmlns:v-slot="http://www.w3.org/1999/XSL/Transform" xmlns:slot-scope="">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

<div id="app">
    <!--    这里是默认显示-->
    <cpn></cpn>


    <!--    这里是横着显示-->
    <cpn >
        <!--        Vue2.5.X以下的版本必须使用template,以上的就不用,div就行-->
        <!--        下面这句话的意思是引用插槽对象,我们这里就可以使用下面的data了-->
        <template slot-scope="message">
            <span>{{message.data.join(' - ')}}</span>
        </template>
    </cpn>

    <br/>

    <!--    还有一种写法-->
    <cpn>
<!--        如果要指定是哪个slot组件的话,就写v-slot:组件的名称 = "引用的名字" -->
        <template v-slot="hehe">
            <span>{{hehe.data}}</span>
        </template>
    </cpn>
</div>

<template id="cpn">
    <div>
        <!--下面这个操作就是让slot自定义了一个属性来绑定保存了pLanguages的数据-->
        <slot :data="pLanguages" >
            <ul>
                <li v-for="item in pLanguages">{{item}}</li>
            </ul>
        </slot>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    let app = new Vue({
        el: '#app',//用于挂载要管理的元素
        data: { //用于定义数据
        },
        components: {
            cpn: {
                template: "#cpn",
                data() {
                    return {
                        pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
                    }
                }
            }
        }
    })
</script>
</body>
</html>
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值