五、Vue中的组件

一、TodoList

<!DOCTYPE html>
<html>
<head>
<title>vue</title>
<meta charset="utf-8">
<script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <input type="text" v-model="task"/>
        <button @click="handleClick">提交</button>
        <ul>
            <li v-for="(item,index) of tasks" :key="index">
                {{item}}
            </li>
        </ul>
    </div>
    <script>
        new Vue({
            el: "#root",
            data: {
                task: "",
                tasks: []
            },
            methods: {
                handleClick: function() {
                    this.tasks.push(this.task);
                }
            }
        });
    </script>
</body>
</html>

二、组件

  vue中的组件其实就是一个vue实例,反过来说,一个vue实例其实也是一个组件,当vue实例中没有定义template模板属性时,默认会将挂载点中的内容作为模板

1.全局组件

  vue的全局组件可通过Vue.component()来构建

Vue.component('todo-item', {
    template: '<li>item<li>'
});

  需要注意的是,在vue中,模板内只允许存在一个根节点,以在vue-cli构建的项目中,使用单文件组件编写模板为例

<template>
    <div>……</div>
    <div>……</div>
</template>
//有两个根节点所以报错
<template>
    <div>
        ……
        <div>……</div>
        ……
    </div>
</template>
//只有一个根节点所以没问题
2.局部组件

  vue的局部组件可以通过新建js对象,之后再在vue实例中通过components属性
注册组件,这样的组件只能在该vue实例管理的dom元素内部使用

var TodoList = {template: '<li>item</li>'}
new Vue({
    el: "#root",
    components: {
        'todo-item': TodoList
    }
});

三.传值

1.父向子传值

  父组件向子组件传值可以通过在使用组件的标签中通过属性名进行传递,在组件中通过props属性来接收,即在vue中,父组件向子组件传值是通过属性进行的

……
<todo-item content="xiaoming"></todo-item>
……
Vue.component('todo-item', {
    props: ['content'],
    template: '<li>{{content}}</li>'
});
……
2.若需要用到表达式,则可以通过:属性名或者v-bind:属性名来使用
……
<todo-item v-for="item of list" :content="item"></todo-item>
……
Vue.component('todo-item', {
    props: ['content'],
    template: '<li>{{content}}</li>'
});
……
3.单向数据流

  在Vue中,有单向数据流的概念,当父组件向子组件(非父子组件也一样)传值时,子组件接收到父组件传递的值后,不可以直接对该值直接进行修改,这是为了避免当传递的值为引用时,多处组件传入同一引用造成相互影响。
  如果需要对传进来的值进行修改,可以在组件中使用data属性制造一个副本,并将传值赋值给这个副本,之后再对副本进行操作

<div id="root">
        <v-content :number="0"></content>
    </div>
    <script>
        var content = {
            props: ['number'],
            template: '<div @click="compute">{{count}}</div>',
            data: function () {
                return {
                    count: this.number
                }
            },
            methods: {
                compute: function () {
                    this.count ++;
                }
            }
        };
        var vm = new Vue({
            el: '#root',
            components: {
                'v-content': content
            }
        }); 
    </script>
4.传值数据校验

   如果需要对父组件中传入的值进行校验,可以在prop属性中进行定义

  (1).简单校验
props: {
    age: Number,
    name: String
}
//当出现多类型同时校验时候可以用数组
props: {
    age: [number, String]
}
  (2).复杂检验
props: {
    name: {
        type: String,
        requied: true,
        default: 'OJBK',
        validator: function (value) {
            return (value.length > 5);
        }
    }
}
5.prop特性与非prop特性
  (1).prop特性是指当父组件通过属性传值,并且子组件中有使用prop属性正确接收传值时,传递的属性不会渲染到页面的元素当中
  (2).非prop特性是指当父组件通过属性传值,而子组件中没有使用prop属性正确接收传值时的情况,主要有两点特性,第一是在这种情况下无法调用传递的属性,否则会报错;第二是传递属性会被渲染到组件的模板标签中,例如
<body>
    <div id="root">
        <v-content :number="0"></content>
    </div>
    <script>
        var content = {
            template: '<div>hello</div>'
        };
        var vm = new Vue({
            el: '#root',
            components: {
                'v-content': content
            }
        }); 
    </script>
</body>

  此时页面中会渲染成<div number="0">hello</div>

6.子组件向父组件传值

  需要在子组件中通过this.$emit()方法进行发布,函数的第一个参数为事件名称,第二个参数为传递的内容,可以传递多个内容;然后在父组件中通过@事件名进行监听

<!DOCTYPE html>
<html>
<head>
    <title>vue</title>
    <meta charset="utf-8">
    <script src="./vue.js"></script>
