Vue.js学习笔记(三)(组件的基本使用)

1.全局组件与局部组件

组件的意义:写一个可以复用的小模块,
举例:写一段可以复用的标签
注册组件基本步骤

  1. 创建组件构造器(调用Vue.extend()方法)
  • 调用Vue.extend()创建的是一个组件构造器。
  • 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
  • 该模板就是在使用到组件的地方,要显示的HTML代码。
  • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
  1. 注册组件(分为全局和局部,调用Vue.component()方法)
  • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
  • 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
  1. 使用组件(在Vue实例的作用范围内使用组件)
    写法代码:
<body>
<div id="app">
    <!--未注册,不可使用-->
    <cpn></cpn>
</div>

<div id="app2">
	<!--3.使用组件-->
    <cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
    // 1.创建组件构造器对象
    const cpn = Vue.extend({
        template:
            `
            <div>
                <h2>模板</h2>
                <h2>模板</h2>
           </div>
            `
    })
    // 2.全局注册组件,可以在多个Vue实例下面使用,没有在Vue实例的作用范围内使用组件的限制
    // Vue.component('cpn', cpn);

    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        }
    })
       //局部组件的使用,此时id=app的div里不可使用此组件
    const aaa2 = new Vue({
        el: "#app2",
        components :{
            cpn: cpn
        }
    })
</script>
</body>

效果:
在这里插入图片描述一个浏览器读出来了标签一个没读出来,因为没有注册

2.父组件与子组件

介绍:如果一个组件能调用另一个组件,那么这个组件是父组件,被调用的是子组件

cpn2(模板2)并不能直接被id=app的div调用,因为他是在模板1生成的时候注册的,模板1生成时可以调用,但是模板1注册的时候(在new vue)不会顺带模板2,除非模板2也在new vue里注册

代码:

<body>
<div id="app">
    <cpn1></cpn1>
    <cpn2></cpn2> <!-- 报错 -->
</div>
<script src="../js/vue.js"></script>
<script>
    //创造子组件
    const cpn2 = Vue.extend({
        template:
            `
            <div>
                <h2>模板2</h2>
           </div>
            `
    })
    //创造父组件
    const cpn1 = Vue.extend({
        template:
            `
            <div>
                <h2>模板1</h2>
                <cpn2></cpn2>
           </div>
            `,
        components:{
            cpn2: cpn2
        }

    })

    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            cpn1: cpn1
        }
    })
</script>
</body>

效果:
在这里插入图片描述
不难看出模板2被模板1调用,所以模板2是子组件,模板1是父组件

3.组件构造的语法糖

介绍:此语法糖就是把构造组件放在注册组件里面,注册组件过程中包含了由内部实现的构造器对象的创建,请看写法:

<script>
    //1.非语法糖
    const cpn1 = Vue.extend({
        template:
            `
            <div>
                <h2>模板1</h2>
           </div>
            `,

    })
    Vue.component('cpn', cpn1);//全局注册
    //局部注册
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            cpn1: cpn1
        }
    })

    //2.语法糖
    //全局注册
    Vue.component('cpn', {
        template:
            `
            <div>
                <h2>模板1</h2>
           </div>
            `,

    });
    //局部注册
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            cpn1: {
                template:
                    `
            <div>
                <h2>模板1</h2>
           </div>
            `,

            }
        }
    })

</script>

4.组件模板的分离写法

介绍:创建组件构造器的时候的template很麻烦,可以用代替
两种方式写模板,用法如下:

<body>
<div id="app">
    <cpn></cpn>
</div>
//method1
<template id="cpn">
    <div>
        <h2>我是模板标题</h2>
        <p>我是模板文字</p>
    </div>
</template>
//method2
<script type="text/x-template" id="cpn">
<div>
    <h2>我是模板标题</h2>
    <p>我是模板文字</p>
</div>
</script>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            cpn: {
                template: "#cpn",
            }
        }
    })
</script>
</body>

两个的结果都是:在这里插入图片描述

5.组件中数据以及数据格式的思考

