Vue基础(三)组件相关

209 阅读2分钟

1 组件注册

步骤:

  • 创建组件构造器
  • 注册组件
  • 使用组件

1.1 全局组件

用代码展示:

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

<script src="../js/vue.js"></script>
<script>
    // 1.创建组件构造器对象-------------------------------------------
    const cpn = Vue.extend({
        template: `
         <div>
           <h2>我是标题</h2>
           <p>我是内容,哈哈哈哈啊</p>
         </div>`,
    });
    // 2.注册组件----------------------------------------
    Vue.component('cpn1', cpn);
    const app = new Vue({
        el: '.app',
        data: {},
    });
</script>

全局组件语法糖写法:

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

<script src="../js/vue.js"></script>
<script>
    // 1.全局组件语法糖-------------------------------------------
    Vue.component('cpn', {
        template: ` 
          <div>
            <h2>我是标题1</h2>
            <p>我是内容, 哈哈哈哈</p>
          </div>`,
    });
    const app = new Vue({
        el: '.app',
        data: {},
    });
</script>

1.2 局部组件

直接语法糖写法:

<div class="app">
    <cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: '.app',
        data: {},
        components: {
            cpn: {
                template: `
                   <div>
                     <h2>我是标题2</h2>
                     <p>我是内容, 呵呵呵呵</p>
                     <cpn1></cpn1>
                   </div>
                     `,
            },
        },
    });
</script>

注意Vue.component和局部组件中components,少了一个s

1.3 模板分离写法

template模板中内容太多时,用 语法书写较繁琐,所以可以将其分离到组件外面,两种写法:

<div class="app">
    <cpn></cpn>
</div>
-------------------------------------------------第一种写法
<script type="text/x-template" id="cpn">
    <div>
        <h2>我是标题</h2>
        <p>我是内容</p>
    </div>
</script>
-------------------------------------------------第二种写法
<template id="cpn">
    <div>
        <h2>我是标题</h2>
        <p>我是内容,哈哈哈哈啊</p>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: '.app',
        data: {},
        components: {
            cpn: {
                template: '#cpn',
            },
        },
    });
</script>

组件data的注意事项:

  • 组件不可以访问Vue实例中的数据
  • 组件有其自身的data属性
  • 组件的data属性必须是函数,且函数返回一个对象,对象内部保存着数据。原因:vue让每个组件对象都返回一个新的对象,如果是同一个对象,使用时会相互影响。

2 组件通信

2.1 父传子通信

通过props传递数据

<div class="app">
//将父组件即Vue实例中的数据传给子组件。
    <cpn :cmovies="movies" :cstars="stars" :cstar="star"></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是子组件</h2>
        //在子组件中使用
        <h2>{{cmovies}}</h2>
        <h2>{{cstar}}</h2>
        <h2>{{cstars}}</h2>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const cpn = {
        template: '#cpn',
        data() {
            return { message: '我是子组件里的数据' };
        },
        //通过props声明传输的数据,可以是数组形式
        // props: ['cmovies', 'cstars', 'cstar'],
        //也可以是对象形式
        props: {
          //可以限制类型,可以设置一些默认值
            cstars: Array,
            cstar: Object,
            cmovies: {
                type: [String, Array],
                default: 'ccc',
                require: true,
            },
        },
    };
    const app = new Vue({
        el: '.app',
        data: {
            movies: '进击的巨人',
            stars: ['德华', '学友', '福成', '明'],
            star: { age: 18 },
        },
        components: {
            cpn,
        },
    });
</script>

大概可以分为三步:

  1. 通过props声明传递的数据,以及设置一些数据限制
  2. 在子组件生成的标签中将父组件的数据通过v-bind传递给子组件
  3. 将数据应用在子组件中

2.2 子传父通信

通过$emit将自定义数据发射给父组件

<div class="app">
    <cpn @m-click="cpnClick"></cpn>
