深入理解 Vue 组件

萝らか妹 提交于 2020-02-06 15:51:13

深入理解 Vue 组件


 组件使用中的细节点

使用 is 属性,解决组件使用中的bug问题 

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 
 4 <head>
 5     <meta charset="UTF-8">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <meta http-equiv="X-UA-Compatible" content="ie=edge">
 8     <title>组件使用中的细节点</title>
 9     <script src="./vue.js"></script>
10 </head>
11 
12 <body>
13     <div id="root">
14         <table>
15             <tbody>
16                 <!-- H5编码规范要求,tbody内必须是tr,因此row组件不能用,会产生bug,
17                 因此 is 关键字起到了很好的作用,将此时的 tr 标签等于我们创建的 row 子组件。
18                 完美解决了既要使用组件永不会影响H5编码规范的问题 
19                 不仅仅是table标签,ul  ol  select 标签都有相同的问题。-->
20                 <tr is="row"></tr>
21                 <tr is="row"></tr>
22                 <tr is="row"></tr>
23             </tbody>
24         </table>
25     </div>
26 
27     <script>
28         // 创建全局子组件
29         Vue.component('row',{
30             template:"<tr><td>this is a row</td></tr>"
31         })
32 
33         var vm = new Vue({
34             el:"#root",
35 
36         })
37     </script>
38 </body>
39 
40 </html>

 

  

子组件定义data数据,data必须是个函数 

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 
 4 <head>
 5     <meta charset="UTF-8">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <meta http-equiv="X-UA-Compatible" content="ie=edge">
 8     <title>Document</title>
 9     <script src="./vue.js"></script>
10 </head>
11 
12 <body>
13     <div id="root">
14         <table>
15             <tbody>
16                 <tr is="row"></tr>
17                 <tr is="row"></tr>
18                 <tr is="row"></tr>
19             </tbody>
20         </table>
21     </div>
22     <script>
23         // 子组件
24         Vue.component("row", {
25             // 子组件定义数据data的方法必须是一个函数返回,不能像根对象一样
26             data: function () {
27                 return {
28                     content: 'this is a row'
29                 }
30             },
31             template: '<tr><td>{{content}}</td></tr>'
32         })
33 
34         var vm = new Vue({
35             el: "#root",
36         })
37     </script>
38 </body>
39 
40 </html>

   

 Vue中的 ref 引用的内容

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ref</title>
    <script src="./vue.js"></script>
</head>

<body>
    <div id="root">
        <!-- 在vue当中,可以通过ref获取dom节点 -->
        <div ref='hello' @click="handleClick">hello world</div>
    </div>
    <script>

        var vm = new Vue({
            el: "#root",
            methods: {
                handleClick: function () {
                    // 获取dom中的内容
                    // this.$refs.hello 获取ref=hello的dom节点
                    alert(this.$refs.hello.innerHTML)
                }
            }
        })
    </script>
</body>

</html>

   

vue实现计数器功能

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>计数器功能</title>
    <script src="./vue.js"></script>
</head>

<body>
    <div id="root">
        <counter ref='one' @change="handleChange"></counter>
        <counter ref='two' @change="handleChange"></counter>
        <!-- 求和 -->
        <div>{{total}}</div>
    </div>
    <script>
    // 子组件
        Vue.component('counter', {
            template: '<div @click="handleClick">{{number}}</div>',
            data: function () {
                return {
                    number: 0
                }
            },
            methods: {
                handleClick: function () {
                    this.number++
                    // 向外发送change事件
                    this.$emit('change')
                }
            }
        })

        var vm = new Vue({
            el: "#root",
            data: {
                total: 0
            },
            methods: {
                handleChange: function () {
                    this.total = this.$refs.one.number + this.$refs.two.number
                    // console.log(this.$refs.one.number)
                    // console.log(this.$refs.two.number)
                }
            }
        })
    </script>
</body>