组件不可以直接访问Vue实例中的数据,Vue组件的数据应该放在组件对象里
组件对象也有一个data属性(也可以有methods,props属性,下面我们有用到)
只是这个data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据
为什么必须是函数:用data:{}的写法,多个组件引用同一个data对象,影响其他组件,data(){}的写法每次都会返回一个新的对象,然后各自组件用各自的对象就不会互相影响了。(想要使用同一个对象参考如果我想obj1,obj2指向同一个对象?

介绍:我们new Vue对象的时候会写data,组件也有data,而且是组件自身的
意义:组件可以存放数据,才能体现组件复用的好处,复用时每个组件数据不能粘连
案例写法(用同一个组件写3个计数器,数据相互独立):

<body>
<div id="app">
  <!--组件实例对象-->
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>
<template id="cpn">
  <div>
    <h2>当前计数: {{counter}}</h2>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  Vue.component('cpn', {
    template: "#cpn",
    data() {
      return {
        counter: 0
      }
    },
    // data:{}不可以这样
    methods: {
      increment() {
        this.counter++;
      },
      decrement() {
        this.counter--;
      }
    }
  });
  const aaa = new Vue({
    el: "#app",
    data: {
      message: "hello,guys",
    }
  })
</script>
</body>

效果:在这里插入图片描述
思考:我们写的三个组件是不是使用同一个data对象

  • 不是
  • 原因:创建组件的时候会调用data函数,每次调用都会return一个新的对象

举个例子:
下面代码obj1和obj2是不是同一个对象?
不是,函数每次执行的时候都会在栈空间创建新的对象来返回,所以obj1和obj2指向不同内存地址

<script>
  function f() {
    return {
      name: "aaa",
      age: 18
    }
  }

  let obj1 = f();
  let obj2 = f();
  obj1.name = "kobe"

  console.log(obj1);
  console.log(obj2);
</script>

结果:
在这里插入图片描述
如果我想obj1,obj2指向同一个对象?

  • obj本质上是个内存地址(0x100),函数返回对象的时候本质上返回obj的内存地址,所以一旦调用都返回内存地址,obj1/obj2内存地址都是0x100,都指向obj
  const obj = {
    name: 'aaa',
    age: 18
  }

  function f() {
    return obj
  }

  let obj1 = f()
  let obj2 = f()

  obj1.name = 'kobe'
  console.log(obj1);
  console.log(obj2);

在这里插入图片描述

父子组件的通信

  • 子组件是不能引用父组件或者Vue实例的数据的。
  • 在开发中,往往一些数据确实需要从上层传递到下层:
    • 比如在一个页面中,我们从服务器请求到了很多的数据。其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)
  • 如何进行父子组件间的通信呢?Vue官方提到
    • 通过props向子组件传递数据
    • 通过事件向父组件发送消息

6.父组件传值给子组件

意义:开发场景中,通常是根组件接受后台传来的数据,然后再传递给下级子组件
小实例展示父组件如果传值子组件:

<body>
<div id="app">
    <cpn :c-message="message" :c-movies="movies"></cpn>
</div>
<template id="cpn">
    <div>
        <ul>
            <li v-for="item in cmovies">{{item}}</li>
        </ul>
        <h2>{{cmessage}}</h2>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: '你好啊',
            movies: ['海王', '海贼王', '海尔兄弟']
        },
        components: {
            'cpn': {
                template: "#cpn",
                // method1 三种方法都可以,已debug
                // props: ['cmessage', 'cmovies'],

                //method2 可以进行类型判断
                // props: {
                //     cmessage: String,   //有自定义构造函数时,验证也支持自定义的类型
                //     cmovies: Array
                // },

                //method3 设置默认参数
                props: {
                    cmessage: {
                        type: String,
                        default: '默认参数',
                        require: true  //一定要传的参数
                    },
                    cmovies: {
                        type: Array,
                        default() {
                            return ['空数组1', '空数组2']
                        },
                        require: false  //不一定要传的参数
                    }
                },
                data() {
                    return {}
                }
            }
        }
    })
</script>
</body>

效果:
cpn没有被传参时:在这里插入图片描述
cpn里有参数的时候:在这里插入图片描述
另外,值得注意的是,html标签不能识别驼峰原则的属性,如果js里面属性是驼峰,应该这样:

html:
<cpn :c-message="message" :c-movies="movies"></cpn>
js:
props: {
                    cMessage: {
                        type: String,
                        default: '默认参数',
                        require: true  //一定要传的参数
                    },
                    cMovies: {
                        type: Array,
                        default() {
                            return ['空数组1', '空数组2']
                        },
                        require: false  //不一定要传的参数
                    }
                },

在这里插入图片描述

7.子组件传值给父组件

