【笔记】Vue组件

207 阅读7分钟

组件基础

组件是可复用的 Vue 实例

注册方法

组件名

  • 短横线分隔命名。 比如<my-component>
  • 首字母大写命名。 比如<MyComponent>

全局注册

实例

<div id="app">
     <my-component></my-component>
</div>
<script>
Vue.component('my-component',{
  template: '<div>我是组件的内容</div>'
})
var app = new Vue({
  el: '#app'
})
</script>
  • 优点: 所有 vue 实例都可以用
  • 缺点: 权限太大,容错率降低

局部注册

  • 局部注册的组件在其子组件中不可用

实例

<div id="app">
   <my-component></my-component>
  </div>
<script>
var app = new Vue({
  el: '#app',

    components: {
       'my-component':{
            template: '<div>我是组件的内容</div>'
    }
  }

})
</script>

问题

Vue 组件模板会受到 html 限制,比如<table>中只能有<tr><td>

解决:只能用 is 挂载属性

<table id='app'>
        <td is="my-component"></td>
    </table>

    <script>
    var app = new Vue({
        el: '#app',
        components: {
            'my-component': {
                template: '<div>hello world</div>'
            }
        }
    })
    </script>

注意

  • 组件名必须是小写且包含连字符-
  • template 必须被 DOM 元素包裹
  • 组件的定义中,除了 template 之外还有 data,computed,methods 等
  • 一个组件的data必须是一个函数,组件不会互相影响 实例
    • 组件是可复用的,组件内的 data 必须互不影响,若 data 以对象多的形式存在,js 对象是引用类型,若对某个组件的 data 修改,会影响到其它组件的 data,因此 data 必须以函数的形式返回
    • 为了实现每个组件可以维护独立的数据拷贝,互不影响

使用 props 传递数据(父-> 子传递数据)

当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property

实例

  <div id="app" style='border: 2px solid red'>
   <h5>我是父组件</h5>
    <child-component msg='我是来自父组件的内容'></child-component>
  </div>
  <!--这里都是父组件作用域-->
 <script>
 var app = new Vue({
  el: '#app',
  components: {
    'child-component': {
      props: ['msg'],
      //template是子组件作用域
      template: '<div style="border: 2px solid green">{{msg}}</div>'
    }
  }
})
</script>

vue 的父组件和子组件生命周期钩子执行顺序

父组件挂载完成必须等到子组件挂载完成之后

[父]beforeCreate -> [父]created -> [父]beforeMount -> [子]beforeCreate -> [子]created -> [子]beforeMount -> [子]mounted -> [父]mounted

  • 父组件更新过程
    • 影响到子组件:[父]beforeUpdate -> [子]beforeUpdate -> [子]updated -> [父]updated
    • 不影响到子组件:[父]beforeUpdate -> [父]updated
  • 子组件更新过程
    • 影响到父组件:[父]beforeUpdate -> [子]beforeUpdate -> [子]updated -> [父]updated
    • 不影响父组件:[子]beforeUpdate -> [子]updated

v-bind 动态传递 prop

实例

<div id="app" style='border: 2px solid red'>
<input type="text" v-model='parentmsg'>
    <bind-component :msg='parentmsg'></bind-component>
  </div>
<script>
var app = new Vue({
  el: '#app',
  data: {
    parentmsg: 'hello world'
    //绑定的属性值改变,传递的值也变化,动态
  },
  components: {
    'bind-component': {
      props: ['msg'],
      template: '<div style="border: 2px solid green">{{msg}}</div>'

    }
  }
})
</script>

单向数据流

通过 props 传递数据是单向的,只能父传子

  • 目的:尽可能将父子组解稿,避免子组件无意中修改了父组件的状态
    • 父组件下可以有多个子组件,若一个子组件能够修改父组件的状态,会使数据流难以理解
  1. 这个 prop 用来传递一个初始值
    • 注册组件
    • 父组件传递初始值进来,在子组件中用 props 接受
    • 最好定义一个本地的 data property 并将这个 prop 用作其初始值
<div id="app">
    <my-component msg='我是父组件传递的数据'></my-component>
</div>
<script>
Vue.component('my-component',{
  props: ['msg'],
  template: '<div>{{count}}</div>',
  data:function(){
    return {
      count: this.msg
    }
  }
})
var app = new Vue({
  el: '#app',
})
</script>
  • 这个 prop 以一种原始的值传入且需要进行切换
    • 注册组件
    • 将父组件的数据传递起来,并在子组件中用 prop 接收
    • 最好使用这个 prop 的值来定义一个计算属性