</div>
<template id="cpn">
    <div>
        <button v-for="m in movies" @click="btnClick(m)">{{m}}</button>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>
    const cpn = {
        template: '#cpn',
        data() {
            return {
                movies: ['烬', '目', '水', '或', '图'],
            };
        },
        methods: {
            btnClick(m) {
                // 发射事件: 自定义事件
                this.$emit('m-click', m);
            },
        },
    };
    const app = new Vue({
        el: '.app',
        date: {},
        components: {
            cpn,
        },
        methods: {
            cpnClick(m) {
                console.log('cpnClick', m);
            },
        },
    });
</script>

当父组件将数据传输给子组件后,如果数据被操作后,需要将操作反馈给父组件,这时需要$emit()来触发事件,并将相应事件传给父组件,而父组件中通过v-on来监听子组件。

2.3 通信案例

通过v-model和父子间通信的原理实现,改变子组件的数据影响父组件的数据,形成一个循环,改变循环中的任何一个数据,所有数据都会随着改变。有点绕。

<div class="app">
    <cpn
        :number1="num1"
        :number2="num2"
        @num1change="number1change"
        @num2change="number2change"
    ></cpn>
</div>
<template id="cpn">
    <div>
        <h2>props:{{number1}}</h2>
        <h2>data:{{dnumber1}}</h2>
        <input type="text" :vualue="dnumber1" @input="num1Input" />
        <!-- <input type="text" v-model="dnumber1" /> -->

        <h2>props:{{number2}}</h2>
        <h2>data:{{dnumber2}}</h2>
        <input type="text" :vualue="dnumber2" @input="num2Input" />

        <!-- <input type="text" v-model="dnumber2" /> -->
    </div>
</template>
<script src="../vue/js/vue.js"></script>
<script>
    const cpn = {
        template: '#cpn',
        data() {
            return {
                dnumber1: this.number1,
                dnumber2: this.number2,
            };
        },
        props: {
            number1: Number,
            number2: Number,
        },
        methods: {
            num1Input(event) {
                this.dnumber1 = event.target.value;
                this.$emit('num1change', this.dnumber1);
                this.dnumber2 = this.dnumber1 * 100;
                this.$emit('num2change', this.dnumber2);
            },
            num2Input(event) {
                this.dnumber2 = event.target.value;
                this.$emit('num2change', this.dnumber2);
                this.dnumber1 = this.dnumber2 / 100;
                this.$emit('num1change', this.dnumber1);
            },
        },
    };
    const app = new Vue({
        el: '.app',
        data: {
            num1: 1,
            num2: 2,
        },
        components: {
            cpn,
        },
        methods: {
            number1change(value) {
                this.num1 = parseInt(value);
            },
            number2change(value) {
                this.num2 = parseInt(value);
            },
        },
    });
</script>

通过watch监听也可以实现

<div class="app">
    <cpn
        :number1="num1"
        :number2="num2"
        @num1change="number1change"
        @num2change="number2change"
    ></cpn>
</div>
<template id="cpn">
    <div>
        <h2>props:{{number1}}</h2>
        <h2>data:{{dnumber1}}</h2>
        <input type="text" v-model="dnumber1" />
        <!-- <input type="text" v-model="dnumber1" /> -->

        <h2>props:{{number2}}</h2>
        <h2>data:{{dnumber2}}</h2>
        <input type="text" v-model="dnumber2" />

        <!-- <input type="text" v-model="dnumber2" /> -->
    </div>
</template>
<script src="../vue/js/vue.js"></script>
<script>
    const cpn = {
        template: '#cpn',
        data() {
            return {
                dnumber1: this.number1,
                dnumber2: this.number2,
            };
        },
        props: {
            number1: Number,
            number2: Number,
        },

        watch: {
            dnumber1(newValue) {
                this.dnumber2 = newValue * 100;
                this.$emit('num1change', newValue);
            },
            dnumber2(newValue) {
                this.dnumber1 = newValue / 100;
                this.$emit('num2change', newValue);
            },
        },
    };
    const app = new Vue({
        el: '.app',
        data: {
            num1: 1,
            num2: 2,
        },
        components: {
            cpn,
        },
        methods: {
            number1change(value) {
                this.num1 = parseInt(value);
            },
            number2change(value) {
                this.num2 = parseInt(value);
            },
        },
    });
        </script>

