【Vue】Re06 组件化

将一个应用页面拆分成若干个可重复使用的组件

一、Vue的组件的使用步骤:

1、创建组件构造器

2、注册组件

3、使用组件

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

<div id="v">
    <!-- 3、使用组件 -->
    <component-instance></component-instance>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--<script src="./../dependencies/vue.js"></script>-->
<script type="text/javascript">

    /* 1、创建一个组件实例 创建需要注入一个JS对象,其中template属性是需要的html模板代码 */
    const componentInstance = Vue.extend({
        template : '<div><h3>This is a template instance</h3><p>template content</p></div>'
    });
    /* 2、将组件注册进Vue中 参数1是组件的标签名称,参数2是组件的实例对象 */
    Vue.component('component-instance', componentInstance);

    const v = new Vue({
        el : '#v'
    });
</script>

</body>
</html>

一些简写的使用:

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

<div id="v">
    <!-- 对模板不需要插入内容可以直接使用自闭和标签 -->
    <component-instance/>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--<script src="./../dependencies/vue.js"></script>-->
<script type="text/javascript">

    const componentInstance = Vue.extend({
        template :  /* 可以使用特殊符号``创建模板域编写模板 */
        `
        <div>
            <h3>This is a template instance</h3>
            <p>template content</p>
        </div>
        `
    });

    Vue.component('component-instance', componentInstance);

    const v = new Vue({
        el : '#v'
    });
</script>

</body>
</html>

二、全局组件和私有组件:

上述使用的就是全局组件,该组件不需要被Vue实例注册,任何Vue实例都可以使用该组件

/* 创建 */
const componentInstance = Vue.extend({
    template :
    `
    <div>
        <h3>This is a template instance</h3>
        <p>template content</p>
    </div>
    `
});
/* 注册 */
Vue.component('component-instance', componentInstance);

私有组件,注册就不是给Vue注册,而是注册到具体的Vue实例中

/* 创建 */
const componentInstance = Vue.extend({
    template :
        `
    <div>
        <h3>This is a template instance</h3>
        <p>template content</p>
    </div>
    `
});
/* 注册 */
const vm = new Vue({
    el : '#v',
    components : {
        componentInstance : componentInstance
    }
});

三、父子组件:

创建子组件和父组件 -> 子组件可以注册到父组件 ->  父组件再注册到Vue实例

Vue实例相当于同时把父子组件都注册进来了,调用父组件就相当于调用子组件

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

<div id="app">
    <parent-comp></parent-comp>
</div>
<!--<script src="./../dependencies/vue-min.js"></script>-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    let childComp = Vue.extend({
        template :
            `
            <div>
                <h1>This is a Sub Component</h1>
            </div>
            `
    });
    let parentComp = Vue.extend({
        template : <!-- 可以在这里嵌套使用组件 -->
        `
            <div>
                <h1>This is a Parent Component</h1>
                <child-comp></child-comp>
            </div>
        `,
        components : { /* 在父组件中也需要把组件注册进来 */
            childComp
        }
    });
    let vm = new Vue({
        el : '#app',
        components : { /* 再把父组件注册到这个Vue实例中 */
            parentComp
        }
    });
</script>
</body>
</html>

关于父子组件的一些问题:

如果单独使用子组件在Vue实例中,私有组件仍然需要在Vue实例中注册

反之全局组件则不需要注册就可以使用

注册全局组件的语法糖:

全局组件注册

/* 2.0.x 原版  */
const componentInstance = Vue.extend({
    template :
    `
    <div>
        <h3>This is a template instance</h3>
        <p>template content</p>
    </div>
    `
});
Vue.component('component-instance', componentInstance);
// ------------------------------------------------------
/* 3.0.x 方式 */
Vue.component('component-instance', {
    template : `
    <div>
        <h3>This is a template instance</h3>
        <p>template content</p>
    </div>
    `
})

私有组件注册:

/* 2.0.x 原版  */
const componentInstance = Vue.extend({
    template :
    `
    <div>
        <h3>This is a template instance</h3>
        <p>template content</p>
    </div>
    `
});
const v1 = new Vue({
    el : '#v',
    components : {
        componentInstance : componentInstance // 使用同名可以直接简写一个 componentInstance
    }
});
// -------------------------------------------------------------------------------------
/* 3.0.x 方式 */
const v2 = new Vue({
    el : '#v',
    components : {
        componentInstance : {
            template : `
            <div>
                <h3>This is a template instance</h3>
                <p>template content</p>
            </div>
            `
        }
    }
});

四、模板剥离解耦:

模板的HTML代码应该独立于组件存在,为了提升可读性和解耦合

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

<div id="v">
    <cpn></cpn>
    <cpn2></cpn2>
</div>

<!-- 写法一 -->
<script type="text/x-template" id="cpn">
    <div>
        <h3>这是模板1</h3>
        <p>这是内容</p>
    </div>
</script>
<!-- 写法二 -->
<template id="cpn2">
    <div>
        <h3>这是模板2</h3>
        <p>这是内容</p>
    </div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--<script type="text/javascript" src="../dependencies/vue.js"></script>-->
<script type="text/javascript">
    /* 把组件注册 */
    const cpn = Vue.component('cpn', {
        template:  '#cpn'
    });
    const cpn2 = Vue.component('cpn2', {
        template:  '#cpn2'
    });

    const v = new Vue({
        el : '#v',
    });
</script>

</body>
</html>

五、组件与Data函数

解决每个组件自己的属性,数据独立性问题

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

<div id="v">
    <cpn></cpn>
    
    <btn-test></btn-test> <!-- 保证数据的独立性 -->
    <btn-test></btn-test>
    <btn-test></btn-test>
    <btn-test></btn-test>

    <!-- 如果要共享直接写data属性中而不是函数 -->
    <hr>
    <btn-test2></btn-test2>
</div>

<template id="cpn">
    <div>
        <h3>这是组件</h3>
        <p>
            这是内容
            {{txt}}
        </p>
    </div>
</template>

<!-- 为什么组件必须使用函数传递数据? -->
<!-- 因为组件可以被允许在一个Vue实例中多次出现 -->
<!-- 组件自身所携带的属性数据是否为公用还是独立成一个问题,为了保证数据独立,Vue采取了这样的data函数方式解决 -->
<template id="btn-test">
    <div>
        <p>
            <button @click="decrement()"> - </button>
            {{counter}}
            <button @click="increment()"> + </button>
        </p>
    </div>
</template>

<template id="btn-test2">
    <div>
        <p>
            <button @click="decrement()"> - </button>
            {{counter}}
            <button @click="increment()"> + </button>
        </p>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--<script type="text/javascript" src="../dependencies/vue.js"></script>-->
<script type="text/javascript">

    /* 模板具有自己的HTML模板,且属性数据也是独立于Vue实例存在的 */
    const cpn = Vue.component('cpn', {
        template : '#cpn',
        /* Vue实例使用data对象,但是组件则使用data函数进行处理 */
        data () {
            return { /*  该函数要求必须返回类型为一个对象 */
                 txt : 'aaa'
            }
        }
    });

    const btnTest = Vue.component('btn-test', {
        template: '#btn-test',
        data () {
            return {
                counter : 0
            }
        },
        methods : {
            increment () {
                this.counter ++
            },
            decrement () {
                this.counter --
            }
        }
    })

    const btnTest2 = Vue.component('btn-test2', {
        template: '#btn-test2',
        data : {
            counter : 0
        },
        methods : {
            increment () {
                data.counter.counter ++
            },
            decrement () {
                data.counter.counter --
            }
        }
    })

    const v = new Vue({
        el : '#v',
    });
</script>
</body>
</html>

六、父子组件通信问题:

父组件 给 子组件传递消息 使用Props属性实现

子组件 给 父组件传递消息 使用$emit对象发送事件

Props属性传递数据:

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