实例 1 实例 2

<div id="app">
<input type="text" v-model='width' >
    <width-component :width='width'></width-component>
  </div>
<script>
Vue.component('width-component',{
  props: ['width'],
  template: '<div :style="style"></div>',
  computed: {
    style: function(){
      return{
        width: this.width+'px',
        background: 'red',
        height: '30px'
      }
    }
  }
})
var app = new Vue({
  el: '#app',
  data: {
  width: 0
}
})
</script>

Prop 数据验证

  • 验证的 type 类型可以是
    • String
    • Number
    • Boolean
    • Object
    • Array
    • Function

实例

<div id="app">
    <my-component :a='a' :b='b' :c='c' :d='d' :e='e' :f='f'></my-component>
</div>
<script>
Vue.component('my-component',{
  props: {
    a: Number,
    b: [String,Number],
    c: {
      type: Boolean,
      default: true
    },
    d: {
      type: Number,
      required: true
    },
    e: {
      type: Array,
      defalut: function(){
        return []
      }
    },
    f: {
      validator: function(value){
        return value > 10
      }
    }
  },
  template: '<div>{{a}}--{{b}}--{{c}}--{{d}}--{{e}}--{{f}}</div>'
})
var app =new Vue({
  el: '#app',
  data:{
    a: 1,
    b: 'abc',
    c: true,
    d: 1234,
    e: ['abc','ddd'],
    f: 88
  }
})
</script>

组件通信

  • 父子组件通信
    • 父->子
      • 子组件 props 接收数据
    • 子->父
      • $emit 方法传递参数
  • 非父子组件通信
    • eventBus,创建一个事件中心用于接收和传递事件

自定义事件 (子组件->父组件传递数据)

  • 自定义事件
  • v-on 和$emit 组合使用
    • 父组件用 v-on 监听事件
    • 子组件用$emit 触发事件(传给监听器回调)
  • 在自定义事件中用一个参数来接受

实例 1 实例 2

<div id="app">
     {{fathermsg}}
    <my-component @change='sendFather'></my-component>
    <!--@change就是自定义事件-->
  </div>
<script>
  Vue.component('my-component',{
  props: ['fathermsg'],
  template: "<button @click='childmsg'>点击传递子组件数据</button>",
  data(){
    return {
      msg: "我是来自子组件的数据"
    }
  },
  methods: {
    childmsg(){
      this.$emit('change',this.msg)
    },
  }
})
var app = new Vue({
  el: '#app',
  data: {
    fathermsg: '我是来自父组件的数据'
  },
  methods: {
    sendFather: function(value){
    this.fathermsg =value
    }
  }
})
</script>

vm.$emit

  • {string} eventName 当前实例上的事件
  • [...args] 要传递的参数

在组件中学习 v-model

  • v-model 其实是一个语法糖
    • v-bind 绑定一个 value 属性
    • v-on 指令给当前元素绑定 input 事件
  <input v-model='search'>
  //等价于
  <input v-bind:value='search' v-on:input='search = $event.target.value'>
  • 使用 v-model
    • 接收一个 value 属性
    • 在有新的 value 时触发 input 事件

实例 1 实例 2

<div v-model='search'></div>
//等同于
<div v-bind:value='search' v-on:input='search'></div>
<div id="app">
    {{fathermsg}}
   <my-component v-model="fathermsg"></my-component>
  </div>
<script>
Vue.component('my-component',{
    template: '<button @click="handle">点击传递数据</button>',
    data(){
        return {
        msg: '我是子组件传递的数据'
        }
},
    methods: {
        handle(){
            this.$emit('input',this.msg)
        }
    }
            })
    var app = new Vue({
        el: '#app',
        data: {
            fathermsg: '我是父组件数据'
        },
    })
</script>

非父组件的通信

创建一个事件中心用于接收和传递事件

var bus = new Vue()

<!--触发组件A中的事件-->
bus.$emit('ddaa','传递的数据')

<!--在组件B创建的钩子中监听事件-->
bus.$on('ddaa',function(){
})

实例

 <div id="app">
    <my-acomponent></my-acomponent>
    <my-bcomponent></my-bcomponent>
  </div>
