少侠请重新来过 - Vue学习笔记(四) - Vue组件

277 阅读6分钟

组件

组件(Component)是 Vue.js 最核心的功能,也是整个框架设计最精彩的地方。

组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。

使用组件

组件注册
  • 全局注册 全局注册之后,任何Vue实例都可以使用到这个组件,比如loading,msg
Vue.component('global-component',{
  template:'<div>{{msg}}</div>',
  data:function(){
    return{
      msg:'我是全局注册的组件'
    }
  }
})
  • 局部注册,在Vue实例中,使用components选项可以局部注册组件,注册后的组件只在该实例作用域下有效
<template>
    <div>
        <global-component></global-component>
        <local-component></local-component>
    </div>
</template>

<script>
var localComponent = {
    template:'<div>我是局部注册2的组件</div>'
}
export default {
    components:{
        localComponent
    }
}
</script>

组件间的数据传递

组件之间不仅可以复用,更重要的是组件之间的通信,在Vue.js中,父组件要正向的向子组件传递数据或者参数,子组件接收后根据参数不同来渲染不同的内容进行操作,这个正向的过程就是通过props来实现的。

组件数据传递

props
  • 字符串数组
    • 在子组件的props选项中构造一个数组,接收一个来自父组件的数据msg,父组件只需要把数据填入子组件的对应的msg属性中
    • 使用 v-bind 可以动态绑定 props 的值,当父组件的数据变化时,也会传递给子组件
    • props是单向绑定的,父组件属性发生变化的时,传到给子组件,但是不会反过来。这是为了防止子组件无意间修改了父组件的状态,因此这是,单向的数据流
    • 通常我们有两种改变props的情况
      • 作为初始值传入,子组件只是将其作为本地数据的初始值使用
      • 作为需要被转变的值传入
<template>
    <div>
        <input type="text" name="" v-model="msg" id="">
        <local-component :msg='msg'></local-component>
    </div>
</template>

<script>
var localComponent = {
    template:`<div>
                <p>{{message}}</p>
                <p>{{messageM}}</p>
            </div>`,
    props:['msg'],
    data(){
        return {
            message:this.msg
        }
    },
    computed:{
        messageM(){
            return '我是第二种:' + this.msg
        }
    }
}
export default {
    data(){
        return{
            msg:'我是第一种'
        }
    },
    components:{
        localComponent
    }
}
</script>
  • 对象
    • 当props数据需要验证的时候,就需要使用对象写法
    • 对象可以设置 typerequireddefaultvalidator
    • 当数据验证不通过时,控制台会发出警告
<template>
    <div>
        <obj-component :msg='"sda"' :num=9 :arr="[1,2,3]"></obj-component>
    </div>
</template>

<script>
var objComponent = {
    template:`<div>
                <p>{{msg}}</p>
                <p>{{num}}</p>
                <p>{{arr}}</p>
            </div>`,
    props:{
        msg:{
            type:String,
            default:'msg',
            required:true
        },
        arr:{
            type: Array,
            default:function(){
                return []
            }
        },
        num:{
            type:Number,
            validator:function(value){
                return value < 10
            }
        }
    },
    data(){
       return {}
    }
}
export default {
    data(){
        return{
            msg:''
        }
    },
    components:{
        objComponent
    }
}
</script>
组件通信

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

  • 父组件用 v-on 绑定自定义事件到子组件上
  • 子组件需要通过 $emit() 来触发事件
    • 通过自定义事件来传递数据
      <template>
          <div>
              <event-component @setTotal="setTotal"></event-component>
              <p>value = {{value}}</p>
          </div>
      </template>
      
      <script>
      var eventComponent = {
          template:`<div>
                      <button @click='add'>+</button>
                      <button @click='reduce'>-</button>
                  </div>`,
          data(){
              return {
                  count:0
              }
          },
          methods:{
              add(){
                  this.count++;
                  this.$emit('setTotal',this.count);
              },
              reduce(){
                  this.count--;
                  this.$emit('setTotal',this.count);
              }
          }
      }
      export default {
          data(){
              return{
                  value:0
              }
          },
          components:{
              eventComponent
          },
          methods:{
              setTotal(value){
                  this.value = value;
              }
          }
      }
      </script>
    
    • 使用 v-modal 其实使用v-modal@input 的语法糖,甚至可以在子组件中进行数据的双向绑定,只需要满足两个要求
      • 接收一个 value 属性 或者 直接在子组件 v-model 绑定
      • 触发 input 事件
      <template>
          <div>
              <!-- <event-component @input="setTotal"></event-component> -->
              <event-component v-model="value"></event-component>
              <p>value = {{value}}</p>
          </div>
      </template>
      
      <script>
      var eventComponent = {
          template:`<div>
                      <input v-model='count' @input='updateCount' />
                      <button @click='add'>+</button>
                      <button @click='reduce'>-</button>
                  </div>`,
          data(){
              return {
                  count:0
              }
          },
          methods:{
              add(){
                  this.count++;
                  this.$emit('input',this.count);
              },
              reduce(){
                  this.count--;
                  this.$emit('input',this.count);
              },
              updateCount(event){
                  this.$emit('input',this.count);
              }
          }
      }
      export default {
          data(){
              return{
                  value:0
              }
          },
          components:{
              eventComponent
          },
          methods:{
              setTotal(value){
                  this.value = value;
              }
          }
      }
      </script>
    