意义:想象一个场景,子组件发生点击事件,页面另一处改变,就需要所点击组件传值给父
案例:点击按钮让父组件收到数据
逻辑流程:模板里v-for产生按钮→点击按钮触发点击事件(点击事件写在子组件里)→点击事件通过$emit函数发送从子组件接受到的数据→html在使用子组件的时候接受子组件传来的数据

<cpn @receive-message="receivedMessage"></cpn>
(至于为什么这样写:和此组件并列的div并不能收到数据,只有这种写法才能正确传递数据,应该是只有
子组件最外层,就是写到html里的那层(在dom层面会被解析)才能获取子组件内部所产生的数据)

→在父组件的方法receivedMessage里就可以展示收到的数据

<body>
<div id="app">
    <cpn @receive-message="receivedMessage"></cpn>
    <!-- 不需要传参数,因为不是@click,这里不写参数不会给事件mouseclick对象,会给emit的参数-->
</div>
<template id="cpn">
    <div>
        <button  v-for="item in categories" @click="sendMessage(item)">{{item.name}}</button>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            'cpn': {
                template: '#cpn',
                data() {
                    return {
                        categories: [
                            {id: 'aaa', name: '热门推荐'},
                            {id: 'bbb', name: '手机数码'},
                            {id: 'ccc', name: '家用家电'},
                            {id: 'ddd', name: '电脑办公'},
                        ]
                    }
                },
                methods: {
                    sendMessage(item){
                        this.$emit('receive-message', item);
                    }
                }

            }
        },
        methods: {
            receivedMessage(item){
                console.log("received", item);
            }
        }
    })
</script>
</body>

效果:
在这里插入图片描述

父子传值总结

  • cpn标签中既可以用来父传子,<cpn :c-message="message" :c-movies="movies"></cpn>
  • 也可以用来字传父 <cpn @receive-message="receivedMessage"></cpn> 接收emit事件
  • 可以看作父子传值的桥梁

8.父子组件双向通讯

意义:想象一个需求,在一个组件里输入值,让父组件传过来的属性改变,同时父组件里的值也要改变,
思考:这无法直接改属性,假设属性是pnum,直接用this.pnum=xxxx(输入的值),会报错,子组件无法直接更改父组件传来的属性在这里插入图片描述
,所以需要先把属性用数据data保存起来data(){ return{ dnum: this.pnum } },然后再改变数据里面的值,同时输入时input标签添加输入函数@input=“changeNumAndSend”,修改数据同时传值给父组件,父组件再接受所改变的值,赋值给data里面传给子组件的对象,对象又改变传给子组件的属性prop
代码:

<body>
<div id="app">
  <cpn :pnum="num" @send-change="receiveChange"></cpn>
</div>
<template id="cpn">
  <div>
    <h2>pnum:{{pnum}}</h2>
    <h2>dnum:{{dnum}}</h2>
    <input type="text" :value="dnum" @input="inputEvent">
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const aaa = new Vue({
    el: "#app",
    data: {
      num: 8888,
    },
    methods: {
      receiveChange(item) {
        this.num = item;
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            dnum: this.pnum
          }
        },
        props: {
          pnum: Number
        },
        methods: {
          inputEvent(event) {
            this.dnum = parseInt(event.target.value);
            this.$emit('send-change', this.dnum);
          }
        }
      }
    }
  })
</script>
</body>

效果:
在这里插入图片描述

输入框里输入什么,num(父组件里面),prop(父组件传的属性),data(子组件数据)都随时变,data与输入函数牢牢绑定,第一个改变,num是输入函数顺便传值给父组件所以改变,prop是随着父组件数据改变所以变了

9.父组件访问子组件里的值

介绍:父组件访问子组件里的值有两种方式,第一种是用$children实现,直接在父组件的methods里面用就好了,以数组的形式返回
案例代码:

<body>
<div id="app">
    <button @click="showChildrenData">在父组件里的按钮</button>
    <cpn ></cpn>
    <cpn ></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是子组件的模板</h2>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        methods: {
            showChildrenData(){
                console.log(this.$children[0].cdata);
                this.$children[0].showMessage();


            }

        },
        components: {
            'cpn': {
                template: "#cpn",
                data() {
                    return {
                        cdata: "我是子组件里的一个data"
                    }
                },
                methods: {
                    showMessage(){
                        console.log("我是子组件里的一个函数");
                    }
                }
            }
        }
    })