<script>
Vue.component('my-acomponent',{
  template: '<button @click="handle">点击我向b组件传递数据</button>',
  data: function(){
    return{
      aaa: '我是来自A组件的内容'
    }
  },
  methods: {
    handle: function(){
      this.$root.bus.$emit('lala',this.aaa)
}
  }
})
Vue.component('my-bcomponent',{
  template: '<div></div>',
  created: function(){
    this.$root.bus.$on('lala',function(value){
      alert(value)
    })
  }
})
var app = new Vue({
  el: '#app',
  data: {
    bus: new Vue()
  }
})
</script>

vm.$root

实例property,当前组件的根vue实例

vm.$on

监听当前实例上的自定义事件,由$emit触发,回调函数接收所有传入事件触发函数的额外参数

  • string Array event
  • {Function} callback

修改父组件属性

实例

<div id='app'>
<child-component></child-component>---{{msg}}
  </div>
<script>
Vue.component('child-component',{
  template: '<button @click="setFatherData">通过点击我修改父组件的数据</button>',
  methods: {
    setFatherData: function(){
      this.$parent.msg= '数据已经修改了'
    }
  }
})
var app = new Vue({
  el: '#app',
  data: {
    msg:'数据还未修改'
  }
})
<script>

获取子组件属性

通过$refs 获取这个子组件属性

实例

<div id="app">
    <my-acomponent ref='A'></my-acomponent>
    <my-bcomponent ref='B'></my-bcomponent>
    <hr>
    <child-component ref='C'></child-component>---{{msg}}
    <hr>
    <button @click='getChildData'>我是父组件,我要拿到子组件的内容</button>
    -----------------{{fromChild}}
  </div>
<script>
Vue.component('my-acomponent',{
  template: '<div></div>',
  data: function(){
    return{
      msg: '我是A中的msg'
    }
  }
})
Vue.component('my-bcomponent',{
  template: '<div></div>',
  data: function(){
    return {
      msg: '我是来自B中的msg'
    }
  }
})
Vue.component('child-component',{
  template: '<div></div>',
  data: function(){
    return {
      msg: '我是C中的msg'
    }
  }
})
var app = new Vue({
  el: '#app',
  data: {
    formChild: '还未拿到'
  },
  methods: {
    getChildData: function(){
      this.fromChild = this.$refs.A.msg;
    }
  }
})
</script>

vm.$refs

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。

插槽

组合组件,需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发,Vue,js 实现了一个内容分发的 API,使用特殊的 slot 元素作为原始内容的插槽。

编辑作用域

  • 父级模板里的所有内容都是在父级作用域中编译的
  • 子模版里的所有内容都是在子作用域中编译的

插槽的用法

混合父组件的内容和子组件自己的模板

参考:slot 插槽的用法-BarryDong

  • 单个和具名插槽是父组件向子组件传递数据

单个插槽

<slot></slot>

实例

  <div id="app">
    <my-component>
      <p>我是父组件插入的内容</p>
    </my-component>
  </div>
<script>
Vue.component('my-component',{
  template: '<div>\
                <slot>\
                  如果没有插入内容,我就出现\
                </slot>\
              </div>',
  data: function(){
    return {
      message: '我是子组件的内容'
    }
  }
})
var app = new Vue({
  el: '#app'
})
</script>

在子组件中使用 slot 做一个插槽,父组件中的内容就可以插到这个插槽里边。

具名插槽

  • 只能在<template></template>上使用v-slot指令
  • 父组件插入对应名字的子组件 slot 中
  • 具名插槽缩写#

实例

  <div id="app">
        <my-component>
          <template v-slot:header>
            <h1>Hello</h1>
          </template>
          <p>vue</p>
          <template v-slot:footer>
            <p>world</p>
          </template>
        </my-component>
  </div>
<script>
      Vue.component('my-component',{
        template: "<div>\
                     <header>\
                       <slot name='header'></slot>\
                      </header>\
                      <main>\
                        <slot></slot>\
                      </main>\
                       <footer>\
                        <slot name='footer'></slot>\
                       </footer>\
                    </div>"
    })
    var app = new Vue({
        el: '#app'
    })
</script>

动态插槽

<my-component>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</my-component>

实例

        <div id="app">
         <my-component>
             <template v-slot:[slotname]>
                 这是header组件
             </template>
         </my-component>
        </div>
        <script>
        Vue.component('my-component',{
            template: `<div>
            <slot name="header">

            </slot>
            </div>`
                    })
            var app = new Vue({
                el: '#app',
                data: {
                    slotname: 'header'
                }
            })
        </script>