<div id="v">
    <!-- 2、在父组件调用的时候 声明指令进行绑定 -->
    <component-instance
            v-bind:comp-message="message"
            v-bind:comp-movie-list="movieList"
    ></component-instance>
</div>

<template id="componentInstance">
    <div>
        <h3>这是子组件 {{compMessage}}</h3> <!-- 3、使用mustache语法引用 -->
        <p>子组件内容</p>
        <ul>
            <li v-for="movie in compMovieList">{{movie}}</li>
        </ul>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--<script type="text/javascript" src="../dependencies/vue.js"></script>-->
<script type="text/javascript">

    const componentInstance = Vue.extend({
        template : '#componentInstance',
        props : ['compMessage', 'compMovieList'], // 1、首先声明 props,数组里面要存放的是的组件的属性变量名称
    })

    const v = new Vue({
        el : '#v',
        data : {
            /* 父组件的消息数据 */
            message : 'hello',
            movieList : [
                '海王', '海贼王', '疾风传', 'eva'
            ]
        },
        components : {
            componentInstance : componentInstance
        }
    });
</script>

</body>
</html>

关于Props的几种写法:

1、原始数组写法:

props : ['property1', 'property2', 'property3', ...]

2、对象写法:

属性是变量名称,值是变量类型:

props : {
    property1 : dataType1,
    property2 : dataType2,
    property3 : dataType3,
},

类型除了基本类型之外,还可以是自定义类型

String 字符
Number 数字
Boolean 布尔
Array 数组
Object 对象原型
Date 日期
Function 函数
Symbol 
自定义类?

3、变量对象写法:

props : {
    ccc : { // 可以再对该属性继续细分
        type : Array, // 单独设置类型限制
        // default : [ // 设置默认值,在vue2.5.x版本以下可以这样写
        //     '111',
        //     '111',
        //     '111',
        // ],
        default() { // 建议使用函数进行返回
            return [
                '111',
                '111',
                '111',
            ];
        },
        required : true // 使用此组件必须传递此属性项
    },
    bBb : {
        type : String,
        default() {
            return 'null';
        }
    }
},

关于驼峰标识问题:

在组件使用时v-bind指令必须按照减号分割线处理,指令目前不支持驼峰命名

<div id="v">
    <!-- 2、在父组件调用的时候 声明指令进行绑定 -->
    <component-instance
            v-bind:comp-message="message"
            v-bind:comp-movie-list="movieList"
    ></component-instance>
</div>

子组件传递信息给父组件 $emit:

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

<div id="v">
    <!-- 2、在使用时将事件传递到父组件中, 而子组件使用$emit定义的自定义事件绑定父组件的函数 -->
    <cpn-ins v-on:cst="emitTaker"></cpn-ins>
</div>

<template id="componentInstance">
    <div>
        <!-- 通过遍历的每一个c元素获取对象 -->
        <button v-for="c in categories" @click="emitEventSenderTest(c)">{{c.name}}</button>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    const componentInstance = Vue.extend({
        template : '#componentInstance',
        data () {
            return {
                categories : [
                    {id : 'aaa', name : '分类1'},
                    {id : 'bbb', name : '分类2'},
                    {id : 'ccc', name : '分类3'},
                    {id : 'ddd', name : '分类4'},
                    {id : 'eee', name : '分类5'},
                ]
            }
        },
        methods : { /* 可以把当前对象用参数注入到事件的函数中来 */
            emitEventSenderTest (thisObject) {
                console.log('子组件发送 -> ' + thisObject);
                /* 1、通过组件的$emit对象发送事件给父组件 注意接受的事件名称定义使用小写加杠 */
                this.$emit('cst', thisObject); // 参数1自定义事件, 参数2传递当前对象
            }
        }
    });

    const v = new Vue({
        el : '#v',
        methods : { /* 3、定义监听接受函数 */
            emitTaker(thisObject) {
                console.log('父组件接收 -> ' + thisObject);
            }
        },
        components : {
            cpnIns : componentInstance
        },
    })