slot

为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发。Vue.js 实现了一个内容分发 API ,参照了当前 Web组件规范草案,使用特殊的 <slot> 元素作为原始内容的插槽。

  • 单个slot
<template>
    <div>
        <slot-component>
            <p>parent to child slot</p>
        </slot-component>
    </div>
</template>

<script>
var slotComponent = {
    template:`<div>
                <p>我是子组件</p>
                <slot>我是分发内容</slot>
            </div>`,
    data(){
        return {
        }
    },
}
export default {
    data(){
        return{
        }
    },
    components:{
        slotComponent
    },
}
</script>

渲染结果为

<div>
    <p>我是子组件</p>
    <p>parent to child slot</p>
</div>
  • 具名slot 可以给slot指定一个name后分发多个内容,且具名的slot可以与单个slot共存,单个slot将作为一个默认的slot出现,即父组件没有使用slot属性的元素将出现在默认slot中
<template>
    <div>
        <slot-component>
            <div slot='header'>
                <p>我是头部2</p>
            </div>
            <div slot='footer'>
                <p>我是底部</p>
            </div>
            <p>我是正文</p>
        </slot-component>
    </div>
</template>

<script>
var slotComponent = {
    template:`<div>
                <slot name='header'></slot>
                <slot>我是分发内容</slot>
                <slot name='footer'></slot>
                <p>我是子组件</p>
            </div>`,
    data(){
        return {
        }
    },
}
export default {
    data(){
        return{
        }
    },
    components:{
        slotComponent
    },
}
</script>

渲染结果为

<div>
    <div>
        <p>我是头部</p>
    </div>
    <p>我是正文</p>
    <div>
        <p>我是底部</p>
    </div>
    <p>我是子组件</p>
</div>
  • 可以通过$slot访问某个具名的slot

组件的高级用法

  • 递归组件

    组件可以在模板内递归的调用自己,只要在组件中设置name的选项,值得注意的是,需要设定一个条件值来限定其递归数量,否则会发生栈溢出的报错,一般用来开发一些未知层级关系的组件。

    <template>
        <div>
            <recursive-component :count="0"></recursive-component>
        </div>
    </template>
    
    <script>
    var recursiveComponent = {
        template:`<div>
                    {{count}}
                    <recursive-component :count="count + 1" v-if="count<3"></recursive-component>
                </div>`,
        name:'recursive-component',
        props:{
            count:{
                type: Number,
                default: 1
            }
        }
    }
    export default {
        data(){
            return{
            }
        },
        components:{
            recursiveComponent
        },
    }
    </script>
    
  • 动态组件

    Vue.js提供了一个特殊的元素<component>来动态的挂载不同的组件,使用is特性来选择要挂载的组件。

    <template>
        <div>
            <!-- 动态组件 -->
            <component :is="targetComponent"></component>
            <button @click="handleChangeView('aComponent')">A</button>
            <button @click="handleChangeView('bComponent')">B</button>
            <button @click="handleChangeView('cComponent')">C</button>
        </div>
    </template>
    
    <script>
    var aComponent = {
        template:`<div> 我是A </div>`,
    }
    var bComponent = {
        template:`<div> 我是B </div>`,
    }
    var cComponent = {
        template:`<div> 我是C </div>`,
    }
    export default {
        data(){
            return{
                targetComponent:aComponent
            }
        },
        components:{
            aComponent,
            bComponent,
            cComponent
        },
        methods:{
            handleChangeView(component){
                this.targetComponent = component
            }
        }
    }
    </script>
    
  • 异步组件

    在大型应用中,我们可能需要将应用拆分为多个小模块,按需从服务器下载。为了让事情更简单, Vue.js 允许将组件定义为一个工厂函数,动态地解析组件的定义。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染