资讯专栏INFORMATION COLUMN

Vue-组件详解

dadong / 3015人阅读

摘要:除了监听事件外,还可以用于组件之间的自定义事件。它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用。将和合并成,会自动去判断是普通标签还是组件。子组件这里的状态绑定的是父组件的数据。

查看原文站点,更多扩展内容及更佳阅读体验!
组件详解 组件与复用

Vue组件需要注册后才可以使用。注册有全局注册和局部注册两种方式。

全局注册

Vue.component("my-component", {});

要在父实例中使用这个组件,必须要在实例创建前注册,之后就可以用的形式来使用组件。

Vue.component("my-component", {
    template: `
这是一个组件
` });

template的DOM结构必须被一个元素包含,缺少

会无法渲染并报错。

在Vue实例中,使用components选项可以局部注册组件,注册后的组件只在该实例作用域下有效。

组件中也可以使用components选项来注册组件,使组件可以嵌套。

var Child = {
    template: `
局部注册组件的内容
` }; new Vue({ el: "#app", components: { "my-component": Child }, });

Vue组件的模板在某些情况下会受到HTML的限制,比如

内规定只允许是
等这些表格元素,所以在内直接使用组件时无效的。这种情况下,可以使用特殊的is属性来挂载组件

Vue.component("my-component", { template: `
这里是组件内容
` });