</html>

  

 父子组件间传值

 父组件向子组件传递数据

 

  1. 父组件通过属性的形式向子组件传递数据。
  2. 父组件可以随意的向子组件传递参数。
  3. 但是子组件绝对不能去修改父组件传进来的参数(单向数据流)。

 

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 
 4 <head>
 5     <meta charset="UTF-8">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <meta http-equiv="X-UA-Compatible" content="ie=edge">
 8     <title>父子间组件传值</title>
 9     <script src="./vue.js"></script>
10 </head>
11 
12 <body>
13     <div id="root">
14         <!-- 父组件都是通过属性的形式向子组件传递数据 -->
15         <counter :count="1"></counter>
16         <counter :count="2"></counter>
17     </div>
18 
19     <script>
20 
21         // 局部组件
22         var counter = {
23             // props 表示子组件接受父组件的内容
24             props: ['count'],
25             data: function () {
26                 return {
27                     // 子组件自己的data number值
28                     number:this.count
29                 }
30             },
31             template: "<div @click='handleClick'>{{number}}</div>",
32             methods: {
33                 // 点击累加方法
34                 handleClick: function () {
35                     // 父组件可以随意的向子组件传递参数
36                     // 但是子组件绝对不能去修改父组件传进来的参数 单向数据流
37                     // 因此修改自己的Number值
38                     this.number++
39                 },
40             }
41         }
42 
43         var vm = new Vue({
44             el: "#root",
45             // 注册局部组件.
46             components: {
47                 counter: counter,
48             }
49         })
50     </script>
51 
52 </body>
53 
54 </html>

  

子组件向父组件传值

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>父子间组件传值</title>
    <script src="./vue.js"></script>
</head>

<body>
    <div id="root">
        <!-- 父组件都是通过属性的形式向子组件传递数据 -->
        <counter :count="3" @change="handleChange"></counter>
        <counter :count="2" @change="handleChange"></counter>
        <div>{{total}}</div>
    </div>

    <script>

        // 局部组件
        var counter = {
            // props 表示子组件接受父组件的内容
            props: ['count'],
            data: function () {
                return {
                    // 子组件自己的data number值
                    number:this.count
                }
            },
            template: "<div @click='handleClick'>{{number}}</div>",
            methods: {
                // 点击累加方法
                handleClick: function () {
                    // 父组件可以随意的向子组件传递参数
                    // 但是子组件绝对不能去修改父组件传进来的参数 单向数据流
                    // 因此修改自己的Number值
                    this.number++
                    // 向外触发事件,后可以跟参数
                    this.$emit('change',1)
                },
            }
        }

        var vm = new Vue({
            el: "#root",
            data:{
                total:5,
            },
            // 注册局部组件.
            components: {
                counter: counter,
            },
            methods:{
                handleChange:function(step){
                    // step = 1  步长为2
                    // 求和等于默认值+点击一下的步长
                    this.total  += step
                }
            }
        })
    </script>

</body>

</html>

   

组件参数校验与非props特性

组件参数校验   

  组件参数校验是指:父组件向子组件传递参数的时候,子组件有权向父组件提出参数的形式和要求,并检验父组件传进的参数是否合乎要求。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>组件参数校验与非props特性</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <!-- <child :content="123"></child> -->
        <child content="123"></child>
    </div>

    <script>

        Vue.component('child',{
            props:{
                // content:String,  // 子组件接收到的content数据,必须是一个字符串类型
                // content:[Number,String]  // 子组件接收到的content数据,要么是字符串,要么是数字
                content:{ // 接收content
                    type:String, //类型type必须是string
                    // required:true, // 表示content必需传
                    // default:'default value', // 如果没有传进来,默认显示这个
                    validator:function(value){ // 校验器校验传入的内容长度必须大于5
                        return (value.length>5)
                    },
                }
            },
            template:'<div>{{content}}</div>',
        })

        var vm = new Vue({
            el:"#root",
            
        })
    </script>

</body>
</html>

  

非 Props 特性

Props 特性是指:当你的父组件使用子组件的时候通过属性向子组件传值的时候,恰好子组件里面声明了对父组件传递过来的属性的接收。

   

非Props 特性是指:父组件向子组件传递了一个属性,但是子组件并没有props接收的内容,也就是说,子组件并没有声明要接受父组件传递进来的属性。

  

