深入理解vue组件

一、组件使用过程中的细节问题

1.使用 is 解决H5标签的小bug(用 is='组件名' 解决)

好处:这样的写法一方面可以使用组件,另一方面也符合H5的规范。

/*创建组件*/
Vue.component('row',{
    template:'<tr><td>this is a row</td></tr>';
});
/*vue实例接管#root*/
new Vue({
    el:'#root'
});
<div id='root'>
    <table>
        <tbody>
            <row></row> <!-- 如果直接写组件名字,源代码渲染中会出错,<tr>标签会成为table的兄弟元素,-->
            <tr is='row'></tr> <!--正确写法:正常写<tr>标签,然后 is='row'(组件名)-->
            <tr is='row'></tr>
          </tbody>
      </table>
</div>

2.在子组件中定义 data 时,data 必须是个有返回值的函数,不能向父组件中一样定义。

原因:子组件需要被网页中的元素多次使用,通过一个函数返回一个对象的目的,就是让每个子组件都拥有一个独立的数据存储,就不会出现多个子组件相互影响的问题。

Vue.component("row",{   //组件名row
    data:function(){  //函数
        return {          //有返回值
            content:'this is a row'
        }
    },
    template='<li>{{content}}</li>'   //模板
});

3.获取DOM节点(vue是面向数据的开发,极少操作DOM)

用法:

  1.在标签中添加  ref='ref名字'    例如:ref='hello'

  2.使用Vue内置属性获取,$.refs.ref名字  例如:$refs.hello

当写在div标签中:使用 this.$refs.hello 获取的是 DOM节点

当写在组件中:使用 this.$refs.hello 获取的是子组件的引用

<div ref='dv' @click='dvClick'> ref 可以获取DOM节点 </div>
new Vue({
    el:'root',
     methods:function(){
          dvClick:function(){
               console.log(this.$refs.dv);   //<div> ref 可以获取DOM节点</div>
          }
      }
}

4.案例:计数器

<div class="root">
    <row @change='sumChange' ref='num1'></row>  <!--第二步:监听子组件的触发事件,一旦触发事件,就执行sumClick方法-->
    <row @change='sumChange' ref='num2'></row>  <!--第二步:监听子组件的触发事件,一旦触发事件,就执行sumClick方法-->
    <div>{{total}}</div>
</div>
View Code
// 全局子组件
Vue.component('row', {
    data: function() {
        return {
            number: 0
        }
    },
    template: "<div @click='spanClick'>{{number}}</div>",
    methods: {
        spanClick: function() {
            this.number++;
            this.$emit("change"); //第一步:子组件向父组件触发一个名叫change的事件
        }
    }
});
// 实例组件
new Vue({
    el: '.root',
    data: {
        total: 0
    },
    methods: {
        sumChange: function() {
            //console.log(this.$refs.num1.number);//获取子组件中number的值
            this.total = this.$refs.num1.number + this.$refs.num2.number;
        }
    }
});
View Code

二、父子组件的数据传递

1.父组件向子组件传递数据(属性)

父组件通过属性的方式向子组件中传递数组

<div count='0'> count后面跟的0 是字符串</div>
<div :count='2'> :count后面跟着的2 是数字,因为:count后面的是表达式</div>

子组件使用props接收父组件传递过来的数据

var counter={
   props:['count'],   //接收父组件传递过来的数据
   template:'<div>{{count}}</div>' 
};

注意:

有一个隐形规定:单向数据流

在vue中有单向数据流的概念:父组件可以随意向子组件传递数据,但是子组件不能修改父组件传递过来的值。

设置单向数据流,是为了防止,如果父组件传递过来的是对象形式的,在子组件中修改后,如果再被其他子组件引用,也会把其他子组件的数据一起更改了。

解决方式:

把父组件传递过来的值,赋值给子组件 data 中,找个数据接收。

var counter={
    props:['count'], //接收父组件传递过来的数据
    data:function(){
        return {
            number:this.count   //把父组件的值赋值给子组件的data,此时再修改,不会影响父组件中的值
        }
    },
    template:'<div @click="dvClick">{{number}}</div>',
    methods:{
        dvClick:function(){
            this.number++;
        }
    }
});
View Code