常见的限制元素还有

      props: ["message"], template: `
      {{message}}
      `, data: { parentMessage: "" }

      这里用v-model绑定了父级的数据parentMessage,当通过输入框任意输入时,子组件接收到的props["message"]也会实时响应,并更新组件模板。

      单向数据流

      业务中会经常遇到两种需要改变prop的情况,一种是父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改。这种情况可以在组件data内再声明一个数据,引用父组件的prop

      Vue.component("my-component", { props: ["initCount"], template: `
      {{count}}
      `, data() { return { count:this.initCount } } });

      组件中声明了数据count,它在组件初始化时会获取来自父组件的initCount,之后就与之无关了,只用维护count,这样就可以避免直接操作initCount

      另一种情况就是prop作为需要被转变的原始值传入,这种情况用计算属性就可以。

      Vue.component("my-component", { props: ["width"], template: `
      组件内容
      `, computed: { style: function () { return { width: this.width + "px" } } } });

      因为用CSS传递宽度要带单位(px),数值计算一般不带单位,所以统一在组件内使用计算属性。

      在JavaScript中对象和数组时引用类型,指向同一个内存空间,所以props是对象和数组时,在子组件内改变是会影响父组件。
      数组验证

      prop需要验证时,需要对象写法。

      一般当组件需要提供给别人使用时,推荐都进行数据验证。比如某个数据必须是数字类型,如果传入字符串,就会在控制台弹出警告。

      Vue.component("my-component", {
          props: {
              // 必须是数字
              propA: Number,
              // 必须是字符串或数字类型
              propB: [String, Number],
              // 布尔值,如果没有定义,默认值是true
              propC: {
                  type: Boolean,
                  default: true
              },
              // 数字,而且是必传
              propD: {
                  type: Number,
                  default: true
              },
              // 如果是数组或对象,默认值必须是一个函数来返回
              propE: {
                  type: Array,
                  default: function () {
                      return []
                  }
              },
              // 自定义一个验证函数
              propF: {
                  validator: function (value) {
                      return value > 10
                  }
              }
          }
      });

      验证的type类型可以是:

      String

      Number

      Boolean

      Object

      Array

      Function

      type也可以是一个自定义构造器,使用instanceof检测。

      组件通信

      组件关系可分为父组件通信、兄弟组件通信、跨级组件通信。

      自定义事件

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

      v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。

      JavaScript的设计模式——观察者模式方法:

      dispatchEvent

      addEventListener

      Vue组件的子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件。

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

      总数:{{total}}

      Vue.component("my-component", { template: `
      `, data() { return { counter: 0 } }, methods: { handleIncrease: function () { this.counter++; this.$emit("increase", this.counter); }, handlereduce: function () { this.counter--; this.$emit("reduce", this.counter) } } }); new Vue({ el: "#app", data: { total: 0 }, methods: { handleGetTotal: function (total) { this.total = total; } } });

      在改变组件的data "counter"后,通过$emit()再把它传递给父组件,父组件用@increase@reduce$emit()方法的第一个参数是自定义事件的名称。

      除了用v-on在组件上监听自定义事件外,也可以监听DOM事件,这时可以用.native修饰符表示监听时一个原生事件,监听的是该组件的根元素。

      
      
      使用v-model

      Vue可以在自定义组件上使用v-model指令。

      
      

      组件$emit()的事件名时特殊的input,在使用组件的父级,并没有在上使用@input="handler",而是直接用了v-model绑定的一个数据total

      
      

      v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定。

      总数:{{total}}

      Vue.component("my-component", { props: ["value"], template: ``, methods: { updateValue: function () { this.$emit("input", event.target.value) } } }); new Vue({ el: "#app", data: { total: 10 }, methods: { handleReduce: function () { this.total--; } } });

      实现这样一个具有双向绑定的v-model组件要满足下面两个要求:

      接收一个value属性

      在有新的value时触发input事件

      非父子组件通信

      在实际业务中,除了父子组件通信外,还有很多非父子组件通信的场景,非父子组件一般有两种,兄弟组件跨多级组件

      Vue 1.x版本中,除了$emit()方法外,还提供了¥dispatch()$broadcast()

      $dispatch()用于向上级派发事件,只要是它的父级(一级或多级以上),都可以在Vue实例的events选项内接收。

      此实例只在Vue 1.x版本中有效:

      {{message}}

      Vue.component("my-component", { template: ``, methods: { handleDispatch: function () { this.$dispatch("on-message", "来自内部组件的数据") } } }); new Vue({ el: "#app", data: { message: "" }, events: { "on-message": function (msg) { this.message = msg; } } });

      $broadcast()是由上级向下级广播事件,用法完全一致,方向相反。

      这两种方法一旦发出事件后,任何组件都可以接收到,就近原则,而且会在第一次接收到后停止冒泡,除非返回true

      这些方法在Vue 2.x版本中已废弃。

      在Vue 2.x中,推荐任何一个空的Vue实例作为中央事件总线(bus),也就是一个中介。

      {{message}}

      var bus = new Vue(); Vue.component("component-a", { template: ``, methods: { handleEvent: function () { bus.$emit("on-message", "来自组件component-a的内容") } } }); var app = new Vue({ el: "#app", data: { message: "" }, mounted: function () { var _this = this; // 在实例初始化时,监听来自bus实例的事件 bus.$on("on-message", function (msg) { _this.message = msg; }) } });

      首先创建了一个名为bus的空的Vue实例;然后全局定义了组件component-a;最后创建了Vue实例app

      app初始化时,也就是在生命周期mounted钩子函数里监听了来自bus的事件on-message,而在组件component-a中,点击按钮后会通过bus把事件on-message发出去。此时app就会接收到来自bus的事件,进而在回调里完成自己的业务逻辑。

      这种方法巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。

      如果深入使用,可以扩展bus实例,给它添加datamethodscomputed等选项,这些都是可以公用的。

      在业务中,尤其是协同开发时非常有用,因为经常需要共享一些通用的信息,比如用户登录的昵称、性别、邮箱等,还有用户的授权token等。

      只需在初始化时让bus获取一次,任何时间、任何组件就可以从中直接使用,在单页面富应用(SPA)中会很实用。

      除了中央事件总线bus外,还有两种方法可以实现组件间通信:父链和子组件索引。

      父链

      在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,父组件也可以通过this.$children访问它所有的子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。

      {{message}}

      Vue.component("component-a", { template: ``, methods: { handleEvent: function () { this.$parent.message = "来自组件component-a的内容" } } }); var app = new Vue({ el: "#app", data: { message: "" } });

      尽管Vue允许这样操作,但在业务中,子组件应该尽可能地避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父子组件紧耦合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想状态下,只有组件自己能修改它的状态。

      父子组件最好还是通过props$emit()来通信。

      子组件索引

      当子组件较多时,通过this.$children来遍历出需要的一个组件实例是比较困难的,尤其是组件动态渲染时,它们的序列是不固定的。

      Vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定一个索引名称。

      Vue.component("component-a", { template: `
      子组件
      `, data() { return { message: "子组件内容" } }, }); var app = new Vue({ el: "#app", methods: { handleRef: function () { // 通过$refs来访问指定的实例 var msg = this.$refs.comA.message; console.log(msg); } } });

      在父组件模板中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$refs来访问指定名称的子组件。

      $refs只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用$refs

      Vue 2.x将v-elv-ref合并成ref,Vue会自动去判断是普通标签还是组件。

      使用slot分发内容

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

      组件不知道它的挂载点会有什么内容。挂载点的内容是由的父组件决定的。

      组件很可能有它自己的模板。

      props传递数据、events触发事件和slot内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这3部分构成。

      作用域

      父组件中的模板:

      
          {{message}}
      
      

      这里的message就是一个slot,但是它绑定的是父组件的数据,而不是组件的数据。

      父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。

      Vue.component("child-component", { template: `
      子组件1
      `, }); var app = new Vue({ el: "#app", data: { showChild: true } });

      这里的状态showChild绑定的是父组件的数据。

      在子组件上绑定数据:

      Vue.component("child-component", { template: `
      子组件
      `, data() { return { showChild: true } } }); var app = new Vue({ el: "#app", });

      因此,slot分发的内容,作用域是在父组件上

      单个slot

      在子组件内使用特殊的元素就可以为这个组件开启一个slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的标签及它的内容。

      分发的内容

      更多分发的内容

      Vue.component("child-component", { template: `

      如果没有父组件插入内容,我将作为默认出现。

      `, }); var app = new Vue({ el: "#app", });

      子组件child-component的模板内定义了一个元素,并且用一个

      作为默认的内容,在父组件没有使用slot时,会渲染这段默认的文本;如果写入了slot,就会替换整个

      子组件内的备用内容,它的作用域是子组件本身。
      具名Slot

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

      标题

      正文的内容

      更多正文的内容

      底部信息
      Vue.component("child-component", { template: `
      `, }); var app = new Vue({ el: "#app", });

      子组件内声明了3个元素,其中在

      内的没有使用name特性,它将作为默认slot出现,父组件没有使用slot特性的元素与内容都将出现在这里。

      如果没有指定默认的匿名slot,父组件内多余的内容都将被抛弃。

      在组合使用组件时,内容分发API至关重要。

      作用域插槽

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

      Vue.component("child-component", { template: `
      `, }); var app = new Vue({ el: "#app", });

      子组件的模板,在元素上有一个类似props传递数据给组件的写法msg="xxx",将数据传递到插槽。

      父组件中使用了