</head>
<body>
    <div id="root">
        <input type="text" v-model="text">
        <button @click="handleSubmit">提交</button>
        <br/>
        <ul>
            <todo-list v-for="(item,index) of list"
                :key="index" :content="item" :index="index"
                @delete="handleDelete">
            </todo-list>
        </ul>
    </div>
    <script>
        Vue.component('todo-list',{
            props: ['content','index'],
            template: '<li @click="handleClick">{{content}}</li>',
            methods: {
                handleClick: function() {
                    this.$emit('delete',this.index);
                }
            }
        });
        new Vue({
            el: '#root',
            data: {
                list: [],
                text: ''
            },
            methods: {
                handleSubmit: function() {
                    this.list.push(this.text);
                    this.text = '';
                },
                handleDelete: function(index) {
                    this.list.splice(index,1);
                }
            }
        });
    </script>
</body>
</html>

四、组件绑定原生事件

  一般情况下,如果在vue组件中直接绑定事件,则该事件指的是自定义事件而非原生事件,即是通过$emit()方法进行广播的事件,是无法直接绑定事件触发父组件中的方法的,例如

<body>
    <div id="root">
        <counter @click="handleClick"></counter>
    </div>
    <script>
        Vue.component('counter', {
            template: '<div>hello</div>',
        });
        var vm = new Vue({
            el: '#root',
            methods: {
                handleClick: function () {
                    alert("hello");
                }
            }
        })
    </script>
</body>

  此时无论如何点击,都不会触发handleClick方法。想要在组件中直接绑定原生事件,只要在绑定的事件属性后面加上.native
  <counter @click.native="handleClick"></counter>
  这样当点击组件时就会触发事件,调用handleClick方法

五.发布订阅模式

  非父子组件之间传递数据,需要通过发布订阅模式(Bus/总线/观察者模式)来进行。
  1.首先需要向Vue的原型prototype挂载一个bus属性并给其赋值一个新建的vue实例,这样会使得之后的每一个Vue实例都会有一个bus属性指向同一个vue实例
  2.通过this.bus.$emit()来广播事件
  3.使用生命周期钩子,通过this.bus.$on()来监听bus所广播的事件,其中$on()函数的第一个参数为事件名称;第二个参数为调用的函数,该函数接收广播内容

<body>
    <div id="root">
        <child content="Schuyler"></child>  
        <child content="Cai"></child>
    </div>
    <script>
        Vue.prototype.bus = new Vue();
        Vue.component('child', {
            props: ['content'],
            template: '<div @click="handleClick">{{selfContent}}</div>',
            data: function () {
                return {
                    selfContent: this.content
                }
            },
            methods: {
                handleClick: function () {
                    this.bus.$emit('change', this.selfContent);
                }
            },
            mounted: function () {
                var this_ = this;
                this.bus.$on('change', function (msg) {
                    this_.selfContent = msg;
        //需要注意的是,在这个方法体内,this的引用已经发生了变
        //化,所以需要在方法体外先定义好组件实例的this引用
                });
            }
        });
        var vm = new Vue({
            el: '#root'
        });
    </script>
</body>

六、插槽

  在组件中,当父组件需要向子组件传递dom元素时,如果通过属性的方式进行传递,并在子组件中直接接收使用会将父组件传递的dom元素作为一个字符串来使用,例如

<body>
    <div id="root">
        <v-content content="<p>text</p>"></content>
    </div>
    <script>
        Vue.component('v-content',{
            props: ['content'],
            template: '<div>{{content}}</div>'
        })
        var vm = new Vue({
            el: '#root',
        }); 
    </script>
</body>

  这样会在页面上显示为<p>text</p>,要解决这个问题,可以在组件模板div标签(必须是div)中添加属性 v-html="this.content"

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

  虽然问题解决了,但是如果父组件需要传递大量的dom元素,若通过属性的方式来传递,代码会变得十分难以阅读,此时我们可以使用Vue提供的组件的插槽

1.插槽

  使用插槽的方法十分简单,首先直接在组件标签内部编写需要传递的dom元素,之后在对应的组件模板中通过slot标签来接收即可

<body>
    <div id="root">
        <v-content>
            <p>text</p>
        </v-content>
    </div>
    <script>
        Vue.component('v-content',{
            template: '<div><slot>默认值</slot></div>'
        })
        var vm = new Vue({
            el: '#root',
        }); 
    </script>
</body>
2.具名插槽

  当需要传递多个dom元素时,有时会需要根据模板的内容去调整传递的dom元素的位置,这个时候可以使用具名插槽。只需要在父组件传递的标签中添加slot="name"属性,在对应的组件模板的slot标签中使用name属性来接收