2.子组件向父组件中传递数据(通过事件传值)

<div id='root'>
    <counter :count='3' @change='changeTotal'></counter><!--第二步:监听change事件,一旦触发,就执行changeTotal方法-->
    <counter :count='2' @change='changeTotal'></counter>
    <div>{{total}}</div>
</div>
View Code
var counter={
    props:['count'],
    data:function(){
        return {
            number:this.count
        }
    },
    template:'<div @click="dvClick">{{number}}</div>',
    methods:{
        dvClick:function(){
            this.number++;
            this.$emit('change',1);  //第一步:每次点击时向外触发一个change事件,可以携带多个参数
        }
    }
});
new Vue({
  el:'root',
  data:{
    total:5
  },
  components:{  //把局部组件在实例中声明
    counter:counter
  },
  methods:{
    changeTotal:function(step){  //实现子组件触发事件,父组件监听到后的处理函数
      this.total+=step;
    }
  }
});
View Code

三、组件参数校验与非props特性

如果 子组件 要校验 父组件 参数 类型则:

props: {
    //content:String   //校验参数 content 是否是 String 类型
    //content: [String, Number] //校验参数 content 是否为 Sting 或 Number中的一种类型
    content: {
        type: String, //类型
        required: false, //false:有没有都可以,true:必须有content这个变量
        default: '默认文本内容', //默认文本内容,如果有content属性,则显示content的内容
        validator: function(value) { //对传入的值校验
            return value.length > 5; //true 判断值的长度是否大于5
        }
    }
}
View Code

props特性:

1.要求父组件传参数,子组件要接收,然后可以在子组件中直接使用

2.props特性,不会将属性显示在DOM的标签之中

非props特性:

1.父组件中定义了参数,子组件中没有接收,此时父组件中定义的参数就是非props特性,非props特性的内容,也就无法在子组件中使用。

2.非props特性的元素会展示在子组件最外层的DOM标签的HTML属性里面

四、给组件绑定原生事件(.native)

方式一:给子组件绑定事件,通过父组件监听触发事件
缺点:代码编写太麻烦
<child @click='childClick'></child><!--在组件中定义的事件属于自定义事件,不是原生事件-->
<!--方式一:给子组件绑定事件,通过父组件监听触发事件-->
<child @change='clickEvent'></child><!--监听子组件中是否触发了change-->
// 方式一:子组件绑定原生事件:直接在编写模板中绑定,通过向父组件触发事件达到效果,另外父组件中必须监听子组件的change事件有没有被触发
Vue.component=({
    template:'<div @click='childClick'>此时绑定的是原生事件</div>,
    methods:{
        childClick:function(){
            this.$emit('change');//通过childClick的点击,向父组件中触发一个名叫 change 的事件
        }
    } 
});

方式二:

更改监听事件的指向(.native)

<child @click.native='childClick'></child><!--此时监听的不再是子元素的自定义事件,而是父组件的原生事件-->
// 方式二:更改监听事件的指向(native)
Vue.component=({
    template:'<div>child</div>',
});
new Vue({
    el:'#app',
    methods:{
        childClick:function(){
            console.log('通过子组件的@click.native可以直接调用此方法');
        }
    }
});

五、非父子组件之间传值

也叫作Bus、总线、发布订阅模式、观察者模式

<div id='root'>
    <child content='Dell'></child>
    <child content='Lee'></child>