作用域插槽

作用域插槽是一种特殊的 slot,使用可以复用的模板来替换已经渲染的元素

作用域插槽是父组件从子组件中获取数据

  • 把需要传递到的内容绑到子组件<slot>
  • 在父组件用 v-slot 设置一个值来定义我们提供插槽的名字

注意

  • 只有提供的是默认插槽时,组件标签才能当作插槽模板来使用。
  <my-component v-slot:defalut='slotProps'></my-component>
  • 具名插槽使用template作为插槽模板使用
  <template v-slot:other='slotProps'></template>
  • 出现多个插槽时,都用template作为插槽模板
  <my-component>
  <template v-slot:default='slotProps'></template>
  <template v-slot:other='slotProps'></template>
  </my-component>

实例

  <div id="app">
    //2.带值的 v-slot 来定义我们提供的插槽 prop 的名字。
  <my-component v-slot:default='slotProps'>
      {{slotProps.userText.firstName}}
    </my-component>
  </div>
<script>
Vue.component('my-component',{                     
  template: "<div>\                                
      //1.绑定在<slot>上的属性被称为插槽prop。
              <slot :userText='user'>{{user.lastName}}</slot>\
            </div>",
  data: function(){
  return {
    user: {
      firstName: 'li',
      lastName: 'bai'
      }
    }
  }
})
var app = new Vue({
  el: '#app',

})
</script>

访问 slot

vm.$slots 用来访问被插槽分发的内容

vm.$slots用来访问插槽分发的内容,vm.$slots.header能找到v-slot:header中的内容。。

实例

  <div id="app">
        <my-component>
          <template v-slot:header>
            <h1>Hello</h1>
          </template>
          <p>vue</p>
          <template v-slot:footer>
            <p>world</p>
          </template>
        </my-component>
  </div>
<script>
      Vue.component('my-component',{
        template: "<div>\
                     <header>\
                       <slot name='header'></slot>\
                      </header>\
                      <main>\
                        <slot></slot>\
                      </main>\
                       <footer>\
                        <slot name='footer'></slot>\
                       </footer>\
                    </div>",
        mounted: function(){
        var header = this.$slots.header;
        console.log(header);
        },
  //将获取到的header的值插入到div中
       render(createElement){
                var header = this.$slots.header;
                return createElement('div',[
                    createElement('header',header)
                ])
            }
    }) 
    var app = new Vue({
        el: '#app'
    })
  </script>

组件高级用法--动态组件

is 用于动态组件

实例

<div id="app">
    <component :is='thisView'></component>
    <button @click='handleView("A")'>A</button>
    <button @click='handleView("B")'>B</button>
    <button @click='handleView("C")'>C</button>
  </div>
<script>
Vue.component('compA',{
  template: '<div>我是一个组件A</div>'
})
Vue.component('compB',{
  template: '<div>我是一个组件B</div>'
})
Vue.component('compC',{
  template: '<div>我是一个组件C</div>'
})
var app = new Vue({
  el: '#app',
  data: {
    thisView: 'compA'
  },
  methods: {
    handleView: function(tag){
      this.thisView = 'comp' + tag
    }
  }
})
  </script>

在动态组件上使用 keep-alive

保持组件状态,避免反复重复渲染导致的性能问题

实例

  <div id="app">
    <keep-alive>
    <my-component :is='thisView'></my-component>
    </keep-alive>
    <button @click="handle('A')">A</button>
    <button @click="handle('B')">B</button>
    <button @click="handle('C')">C</button>

  </div>
<script>
  Vue.component('compA',{
  template: '<div>\
              one:<input type="radio"/>\
              two:<input type="radio"/>\
            </div>'
})
Vue.component('compB',{
  template: '<div>我是一个组件B</div>'
})
Vue.component('compC',{
  template: '<div>我是一个组件C</div>'
})
var app = new Vue({
  el: '#app',
  data: {
    thisView: 'compA'
  },
  methods: {
    handle: function(tag){
      this.thisView = 'comp' + tag
    }
  }
})
  </script>

点击 A 按钮选择一个 radio,点击其它按钮再返回 A,radio 仍是未选择状态。使用 keep-alive 保证返回 A 按钮,radio 仍保持是你选择的状态。