</script>
</body>
</html>

父子组件的双向绑定:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="v">
    <h3>实例变量 {{n1}} {{n2}}</h3>
    <cpn v-bind:num1="n1" v-bind:num2="n2" />
</div>

<template id="sss">
    <div>
        <h3>这是组件 {{num1}} {{num2}}</h3>
        <p>
            这是组件内容 <br> <!-- 当前这样的绑定对子组件有效,但是对父组件的变量绑定是无效的 -->
            num1 绑定 <input type="text" v-model="num1"> <br> 
            num2 绑定 <input type="text" v-model="num2">
        </p>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    const vm = new Vue({
        el : '#v',
        data : {
            n1 : 100,
            n2 : 50
        },
        components : {
            cpn : {
                template : '#sss',
                props : {
                    num1 : Number,
                    num2 : Number
                }
            }
        }
    })
</script>
</body>
</html>

结果查看:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="v">
    <h3>Vue实例{{n1}} {{n2}}</h3>
    <cpn
            v-bind:num1="n1"
            v-bind:num2="n2"
            v-on:num1get="getNum1"
            v-on:num2get="getNum2"
    />
</div>

<template id="sss">
    <div>
        <h3>这是组件 {{num1}} {{num2}}</h3>
        <p>
            这是组件内容 <br> <!-- 当前这样的绑定对子组件有效,但是对父组件的变量绑定是无效的 -->
<!--    num1 绑定 <input type="text" v-model="num1"> <br>-->
<!--    num2 绑定 <input type="text" v-model="num2">-->

            <!-- 解决方案是不使用v-model,拆分绑定 -->
<!--            num1 绑定 <input type="text" v-bind:value="dNum1" @input="dNum1 = $event.target.value"> <br>-->
<!--            num2 绑定 <input type="text" v-bind:value="dNum2" @input="dNum2 = $event.target.value">-->

            <!-- 将方法封装成函数 -->
            <h3>props:{{num1}}</h3>
            <h3>data():{{dNum1}}</h3>
            num1 绑定 <input type="text" v-bind:num1="dNum1" @input="num1Input"> <br>
            <h3>props:{{num2}}</h3>
            <h3>data():{{dNum2}}</h3>
            num2 绑定 <input type="text" v-bind:num1="dNum2" @input="num2Input">
        </p>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    const vm = new Vue({
        el : '#v',
        data : {
            n1 : 100,
            n2 : 50
        },
        methods : {
            getNum1(val) { // 父组件则处理接受
                // 传入的过程类型发生了转变 console.log(typeof val); 发现确实是String
                this.n1 = parseInt(val); // 强转了参数处理
            },
            getNum2(val) {
                this.n2 = parseInt(val);
            },
        },
        computed : {

        },
        components : {
            cpn : {
                template : '#sss',
                props : {
                    num1 : Number,
                    num2 : Number
                },
                data () {
                    return {
                        dNum1 : this.num1,
                        dNum2 : this.num2
                    }
                },
                methods : {
                    num1Input(eventObject) {
                        this.dNum1 = eventObject.target.value;
                        this.$emit('num1get', this.dNum1); // 利用外值传递结合emit事件发送出去
                    },
                    num2Input(eventObject) {
                        this.dNum2 = eventObject.target.value;
                        this.$emit('num2get', this.dNum2);
                    },
                },

            }

        }
    })
</script>
</body>
</html>

使用watch实现双向绑定:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="v">
    <h3>Vue实例{{n1}} {{n2}}</h3>
    <cpn
            v-bind:num1="n1"
            v-bind:num2="n2"
            v-on:num1get="getNum1"
            v-on:num2get="getNum2"
    />
</div>

<template id="sss">
    <div>
        <h3>这是组件 {{num1}} {{num2}}</h3>
        <p>
            这是组件内容 <br> <!-- 当前这样的绑定对子组件有效,但是对父组件的变量绑定是无效的 -->