</div>
Vue.prototype.bus=new Vue();  //vue原型属性bus,指向vue的实例,任何一个组件和实例中都有bus的属性
Vue.component({
    props:{
        content:String   //判断接收的数据是否是String类型
    },
    data:{
        childContent:this.content
    },
    template:'<div @click="dvClick">{{childContent}}</div>',  //定义模板
    methods:{
        dvClick:function(){
            this.bus.$emit('change',this.childContent); //通过bus向整个vue触发一个change事件,并携带一个this.childContent的数据(此时其他子组件想使用这个数据,应该在组件中设置监听)
        }
    },
    //生命周期钩子:组件被挂载的时候执行的函数
    mouted:function(){
        var that=this;
        this.bus.$on('change',function(msg){
            that.content=msg;
    });
});
new Vue({
    el:'#root'
});
View Code

六、Vue中的插槽(slot)

方便向子组件中传递DOM元素

<div class='root'>
    <child></child><!--子组件中没有DOM结构-->
</div>

Vue.component=('child',{
    template:`<div>
        <p>子组件内容</p>
        <slot>默认内容</slot>   /* 但是模板中定义了<slot>,则会显示插槽中默认文字 */
    </div>`
});

显示效果:

<div class='root'>
    <p>子组件内容</p>
     默认内容
</div

具体使用方法:

<div class='root'>
    <pages>
        <div class='header' slot='header'>header</div><!--向子组件中插入DOM节点-->
        <div class='footer' slot='footer'>footer</div><!--向子组件中插入DOM节点-->
    </pages>
</div>
new Vue({
    el:'.root'
});
Vue.component('pages',{
     template:`<div>
                         <slot name='header'>Header</slot> //name 指定显示哪个DOM结构,和DOM中的 slot 相呼应
                         <div class='content'>Content</div>  //原来子组件中要显示的内容
                         <slot name='footer'>Footer</slot>
                      </div>`
});
View Code

七、Vue中的作用域插槽

 子组件模板可能在不同的地方被调用,不希望模板的样式被child给固定,希望模板样式由父组件告诉我们应该使用哪种样式

<div class='app'>
    <child>
        <template slot-scope='props'>
            <h1>{{props.item}}</h1> <!--下面ul中的循环内容,父元素使用 h1 标签显示-->
        </template>
    </child>
</div>
/*
父组件调用子组件时,给子组件传了一个插槽(作用域插槽),作用域插槽必须是<template>开头结尾的标签 ,同时作用域插槽要声明,要从子组件中接收的数据都放在哪(props)还告诉子组件一个模板的信息,接收到props后以什么形式展示。

何时使用:当子组件做循环,或者某一部分由外部传递进来的时候,这时候就是用作用域插槽。

子组件可以向父组件的插槽中插入数据。父组件传递过来的插槽如果想接收这个数据,必须在外层使用一个template ,同时通过  slot-scote 对应的名字(props)来接收子组件传递过来的所有数据

子组件传了一个item 给父组件,在父组件的作用域插槽中就可以接收到这个item。
*/
Vue.component('child',{
    data:function(){
        return {
            list:[1,2,3,4,5]
        }
  },
    template:`<div>
        <ul>
            <slot
                v-for='item of list'
                :item=item
            ></slot>
        </ul>
    </div>`
});

八、动态组件<component>与v-once

v-once:会把数据存放在内容(提高加载静态数据的速度)

template:'<div v-once>数据会被存放到内存中</div>'

动态组件:会根据 is 里面的数据的变化,自动加载不同的组件(底层在切换时,会先销毁一个组件,然后展示另外一个组件(消耗性能))

<div class='root'>
    <component :is='type'></component>
</div>
Vue.component("child-one",{
    template:"<div>child-one</div>"
});
Vue.component("child-two",{
    template:"<div>child-two</div>"
});
new Vue({
    el:'#root',
    data:{
         type:'child-one'
    },
    methods:{
        btnClick:function(){
            this.type=(this.type==='child-one'?'child-two':'child-one');
        }
    }
});
原文地址:https://www.cnblogs.com/qtbb/p/12727877.html