Loading

7-组件详解

7.1 组件与复用

  • 注册
    • 全局注册
      Vue.component('my-component-name',{
          template:'<div>模板</div>'
      });
      
    • 局部注册
      var Chile={
          template:'<div>模板</div>'
      };
      var app=new Vue({
         el:'#app',
         components:{
             'my-component':Child
         }
      });
      
  • 组件可以使用的选项
    • template

    • data

      • 区别:必须是函数,然后把数据return
          data:function(){
              return{
                  message1:'组件内容',
                  message2:'另一个组件内容'
              }
          }
        
      • 注意:如果return的对象引用了外部对象,那么数据会共享,所以为了复用组件,需要给组件返回一个新的data独立
    • computed

    • methods

    • <table>内需要使用特殊的is属性挂在组件

7.2 使用props传递数据

组件间通信:
  • 正向传递数据:父组件向子组件传递数据或参数
    • 接受父级的数据(字符串)
    //props 为了 复用+个性化 组件?
    //需要驼峰命名的props名称转为短横分隔命名 (warningText->warning-text)
    //my-component 接受来自父级的数据(warning-text),并把他在组件模板中渲染(template)
          div#app
              my-component[warning-text="提示信息"]
              my-component[warning-text="警告信息"]
          
          Vue.component->'my-component'
              props:['warningText'],
              template:'<div>{{warningText}}</div>'
    
    • 动态绑定props的值,使用来自父级的动态数据(单向)
    // 数据来源 input-text: parentMessage --> 父组件绑定属性 bind:message-one: parentMessage --> 渲染 props:messageOne   div{{messageOne}}
      div#app
          input[type=text][v-model=parentMessage]
          my-component[:message-one=parentMessage]
          
      Vue.component -> 'my-component'
          props:['messageOne'],
          template:'<div>{{messageOne}}</div>'
      
      app -> new Vue
          el:'#app',
          data:{
              parentMessage:''
          }
    
    • 传输 非字符串类型数据(数组、对象、数字)时需要 bind
单向数据流

父组件数据变化时会传递给子组件,但是反过来不行,避免子组件无意中修改父组件的状态

两种需要改变prop的情况
  • 接收父组件传来的初始值

    //此例中 initCount 是父组件传来初始值的,之后只要维护 count就可以了
      div#app
          my-component[:init-count="1"]
          
      Vue.component-> 'my-component'
          props:['initCount'],
          template:'<div>{{count}}</div>',
          data:function(){
              return {
                  count:this.initCount
              }
          }
      
      var app -> new Vue
          el:'#app'
    
  • 作为需要被转变的原始值传入(这种情况用计算属性)

        //此例中是 width: 200 -> 200px 
            div#app
                my-component[:width="200"]
            
            Vue.component -> my-component
                props:['width']
                template:'<div :style="style">组件内容<div>'
                computed:{
                    style:function(){
                        return {
                            width:this.width+'px'
                        }
                    }
                }
    
  • props是对象或数组时,在子组件内改变会影响父组件。(因为是引用类型)

7.2.3 数据验证

一般组件提供给别人使用时,需要进行数据验证(prop)

  • prop数据类型验证示例
    • 数字类型