非Props 特性特点一:如果子组件没人接收父组件传进的属性,则子组件不能使用父组件传进的值。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>非 Props 特性</title>
    <script src="./vue.js"></script>
</head>

<body>

    <div id="root">
        <!-- <child :content="123"></child> -->
        <child content="hell"></child>
    </div>

    <script>

        Vue.component('child', {
            // props: {
            //     content: { // 接收content
            //         type: String, //类型type必须是string
            //     }
            // },

            // content 找不到,就会报错
            template: '<div>{{content}}</div>',
        })

        var vm = new Vue({
            el: "#root",

        })
    </script>
</body>

</html>

  

非Props 特性特点二:DOM中会保留父组件传递给子组件的属性标识

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>非 Props 特性</title>
    <script src="./vue.js"></script>
</head>

<body>

    <div id="root">
        <!-- <child :content="123"></child> -->
        <child content="hell"></child>
    </div>

    <script>

        Vue.component('child', {
            // props: {
            //     content: { // 接收content
            //         type: String, //类型type必须是string
            //     }
            // },

            template: '<div>hello</div>',

            // content 找不到,就会报错
            // template: '<div>{{content}}</div>',
        })

        var vm = new Vue({
            el: "#root",

        })
    </script>
</body>

</html>

  

给组件绑定原生事件

很简单,在绑定事件的click后面加一个修饰符就行。

修饰符为 .native

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>给组件绑定原生事件</title>
    <script src="./vue.js"></script>
</head>
<body>
    
    <div id="root">
        <!-- 原生点击事件 -->
        <child @click.native="handleClick"></child>
    </div>

    <script>

        Vue.component('child',{
            template:'<div @click="handleChildClick">Child</div>', 
        })

        var vm = new Vue({
            el:"#root",
            methods:{
                handleClick:function(){
                    alert('click')
                }
            }
        })
    </script>

</body>
</html>    

  

非父子组件间的传值

情景分析

我们可以把一个网页拆分成很多个部分,每个部分就是我们代码中是我一个组件,如下面的一张图:

  

如果 1  2 层需要进行传值,则为父子组件之间的传值,通信方式在之前的内容讲到过。

    

如果 1  3 层进行传值,则为非父子组件间的传值,应该怎么办呢?

  

第一中方式:和父子组件间传值一样,一层一层的传递,第一层传给第二层,第二层在传给第三层,反之亦然。但是这种传值方式显然不方便太繁琐。

加入 3  3 层进行的非父子组件传值,又会是怎样的处理方法呢?

  

这种情况显然更加不适合层层传值,即第三层传给第二层,第二层传给第一层,第一层传给第二层,第二层传给第三层,累死了!代码变得非常的复杂。

非父子组件传值解决方法

第一种方法,我们可以使用 VUE 官方提供的一个数据层的框架,名字叫做 VUEX 来解决,但是使用有难度。

第二种方法,使用 发布订阅模式 来解决非父子组件的传值问题,在vue中叫做 总线机制

使用总线机制解决非父子组件传值问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>非父子组件间的传值(Bus|总线|发布订阅模式|观察者模式)</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <child content="Jayvee"></child>
        <child content="Wong"></child>
    </div>

    <script>

        Vue.prototype.bus = new Vue()

        Vue.component('child',{
            data:function(){
                return{
                    selfContent:this.content
                }
            },
            template:'<div @click="handleClick">{{selfContent}}</div>',
            props:{
                content:String,
            },
            methods:{
                handleClick:function(){
                    this.bus.$emit('change',this.selfContent)
                }
            },
            mounted:function(){
                var this_ = this
                this.bus.$on('change',function(msg){
                    this_.selfContent = msg
                })
            }
        })

        var vm =new Vue({
            el:"#root",
        })
    </script>