3 父子访问

3.1 父访问子

使用$children可以访问子组件,但$children生成的是一个数组,当子组件较多时,下标可能会改变。而$refs可以绑定在一个组件上,使用起来更方便。

 <div class="app">
     <cpn></cpn>
     <cpn></cpn>
     <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('我是父组件的方法');
                 //第一种方法,通过$children,使用较少
                 // this.$children[0].showMessage();
                 // for (let i of this.$children) {
                 //     console.log(i.name);
                 // }
                 //第二种方法,通过$refs,使用较多
                 console.log(this.$refs.aaa.name);
             },
         },
         components: {
             cpn: {
                 template: '#cpn',
                 data() {
                     return {
                         name: '我是子组件的name',
                     };
                 },
                 methods: {
                     showMessage() {
                         console.log('我是子组件里的方法');
                     },
                 },
             },
         },
     });
 </script>

3.2 子访问父

children可以访问父组件,children可以访问父组件,root可以访问根组件

<div class="app">
    <cpn></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是子组件</h2>

        <ccpn></ccpn>
    </div>
</template>
<template id="ccpn">
    <div>
        <h2>我是子子组件</h2>
        <button @click="btnClick">按钮</button>
    </div>
</template>

<script src="js/vue.js"></script>
<script>
    const app = new Vue({
        el: '.app',
        data: { message: '起飞' },
        components: {
            cpn: {
                template: '#cpn',

                components: {
                    ccpn: {
                        template: '#ccpn',
                        methods: {
                            btnClick() {
                                //1.访问父组件
                                console.log(this.$parent);
                                //2.访问根组件
                                console.log(this.$root);
                            },
                        },
                    },
                },
            },
        },
    });
</script>

4 slot

4.1 匿名slot

slot标签中可以有其他标签,使用子组件标签内部没有其他内容时,会将slot内部的内容显示出来,子组件标签内部有内容时会覆盖slot标签内容的内容。

<div class="app">
    <cpn></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是子组件</h2>
        <slot><p>我是插槽</p></slot>
        <slot>插槽</slot>
    </div>
</template>
<script src="js/vue.js"></script>
<script>
    const app = new Vue({
        el: '.app',
        data: {},
        components: {
            cpn: {
                template: '#cpn',
            },
        },
    });
</script>

4.2 具名slot

使用name属性标记子组件中的slot标签,可以有选择的使用插槽。

<div class="app">
    <cpn><button slot="top">按钮</button></cpn>
</div>
<template id="cpn">
    <div>
        <slot name="top">
            <h2>起飞</h2>
        </slot>
        <slot name="bottom"></slot>
    </div>
</template>
<script src="js/vue.js"></script>
<script>
    const app = new Vue({
        el: '.app',
        data: {},
        components: {
            cpn: {
                template: '#cpn',
            },
        },
    });
</script>

4.3 作用域插槽

通过slot-scope连接到子组件,然后调取子组件中的数据slot.data

<div class="app">
    <cpn></cpn>
    <cpn>
        <template slot-scope="slot">
            <!-- <span v-for="m in slot.data">{{m}}</span> -->
            <span>{{slot.data.join('--')}}</span>
        </template>
    </cpn>
</div>
<template id="cpn">
    <div>
        <slot :data="pLanguages">
            <ul>
                <li v-for="m in pLanguages">{{m}}</li>
            </ul>
        </slot>
    </div>
</template>
<script src="js/vue.js"></script>
<script>
    const cpn = {
        template: '#cpn',
        data() {
            return {
                pLanguages: ['C', 'Java', 'C++', 'Go'],
            };
        },
    };
    const app = new Vue({
        el: '.app',
        data: {},
        components: {
            cpn,
        },
    });
</script>

接下来学习webpack