Vue.component -> my-component
    props:{
        //必须是数字类型
        propA:Number,
        //必须是字符串或数字类型
        propB:[String,Number],
        //布尔值,如果没有定义,默认值就是true
        propC:{
            type:Boolean,
            default:true
        },
        //数字,而且是必传
        propD:{
            type:Number,
            required:true
        },
        //如果是数组或对象,默认值必须是一个函数来返回
        propE:{
            type:Array,
            default:function(){
                return [];
            }
        },
        //自定义一个验证函数
        propF:{
           validator:function(value){
           return value>10;
        }
    }
    //type: String Number  Boolean  Object Array  Function
    //type 也可以是一个自定义构造器,使用 instanceof 检测
    //prop 验证失败,开发版本下会在控制台抛出一个警告

7.3 组件通信

img
7.3.1 自定义事件

  当子组件需要向父组件传递数据时,就要用到自定义事件。

  子组件用$emit()触发事件,父组件用$on()来监听子组件的事件。
??父组件也可以直接在子组件的自定义标签上使用 v-on监听子组件触发的自定义事件。

//子组件的: counter  &&  handleIncrease &&  handleReduce
//通过$emit传递给父组件: this.$emit('crease',this.counter) && this.$emit('reduce',this.counter)
//父组件:my-component[@crease="handleGetTotal"][@reduce="handleGetTotal"]
div#app
    p{总数:{{total}}}
    my-component[@increase="handleGetTotal"][@reduce="handleGetTotal"]
    
Vue.component('my-component',{
    template:'\
    <div>\
        <button @click="handleIncrease">+1</button>\
        <button @click="handleReduce">-1</button>\
    </div>',
    data:function(){
        return {
            counter:0
        }
    },
    methods:{
        handleIncrease:function(){
            this.counter++;
            this.$emit('increase',this.counter);
        },
        handleReduce:function(){
            this.counter--;
            this.$emit('reduce',this.counter);
        }
    }
});

var app ->  new Vue()
    el:'#app',
    data:{
        total:0
    },
    methods:{
        handleGetTotal:function(total){
            this.total=total;
        }
    }

//使用 .native 修饰符监听DOM事件
// <my-component v-on:click.native="handleClick"></my-component>
7.3.2 自定义组件使用v-model
  • 直接使用 v-model 绑定数据
//父组件   v-model=total
//app ->  data:total
//子组件: this.$emit('input',this.counter);
div#app
    my-component[v-model=total]

my-component
    template: button[@click=handleIncrease]
    data: -> -> -> counter:0
    methods:
        handleIncrease:function(){
            this.counter++;
            this.$emit('input',this.counter);
        }
//相当于 父组件:@input=handleIncrease
//             methods: handleIncrease(){...}
  • 创建自定义表单输入组件,进行数据双向绑定
    • 需要:接收一个value属性
    • 需要:在有新的value时触发input事件
    //实现父组件改变值: total--
    //子组件同步改变:counter--?
      div#app
          p{总数:{{total}}}
          my-component[v-model=total]
          button[@handleReduce]{-1}
       
      mycomponent
          template:'<input :value @input="updateValue">'
          methods:{
              //实际上这个根本没有运行?没有也行?
              updateValue:function(event){
                  console.log(this.value);
                  console.log(event.target.value);
                  this.$emit('input',event.target.value);
              }
          }
      
      app  -> new Vue
          data: 
              total
          methods:
              handleReduce
    
7.3.3 非父子组件通信
  • 使用一个空的Vue实例作为中央时间总线(bus).
div#app
    {{message}}
    <component-a></component-a>
script
    var bus = new Vue();
    
    component-a
        template:'<button @click="handleEvent"></button>',
        methods:
            handle:function(){
                bus.$emit('on-message','来自子组件的内容')
            }
    
    var app = new Vue
        data:  message ,
        mounted:funciton(){
            _this=this;
            bus.$on('on-message',function(msg){
                _this.message=msg;
            })
        }
//创建一个名为bus的空Vue实例
//全局定义组件 component-a
    //点击按钮 会触发事件 onmessage
//创建Vue实例 app
    //mounted钩子函数里监听 bus 的事件 on-message
//点击按钮,会通过bus把时间 on-message 发出去
//此时app就会接受到来自bus的事件,进而在回调里完成自己的业务逻辑
  • 父链
    (子组件使用 this.$parent可以直接访问该组件的父实例或组件,父组件可以使用this.$children访问它所有的子组件。并且可以无限的向上或向下访问。)

    • this.$parent
      div#app
          {{message}}
          component-b
      component-b
          template:'button[@click=handleEvent]'
          methods
              handleEvent:function(){
                  this.$parent.message="更改父实例的message"
              }
      app = new Vue
          el:'#app',
          data:{
              message:''
          }
    
    • this.$children
      • (使用特殊的属性ref为子组件指定一个索引名称)
      • this.$refs只在组件渲染完成后才填充,并且是非响应式的
      • 应该避免在模板或计算属性中使用 $refs
      div#app
          {{message}}
          button[@click="handleRef"]
          component-a[refs='comA']
      script
          component-a
              data
                  msg:'子组件内容'
          app
              el:'app',
              data:
                  message:''
              methods:
                  handleRef:function(){
                      this.message=this.$refs.comA.msg;
                  }
      

7.4 使用slot分发内容

当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到 slot, 这个过程叫作内容分发( transclusion)。

<slot>元素指定一个name后可以分发多个内容,具名Slot可以和单个Slot共存。

如果slot没有使用name属性,那么将作为默认slot出现。如果没有指定匿名的slot,父组件内多余的内容片段都将被抛弃。

子组件里的slot标签可以设置备用内容。

div#app
    my-component
        h3[slot="header"]
        p{匿名的slot}

script
    Vue.component  'my-component'
        p{一些其他内容}
        slot[name="header"]
        slot
        slot[name="footer"]{我有备用}
        
    app=new Vue   
7.4.4 作用域插槽

  特殊的slot,使用一个可以复用的模板替换已渲染元素。

div#app
    component-a
        {父组件的内容}
        template[scope="props"]
            {{props.message}}
script
    Vue.component  'component-a'
        slot[msg="来自子组件的内容"]
    
//子组件内,slot 元素使用 msg="xxx",将数据传到插槽上
//父组件使用 <template> 元素 的 scope="props" ,通过临时变量 props 访问来自子组件插槽的数据 msg。

  另一个例子

div#app
    my-list[:books=books]
        template[slot="book"][scope="props"]
            li{{props.bookName}}
    
script
    Vue.component  'my-list'
        props:{
            books:{
                type:Array,
                default:function{
                    return [];
                }
            }
        },
        template:`
            <ul>
                <slot v-for="book in books" :book-name="book.name" name="book"></slot>
            </ul>
        `

    app
        el:'app',
        data:{
            books:[
                {name:'第一本书'},
                {name:'第2本书'},
                {name:'third book'}
            ]
        }
// 数据由子组件提供,样式有父组件设置
7.4.5 访问slot

  使用$slots方法访问slot分发的内容。主要用于独立组件的开发。

div#app
    h3[slot=header]{标题}
    p{默认内容}
Vue.component  'my-component'
    template
        slot[name=header]
    
    mounted:function(){
        var header=this.$slots.header;
        var main=this.$slots.default;
        console.log(header);
        console.log(main[0].elm.innerHTML);
    }
    
app  el:'#app'

7.5 组件高级用法

7.5.1 递归组件

  组件在它的模板内可以递归地调用自己,只要给组件设置name选项。

可以用来开发一些位未知层级关系的独立组件,比如级连选择器和树形选择器。



7.5.2 内联模板

  给组件标签使用inline-template特性,组件就会把它的内容当成模板。

div#app
    child-component[inline-template]
        h3{在父组件中定义子组件的模板}
        {{message}}
        {{msg}}
        
script  
    Vue.component('child-component',{  })
        data:funciton(){
            return {
                msg:'子组件声明的数据'
            }
        }
    
    app
        el:'#app',
        data:{
            message:'父组件声明的数据'
        }

//使用 vue.js(开发模式), 模板内的父组件数据{{message}}不能渲染
//同名时,优先使用子组件的数据
7.5.3 动态组件
div#app
    component[:is="currentView")]
        button[@click="handleCurrentView('A')"]{切换到A}
        button[@click="handleCurrentView('B')"]{切换到B}
        button[@click="handleCurrentView('C')"]{切换到C}
