栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > Vue.js

Vue 组件 · Vue.js教程

Vue.js 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

什么是组件?

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

使用组件

注册

之前说过,我们可以通过以下方式创建一个 Vue 实例:

new Vue({
  el: '#some-element',
  // 选项
})

要注册一个全局组件,你可以使用 Vue.component(tagName, options)。 例如:

Vue.component('my-component', {
  // 选项
})

对于自定义标签名,Vue.js 不强制要求遵循 [W3C规则](https://www.w3.org/TR/custom-elements/#concepts) (小写,并且包含一个短杠),尽管遵循这个规则比较好。

组件在注册之后,便可以在父实例的模块中以自定义元素 的形式使用。要确保在初始化根实例 之前 注册了组件:

// 注册
Vue.component('my-component', {
  template: 'A custom component!'
})

// 创建根实例
new Vue({
  el: '#example'
})

渲染为:

A custom component!

局部注册

不必在全局注册每个组件。通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用:

var Child = {
  template: 'A custom component!'
}

new Vue({
  // https://www.mshxw.com/file/tupian/20210206/..
  components: {
    //  将只在父模板可用
    'my-component': Child
  }
})

这种封装也适用于其它可注册的 Vue 功能,如指令。

DOM 模版解析说明

当使用 DOM 作为模版时(例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。尤其像这些元素

      , , 限制了能被它包裹的元素,
      https://www.mshxw.com/file/tupian/20210206/..

      自定义组件 被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性:

      应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:

        
        
        

        由于这三个组件共享了同一个 data , 因此增加一个 counter 会影响所有组件!我们可以通过为每个组件返回新的 data 对象来解决这个问题:

        data: function () {
          return {
            counter: 0
          }
        }
        

        现在每个 counter 都有它自己内部的状态了:

        构成组件

        组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B 。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。

        在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。看看它们是怎么工作的。


        props down, events up

        Prop

        使用 Prop 传递数据

        组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。

        prop 是父组件用来传递数据的一个自定义属性。子组件需要显式地用 props 选项声明 “prop”:

        Vue.component('child', {
          // 声明 props
          props: ['message'],
          // 就像 data 一样,prop 可以用在模板内
          // 同样也可以在 vm 实例中像 “this.message” 这样使用
          template: '{{ message }}'
        })
        

        然后向它传入一个普通字符串:

        
        

        结果:

        camelCase vs. kebab-case

        HTML 特性不区分大小写。当使用非字符串模版时,prop的名字形式会从 camelCase 转为 kebab-case(短横线隔开):

        Vue.component('child', {
          // camelCase in Javascript
          props: ['myMessage'],
          template: '{{ myMessage }}'
        })
        
        
        

        再次说明,如果你使用字符串模版,不用在意这些限制。

        动态 Prop

        类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件:


        使用 v-bind 的缩写语法通常更简单:

        
        

        结果:


        字面量语法 vs 动态语法

        初学者常犯的一个错误是使用字面量语法传递数值:

        
        

        因为它是一个字面 prop ,它的值以字符串 "1" 而不是以实际的数字传下去。如果想传递一个实际的 Javascript 数字,需要使用 v-bind ,从而让它的值被当作 Javascript 表达式计算:

        
        

        单向数据流

        prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。

        另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop 。如果你这么做了,Vue 会在控制台给出警告。

        通常有两种改变 prop 的情况:

          prop 作为初始值传入,子组件之后只是将它的初始值作为本地数据的初始值使用;

          prop 作为需要被转变的原始值传入。

        更确切的说这两种情况是:

          定义一个局部 data 属性,并将 prop 的初始值作为局部数据的初始值。

          props: ['initialCounter'],
          data: function () {
           return { counter: this.initialCounter }
          }
          

          定义一个 computed 属性,此属性从 prop 的值计算得出。

          props: ['size'],
          computed: {
           normalizedSize: function () {
             return this.size.trim().toLowerCase()
           }
          }
          

        注意在 Javascript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它**会影响**父组件的状态。

        Prop 验证

        组件可以为 props 指定验证要求。如果未指定验证要求,Vue 会发出警告。当组件给其他人使用时这很有用。

        prop 是一个对象而不是字符串数组时,它包含验证要求:

        Vue.component('example', {
          props: {
            // 基础类型检测 (`null` 意思是任何类型都可以)
            propA: Number,
            // 多种类型
            propB: [String, Number],
            // 必传且是字符串
            propC: {
              type: String,
              required: true
            },
            // 数字,有默认值
            propD: {
              type: Number,
              default: 100
            },
            // 数组/对象的默认值应当由一个工厂函数返回
            propE: {
              type: Object,
              default: function () {
                return { message: 'hello' }
              }
            },
            // 自定义验证函数
            propF: {
              validator: function (value) {
                return value > 10
              }
            }
          }
        })
        

        type 可以是下面原生构造器:

          String
          Number
          Boolean
          Function
          Object
          Array

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

        当 prop 验证失败了, Vue 将拒绝在子组件上设置此值,如果使用的是开发版本会抛出一条警告。

        自定义事件

        我们知道,父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,应该怎样做?那就是自定义事件!

        使用 v-on 绑定自定义事件

        每个 Vue 实例都实现了事件接口(Events interface),即:

          使用 $on(eventName) 监听事件
          使用 $emit(eventName) 触发事件

        Vue的事件系统分离自浏览器的[EventTarget API](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)。尽管它们的运行类似,但是`$on` 和 `$emit` __不是__`addEventListener` 和 `dispatchEvent` 的别名。

        另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。

        下面是一个例子:

        {{ total }}

        Vue.component('button-counter', {
          template: '',
          data: function () {
            return {
              counter: 0
            }
          },
          methods: {
            increment: function () {
              this.counter += 1
              this.$emit('increment')
            }
          },
        })
        
        new Vue({
          el: '#counter-event-example',
          data: {
            total: 0
          },
          methods: {
            incrementTotal: function () {
              this.total += 1
            }
          }
        })
        

        {{ total }}

        在本例中,子组件已经和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件。

        给组件绑定原生事件

        有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on 。例如:

        
        

        使用自定义事件的表单输入组件

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

        
        

        仅仅是一个语法糖:

        
        

        所以在组件中使用时,它相当于下面的简写:

        
        

        所以要让组件的 v-model 生效,它必须:

          接受一个 value 属性
          在有新的 value 时触发 input 事件

        一个非常简单的货币输入:

        
        
        Vue.component('currency-input', {
          template: '
            
              $
              
            
          ',
          props: ['value'],
          methods: {
            // 不是直接更新值,而是使用此方法来对输入值进行格式化和位数限制
            updatevalue: function (value) {
              var formattedValue = value
                // 删除两侧的空格符
                .trim()
                // 保留 2 小数位
                .slice(0, value.indexOf('.') + 3)
              // 如果值不统一,手动覆盖以保持一致
              if (formattedValue !== value) {
                this.$refs.input.value = formattedValue
              }
              // 通过 input 事件发出数值
              this.$emit('input', Number(formattedValue))
            }
          }
        })
        

        上面的实现方式太过理想化了。 比如,用户甚至可以输入多个小数点或句号 - 哦哦!因此我们需要一个更有意义的例子,下面是一个更加完善的货币过滤器:

        这个接口不仅仅可以用来连接组件内部的表单输入,也很容易集成你自己创造的输入类型。想象一下:

        
        
        
        

        非父子组件通信

        有时候非父子关系的组件也需要通信。在简单的场景下,使用一个空的 Vue 实例作为中央事件总线:

        var bus = new Vue()
        
        // 触发组件 A 中的事件
        bus.$emit('id-selected', 1)
        
        // 在组件 B 创建的钩子中监听事件
        bus.$on('id-selected', function (id) {
          // https://www.mshxw.com/file/tupian/20210206/..
        })
        

        在更多复杂的情况下,你应该考虑使用专门的 状态管理模式.

        使用 Slot 分发内容

        在使用组件时,常常要像这样组合它们:

        
          
          
        
        

        注意两点:

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

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

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

        编译作用域

        在深入内容分发 API 之前,我们先明确内容的编译作用域。假定模板为:

        
          {{ message }}
        
        

        message 应该绑定到父组件的数据,还是绑定到子组件的数据?答案是父组件。组件作用域简单地说是:

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

        一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:

        
        

        假定 someChildProperty 是子组件的属性,上例不会如预期那样工作。父组件模板不应该知道子组件的状态。

        如果要绑定子组件内的指令到一个组件的根节点,应当在它的模板内这么做:

        Vue.component('child-component', {
          // 有效,因为是在正确的作用域内
          template: 'Child',
          data: function () {
            return {
              someChildProperty: true
            }
          }
        })
        

        类似地,分发内容是在父组件作用域内编译。

        单个 Slot

        除非子组件模板包含至少一个 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。

        最初在 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。

        假定 my-component 组件有下面模板:

        我是子组件的标题

        只有在没有要分发的内容时才会显示。

        父组件模版:

        我是父组件的标题

        这是一些初始内容

        这是更多的初始内容

        渲染结果:

        我是父组件的标题

        我是子组件的标题

        这是一些初始内容

        这是更多的初始内容

        具名 Slot

        元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。

        仍然可以有一个匿名 slot ,它是默认 slot ,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot ,这些找不到匹配的内容片段将被抛弃。

        例如,假定我们有一个 app-layout 组件,它的模板为:

        父组件模版:

        
          

        这里可能是一个页面标题

        主要内容的一个段落。

        另一个主要段落。

        这里有一些联系信息

        渲染结果为:

        这里可能是一个页面标题

        主要内容的一个段落。

        另一个主要段落。

        这里有一些联系信息

        在组合组件时,内容分发 API 是非常有用的机制。

        作用域插槽

        2.1.0 新增

        作用域插槽是一种特殊类型的插槽,用作使用一个(能够传递数据到)可重用模板替换已渲染元素。

        在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件一样:

        在父级中,具有特殊属性 scope 的