</script>
</body>

结果:
在这里插入图片描述
成功调用子组件的data和methods,但是需要通过数组返回

$refs方法,直接访问绑定的标签,耦合度低:

  • this.$refs是对象类型,默认是空对象,在标签中有属性ref='xxx’才有内容
    代码:
<body>
<div id="app">
    <button @click="showChildrenData">在父组件里的按钮</button>
    <cpn ref="aaa"></cpn>
    <cpn ref="bbb"></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是子组件的模板</h2>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        methods: {
            showChildrenData(){
                console.log(this.$refs);
                console.log(this.$refs.aaa.cdata);
                this.$refs.aaa.showMessage();
            }

        },
        components: {
            'cpn': {
                template: "#cpn",
                data() {
                    return {
                        cdata: "我是子组件里的一个data"
                    }
                },
                methods: {
                    showMessage(){
                        console.log("我是子组件里的一个函数");
                    }
                }
            }
        }
    })
</script>
</body>

效果:
在这里插入图片描述

10.子组件访问父组件里面的值

介绍:子组件访问里的值用$parent
案例:

<body>
<div id="app">
    <cpn></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是子组件的模板</h2>
        <button @click="visitParent">我是子组件里面的一个按钮</button>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        methods: {
        },
        components: {
            'cpn': {
                template: "#cpn",
                data() {
                    return{ }
                },
                methods: {
                    visitParent(){
                        console.log(this.$parent);
                        console.log(this.$parent.message);

                        console.log(this.$root);
                        console.log(this.$root.message);
                    }
                }
            }
        }
    })
</script>
</body>

结果:
在这里插入图片描述
$root可以访问根组件的值

11.插槽的基本用法

介绍:插槽就是组件中可变的标签,比如一个组件复用两次,两次使用中所用到的标签略有差异,那就把可变的标签用插槽表示

  • 插槽的的默认值:这样定义插槽 <slot><span>哈哈哈</span></slot> 插槽内不放东西就是默认哈哈哈
  • 如果插槽有多个值,同时放入组件进行替换,一起作为替换元素

案例:三个组件对比:不使用插槽,插槽放一个标签,插槽放两个标签

<body>
<div id="app">
    <cpn></cpn>
    <cpn><h2>这是slot</h2></cpn>
    <cpn>
        <h2>这是h2-slot</h2>
        <p>这是p-slot</p>
    </cpn>
</div>
<template id="cpn">
    <div>
        <h2>模板标题</h2>
        <p>模板内容</p>
        <slot></slot>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            'cpn': {
                template: "#cpn",
            }
        }
    })
</script>
</body>

效果:
在这里插入图片描述

12.给插槽命名

意义:假设一个场景,一个组件里有三个插槽,每次使用三个插槽里的内容都不相同,那就要给每个插槽都命名,某个标签代替插槽的时候通过插槽名字找到对应插槽然后再替代
代码:

<body>
<div id="app">
    <cpn></cpn>
    <cpn><span slot="left">标题</span></cpn>
    <cpn><span slot="left">标题</span> <button slot="right">按钮</button></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 aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            'cpn': {
                template: "#cpn",
            }
        }
    })
</script>
</body>

效果:
在这里插入图片描述

13. 编译作用域

组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译

编译的作用域:
我们在使用组件的时候,<cpn v-show="isShow"></cpn>isShow函数写在Vue里面,
组件里面的函数写在模板里面,写在组件的methods里面

14.作用域插槽

通过插槽来传值
就是读取子组件的值,渲染到父组件,$children要渲染完成才能使用,这个在渲染前就加载好
代码:

<body>
<div id="app">
    <cpn></cpn>
    <cpn>
        <template slot-scope="slot">
            <span v-for="item in slot.data"> {{item}} </span>
        </template>
    </cpn>

</div>
<template id="cpn">
    <div>
        <slot :data="pLanguages">
            <ul>
                <li v-for="item in pLanguages">{{item}}</li>
            </ul>
        </slot>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const aaa = new Vue({
        el: "#app",
        data: {
            message: "hello,guys",
        },
        components: {
            'cpn': {
                template: "#cpn",
                data(){
                    return{
                        pLanguages: ["java", "c++", "Python"]
                    }
                }
            }
        }
    })
</script>
</body>

效果:
在这里插入图片描述

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值