script  
    app = new Vue()
        data:
            currentView:'comA'
        components:{
            'comA':{
                template:'<div>组件A</div>'
            },
            'comB':{
                template:'<div>组件B</div>'
            },
            'comC':{
                template:'<div>组件C</div>'
            }
        },
        methods:{
            handleCurrentView(component_):function(){
                this.currentView='com'+component_;
            }
        }
        
//多个模板在 componets 里
//根据值 currentView 来切换模板 
//需要一个用户能够改变 currentView 值的按钮。
7.5.4 异步组件

  如果组件过多时,一开始就把所有组件都加载是没必要的开销。所以,Vue.js 允许将组件定义为一个工厂函数,动态地解析组件。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果储存起来,用于后面的再次渲染。

Vue.component('my-component',function(resolve,reject){
        window.setTimeout(function(){
            resolve({
               template:'<div>我是异步渲染的</div>' 
            });
        },1000);
    }


//1秒后 渲染子组件

// JavaScript - 异步
// funciton test(resolve,reject){...}
// function todo(result){
//     return (new Promise(function(resolve,reject){...})
// }
// var p1 = new Promise(test)
//       .then(todo)
//       .then(function(result){...})
//       .catch(function(reason){...});

7.6 其他

7.6.1 $nextTick

Vue观察到数据变化时,不是直接更新DOM,而是开启一个队列,并缓冲在同一时间循环中发生的所有数据变化。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。

然后,在下一个事件循环tick中,Vue刷新队列并执行实际(已去重)工作。

div#app
    div#div[v-if=showDiv]{这是一段文本}
    button[@click="getText"]{获取div内容}
script
    var appp = new Vue
        el:'app'
        data:
            showDiv:false
        method:
            getText:function(){
                _this=this;
                this.showDiv=true;
                var text=document.getElementById('div').innerHTML;
                console.log(text);
            }

//因为还没更新DOM
//所以会出现错误:TypeError: "document.getElementById(...) is null"
//$nextTick 等下一个时间循环tick
//   this.$nextTick(function(){ ...getElementById...log...  })
7.6.2 X-Templates

另一种定义模板的方式

div#app
    my-component
    script[type="text/x-template"][id="my-component-"]
        div{这是组件的内容}
script
    Vue.component('my-component',{
        template:'#my-component-'
    })
//为了将模板和组件的其他定义隔离
7.6.3 手动挂载实例

一般通过 new Vue()的形式创建实例。

如果需要动态地创建Vue实例,可以使用Vue.extend$mount两个方法手动挂载一个实例。

div#mount-div

script
    //创建
    var MyComponent = Vue.extend({
        template:'<div>Hello:{{name}}</div>',
        data:function(){
            return {
                name:'Aresn'
            }
        }
    })
    //挂载
    new MyComponent().$mount('#mount-div');
=>  new MyComponent({el:'#mount-div'});
=>var component = new MyComponent().$mount();
  document.getElementById('mount-div').appendChild(component.$el);

7.7 两个常用组件的开发

posted @ 2025-03-12 22:35  一起滚月球  阅读(11)  评论(0)    收藏  举报