</body>
</html>

  

 VUE 中的插槽 - slot

 父组件通过传值的方式向子组件添加标签

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue中的插槽(slot)</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <child content="<p>wjw</p>"></child>
    </div>

    <script>
    
        Vue.component('child',{
            props:['content'],
            template:'<div><p>hello</p><div v-html="this.content"></div></div>'
        })

        var vm = new Vue({
            el:"#root",
        })

    </script>

</body>
</html>

  

使用插槽

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue中的插槽(slot)</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <child>
            <p>wjw</p>
        </child>
    </div>

    <script>
    
        Vue.component('child',{
            template:'<div><p>hello</p><slot>默认内容</slot></div>'
        })

        var vm = new Vue({
            el:"#root",
        })

    </script>

</body>
</html>

  

 传入header和footer

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue中的插槽(slot)</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <body-content>
            <div slot='header' class="header">header</div>
            <div slot='footer' class="footer">footer</div>
        </body-content>
    </div>

    <script>
    
        Vue.component('body-content',{
            template:`<div>
                <slot name='header'></slot>
                <div class="content">content</div>
                <slot name='footer'></slot>
                </div>`
        })

        var vm = new Vue({
            el:"#root",
        })

    </script>

</body>
</html>

  

 Vue中的作用域插槽

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue中的作用域插槽(slot)</title>
    <script src="./vue.js"></script>
</head>

<body>
    <div id="root">
        <child>
            <template slot-scope="props">
                <li>{{props.item}} -- hello</li>
            </template>
        </child>
    </div>

    <script>
        Vue.component('child', {
            data: function () {
                return {
                    list: [1, 2, 3, 4]
                }
            },
            template: `<div>
                <ul>
                    <slot v-for="item of list" :item=item></slot>
                </ul>
                </div>`
        })

        var vm = new Vue({
            el: "#root",
        })
    </script>

</body>

</html>

  

Vue的动态组件与 v-once 指令

 点击按钮实现两个组件显隐切换

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>VUE的动态组件与v-once指令</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <child-one v-if="type === 'child-one'"></child-one>
        <child-two v-if="type === 'child-two'"></child-two>
        <button @click="handleBtnClick">change</button>
    </div>

    <script>

        Vue.component('child-one',{
            template:"<div>child-one</div>"
        })

        Vue.component('child-two',{
            template:"<div>child-two</div>"
        })

        var vm = new Vue({
            el:'#root',
            data:{
                type:'child-one'
            },
            methods:{
                handleBtnClick:function(){
                    this.type = this.type === 'child-one'?'child-two':'child-one'
                },
            }
        })
    </script>

</body>
</html>

    

动态组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>VUE的动态组件与v-once指令</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <!-- component是vue自带的,表示动态组件 -->
        <component :is="type"></component>
        <!-- <child-one v-if="type === 'child-one'"></child-one>
        <child-two v-if="type === 'child-two'"></child-two> -->
        <button @click="handleBtnClick">change</button>
    </div>

    <script>

        Vue.component('child-one',{
            template:"<div>child-one</div>"
        })

        Vue.component('child-two',{
            template:"<div>child-two</div>"
        })

        var vm = new Vue({
            el:'#root',
            data:{
                type:'child-one'
            },
            methods:{
                handleBtnClick:function(){
                    this.type = this.type === 'child-one'?'child-two':'child-one'
                },
            }
        })
    </script>

</body>
</html>

 

V-once 节约性能,提高静态文件的展示效率

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>VUE的动态组件与v-once指令</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <!-- component是vue自带的,表示动态组件 -->
        <!-- <component :is="type"></component> -->
        <child-one v-if="type === 'child-one'"></child-one>
        <child-two v-if="type === 'child-two'"></child-two>
        <button @click="handleBtnClick">change</button>
    </div>

    <script>

        Vue.component('child-one',{
            template:"<div v-once>child-one</div>"
        })

        Vue.component('child-two',{
            template:"<div v-once>child-two</div>"
        })

        var vm = new Vue({
            el:'#root',
            data:{
                type:'child-one'
            },
            methods:{
                handleBtnClick:function(){
                    this.type = this.type === 'child-one'?'child-two':'child-one'
                },
            }
        })
    </script>

</body>
</html>

  

 

  

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!