<body>
    <div id="root">
        <v-content>
            <p slot="one">text-one</p>
            <p slot="two">text-two</p>
        </v-content>
    </div>
    <script>
        Vue.component('v-content',{
            template: '<div>'
                        +'<slot name="one">默认值one</slot>'
                        +'<p>hello</p>'
                        +'<slot name="two">默认值two</slot>'
                    +'</div>'
        })
        var vm = new Vue({
            el: '#root',
        }); 
    </script>
</body>
3.作用域插槽

  一般情况下,如果我们使用组件标签去遍历一个对象,会直接进行遍历,并在组件的模板中展示遍历的内容。但有些时候,可能会在不同场景下使用到这个组件,这个时候我们希望遍历时展示的内容会根据不同的使用场景而不同,即遍历后所展示的内容是由父组件来决定的,这时我们可以使用作用域插槽
  (1).在模板中使用slot标签进行遍历,并使用:属性名=表达式来给父组件传值
  (2).在父组件内使用template标签(固定模式),并使用slot-scope="props"来接收封装了子组件中传递过来的所有数据的对象(props不是固定的,可以换个名字)

3.在template标签内编写dom元素展示相应内容
<body>
    <div id="root">
        <v-content>
            <template slot-scope="props">
                <li>{{props.item}}</li>
            </template>
        </v-content>
    </div>
    <script>
        Vue.component('v-content',{
            data: function () {
                return {
                    list: [1, 2, 3, 4]
                }
            },
            template: '<div>'
                        +'<slot v-for="item of list" :item=item></slot>'
                     +'</div>'
        })
        var vm = new Vue({
            el: '#root',
        }); 
    </script>
</body>

七、细节

1.is属性

  在H5中规定,table标签中需要写tbody,tbody标签中需要写tr,类似的还有ul、ol、select等标签,当在这些标签中调用vue组件时会出现一些bug,例如

<body>
    <div id="root">
        <table>
            <tbody>
                <row></row>
                <row></row>
                <row></row>
            </tbody>
        </table>
    </div>
    <script>
        Vue.component('row', {
            template: '<tr><td>this is a row</td></tr>'
        })
        var vm = new Vue({
            el: '#root'
        });
    </script>
</body>

  在上述代码中,渲染到页面时,row组件下的模板会与table标签同级,而不是在tbody标签内,同样的问题也可能出现在select、ul、ol等标签中。解决这个问题,可以使用Vue提供的is属性

<table>
    <tbody>
        <tr is='row'></tr>
        <tr is='row'></tr>
        <tr is='row'></tr>
    </tbody>
</table>
2.data属性

在vue中,在非根组件,即子组件中定义data属性时,data必须是一个返回一个对象的函数,返回的对象中包含所使用的数据。这样的目的是为了在重复使用子组件数据的时候,每个子组件中的data都是一个新返回的数据对象,避免出现多个子组件中互相影响数据的情况

<body>
    <div id="root">
        <table>
            <tbody>
                <tr is='row'></tr>
                <tr is='row'></tr>
                <tr is='row'></tr>
            </tbody>
        </table>
    </div>
    <script>
        Vue.component('row', {
            data: function () {
                return {
                    content: 'this is content'
                }
            },
            template: '<tr><td>{{content}}</td></tr>'
        })
        var vm = new Vue({
            el: '#root'
        });
    </script>
</body>
3.ref

  ref是Vue提供的来获取引用的属性,在标签中添加ref属性之后,可以通过
this.$refs.refValue来获取引用。
  (1).当在div等常规标签中使用ref时,获取的是这个dom对象的引用

<body>
    <div id="root">
        <div ref='hello'
            @click='handleClick'
        >
            Hello Vue
        </div>
    </div>
    <script>
        var vm = new Vue({
            el: '#root',
            methods: {
                handleClick: function () {
                    alert(this.$refs.hello.innerHTML);
                }
            }
        });
    </script>
</body>

  (2).当在vue组件中使用ref属性时,获取的是整个组件实例的引用

<body>
    <div id="root">
        <counter ref="one" @sum="handleCount"></counter>
        <counter ref="two" @sum="handleCount"></counter>
        <div>{{sum}}</div>
    </div>
    <script>
        Vue.component('counter', {
            template: '<div @click="handleClick">{{number}}</div>',
            data: function () {
                return {
                    number: 0
                }
            },
            methods: {
                handleClick: function () {
                    this.number++;
                    this.$emit('sum');
                }
            }
        });
        var vm = new Vue({
            el: '#root',
            data: {
                sum: 0
            },
            methods: {
                handleCount: function () {
                    this.sum = this.$refs.one.number + 
                        this.$refs.two.number;
                }
            }
        })
    </script>
</body>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值