<!--    num1 绑定 <input type="text" v-model="num1"> <br>-->
<!--    num2 绑定 <input type="text" v-model="num2">-->

            <!-- 解决方案是不使用v-model,拆分绑定 -->
<!--            num1 绑定 <input type="text" v-bind:value="dNum1" @input="dNum1 = $event.target.value"> <br>-->
<!--            num2 绑定 <input type="text" v-bind:value="dNum2" @input="dNum2 = $event.target.value">-->

            <!-- 将方法封装成函数 -->
            <h3>props:{{num1}}</h3>
            <h3>data():{{dNum1}}</h3>
            num1 绑定 <input type="text" v-model="dNum1"> <br> <!-- 这里的model绑定的可能是watch里面的函数 -->
            <h3>props:{{num2}}</h3>
            <h3>data():{{dNum2}}</h3>
            num2 绑定 <input type="text" v-model="dNum2">
        </p>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    const vm = new Vue({
        el : '#v',
        data : {
            n1 : 100,
            n2 : 50
        },
        methods : {
            getNum1(val) { // 父组件则处理接受
                // 传入的过程类型发生了转变 console.log(typeof val); 发现确实是String
                this.n1 = parseInt(val); // 强转了参数处理
            },
            getNum2(val) {
                this.n2 = parseInt(val);
            },
        },
        computed : {

        },
        components : {
            cpn : {
                template : '#sss',
                props : {
                    num1 : Number,
                    num2 : Number
                },
                data () {
                    return {
                        dNum1 : this.num1,
                        dNum2 : this.num2
                    }
                },
                watch : { /* 这里函数必须命名为返回变量一致 */
                    dNum1(newVal, oldVal) { /* 参数是一个新的值和一个旧值 */
                        this.$emit('num1get', newVal); // 利用外值传递结合emit事件发送出去
                    },
                    dNum2(newVal, oldVal) {
                        this.$emit('num2get', newVal);
                    },
                }
            }
        }
    })
</script>
</body>
</html>

父组件访问子组件:引用方式

Vue还提供了两个封装对象:

$children
$refs

$children演示案例:

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

<div id="v">
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
    <button @click="showChildComponent">查看子组件数组对象</button>
</div>

<template id="sss">
    <div>
        <h3>我是子组件</h3>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    const vm = new Vue({
        el : '#v',
        data : {
            message : 'Hello'
        },
        methods : {
            showChildComponent() {
                // console.log(this.$children);
                for (let $child of this.$children) { // 遍历可以得到每一个子组件
                    $child.logMessage();
                    console.log($child.name);
                }
            }
        },
        components : {
            cpn : {
                template : '#sss',
                data() {
                    return {
                        name : '子组件name'
                    }
                },
                methods: {
                    logMessage() {
                        console.log('sss');
                    }
                }
            }
        }
    })
</script>

</body>
</html>

$refs获取具体子组件实例

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

<div id="v">
    <cpn></cpn>
    <cpn></cpn>
    <cpn ref="aaa"></cpn> <!-- 使用$ref需要先声明标注 -->
    <cpn></cpn>
    <button @click="showChildComponent">查看子组件数组对象</button>
</div>

<template id="sss">
    <div>
        <h3>我是子组件</h3>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    const vm = new Vue({
        el : '#v',
        data : {
            message : 'Hello'
        },
        methods : {
            showChildComponent() {
                // 使用时引用具体值
                const aaa = this.$refs.aaa;
                console.log(aaa.name);
                aaa.logMessage();
            }
        },
        components : {
            cpn : {
                template : '#sss',
                data() {
                    return {
                        name : '子组件name'
                    }
                },
                methods: {
                    logMessage() {
                        console.log('sss');
                    }
                }
            }
        }
    })
</script>

</body>
</html>

子组件访问父组件和根组件也提供了一个对象:

this.$parent & this.$root

这里就不再赘述了

 

 

posted @ 2020-10-26 18:20  emdzz  阅读(636)  评论(0编辑  收藏  举报