美文网首页
vue深入了解组件

vue深入了解组件

作者: 胜过夜的美 | 来源:发表于2019-03-11 17:04 被阅读0次

    组件注册

    在注册一个组件的时候,我们始终需要给它一个名字。比如:

    Vue.component('my-component-name', { /* ... */ })
    

    该组件名就是 Vue.component 的第一个参数.
    当直接在DOM中使用一个组件(而不是在字符串模板或单文件组件)的时候,我们强烈推荐遵循w3c规范中的自定义组件名(字母全小写且必须包含一个连字符)。

    组件名大小写

    定义组件名的方式有两种:

    使用kebab-case

    当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

    使用pascalCase

    当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说<my-component-name><MyComponentName>都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

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

    这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中.

    <div id="app">
      <component-a></component-a>
      <component-b></component-b>
      <component-c></component-c>
    </div>
    <script>
    Vue.component('component-a', { /* ... */ })
    Vue.component('component-b', { /* ... */ })
    Vue.component('component-c', { /* ... */ })
    new Vue({ el: '#app' })
    </script>
    
    

    在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

    全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
    在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:var ComponentA = { /* ... */ }
    然后在 components 选项中定义你想要使用的组件:

    new Vue({
      el: '#app',
      components: {
        'component-a': ComponentA,
        'component-b': ComponentB
      }
    })
    

    注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:

    var ComponentA = { /* ... */ }
    
    var ComponentB = {
      components: {
        'component-a': ComponentA
      },
      // ...
    }
    

    如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

    import ComponentA from './ComponentA.vue'
    
    export default {
      components: {
        ComponentA
      },
      // ...
    }
    
    基础组件的自动化全局注册

    可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把他们称为基础组件,它们会在各个组件中被频繁的用到。
    如果你使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js) 中全局导入基础组件的示例代码:

    import Vue from 'vue'
    import upperFirst from 'lodash/upperFirst'
    import camelCase from 'lodash/camelCase'
    
    const requireComponent = require.context(
      // 其组件目录的相对路径
      './components',
      // 是否查询其子目录
      false,
      // 匹配基础组件文件名的正则表达式
      /Base[A-Z]\w+\.(vue|js)$/
    )
    
    requireComponent.keys().forEach(fileName => {
      // 获取组件配置
      const componentConfig = requireComponent(fileName)
    
      // 获取组件的 PascalCase 命名
      const componentName = upperFirst(
        camelCase(
          // 剥去文件名开头的 `./` 和结尾的扩展名
          fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
        )
      )
    
      // 全局注册组件
      Vue.component(
        componentName,
        // 如果这个组件选项是通过 `export default` 导出的,
        // 那么就会优先使用 `.default`,
        // 否则回退到使用模块的根。
        componentConfig.default || componentConfig
      )
    })
    

    prop

    prop的大小写

    HTML 中的特性名是大小写不敏感的,当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。如果你使用字符串模板,那么这个限制就不存在了。

    Vue.component('blog-post', {
      // 在 JavaScript 中是 camelCase 的
      props: ['postTitle'],
      template: '<h3>{{ postTitle }}</h3>'
    })
    <!-- 在 HTML 中是 kebab-case 的 -->
    <blog-post post-title="hello!"></blog-post>
    
    prop类型
    props: {
      title: String,
      likes: Number,
      isPublished: Boolean,
      commentIds: Array,
      author: Object
    }
    

    这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。

    单向数据流

    所有的 prop 都使得其父子prop之间形成了一个单向下行绑定:父级prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

    额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

    这里有两种常见的试图改变一个 prop 的情形:
    1、这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:

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

    2、这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }
    
    • 注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
    Prop验证

    我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。
    为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

    Vue.component('my-component', {
      props: {
        // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
        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 ['success', 'warning', 'danger'].indexOf(value) !== -1
          }
        }
      }
    })
    

    prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

    • 注意那些 prop会在一个组件实例创建之前进行验证,所以实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
    类型检查

    type可以是下列原生构造函数中的一个:

    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol
      额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:
    function Person (firstName, lastName) {
      this.firstName = firstName
      this.lastName = lastName
    }
    // 使用
    Vue.component('blog-post', {
      props: {
        author: Person
      }
    })
    

    来验证 author prop 的值是否是通过 new Person 创建的。

    非Prop的特性

    一个非 prop 特性是指传向一个组件,但是该组件并没有相应 prop 定义的特性。
    例如:

    <bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>
    

    然后这个 data-date-picker="activated"特性就会自动添加到 <bootstrap-date-input> 的根元素上。

    替换/合并以有的特性

    想象一下 <bootstrap-date-input> 的模板是这样的:

    <input type="date" class="form-control">
    

    为了给我们的日期选择器插件定制一个主题,我们可能需要像这样添加一个特别的类名:

    <bootstrap-date-input
      data-date-picker="activated"
      class="date-picker-theme-dark"
    ></bootstrap-date-input>
    

    在这种情况下,我们定义了两个不同的 class 的值:

    • form-control,这是在组件的模板内设置好的
    • date-picker-theme-dark,这是从组件的父级传入的
      对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入 type="text" 就会替换掉 type="date" 并把它破坏!庆幸的是,classstyle 特性会稍微智能一些,即两边的值会被合并起来,从而得到最终的值: form-control date-picker-theme-dark
    禁用特性继承

    如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false。
    这尤其适合配合实例的 attrs 属性使用,该属性包含了传递给一个组件的特性名和特性值, 有了 `inheritAttrs: false` 和 `attrs`,你就可以手动决定这些特性会被赋予哪个元素。在撰写基础组件的时候是常会用到的:

    Vue.component('base-input', {
      inheritAttrs: false,
      props: ['label', 'value'],
      template: `
        <label>
          {{ label }}
          <input
            v-bind="$attrs"
            v-bind:value="value"
            v-on:input="$emit('input', $event.target.value)"
          >
        </label>
      `
    })
    
    • 注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。
    • $attrs--继承所有的父组件属性(除了prop传递的属性、class 和 style )

    自定义事件

    触发的事件名要和监听这个事件所用的名称完全匹配,大小写一致。
    推荐你始终使用 kebab-case 的事件名。

    自定义组件的v-model

    一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于[不同的目的]。model 选项可以用来避免这样的冲突:

    Vue.component('base-checkbox', {
      model: {
        prop: 'checked',
        event: 'change'
      },
      props: {
        checked: Boolean
      },
      template: `
        <input
          type="checkbox"
          v-bind:checked="checked"
          v-on:change="$emit('change', $event.target.checked)"
        >
      `
    })
    
    将原生事件绑定到组件

    你可能有很多次想要在一个组件的根元素上直接监听一个原生事件,这时,你可以使用 v-on.native 修饰符:

    <base-input v-on:focus.native="onFocus"></base-input>
    

    在有的时候这是很有用的,不过在你尝试监听一个类似 <input> 的非常特定的元素时,这并不是个好主意。为了解决这个问题,vue提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:

    {
      focus: function(event) { /*...*/ }
      input: function(value) { /*...*/  }
    }
    

    有了这个 $listeners 属性, 你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input> 的你希望它也可以配合 v-model 工作的组件来说,为这些监听器创建一个类似下述 inputListeners 的计算属性通常是非常有用的:

    Vue.component('base-input', {
      inheritAttrs: false,
      props: ['label', 'value'],
      computed: {
        inputListeners: function () {
          var vm = this
          // `Object.assign` 将所有的对象合并为一个新对象
          return Object.assign({},
            // 我们从父级添加所有的监听器
            this.$listeners,
            // 然后我们添加自定义监听器,
            // 或覆写一些监听器的行为
            {
              // 这里确保组件配合 `v-model` 的工作
              input: function (event) {
                vm.$emit('input', event.target.value)
              }
            }
          )
        }
      },
      template: `
        <label>
          {{ label }}
          <input
            v-bind="$attrs"
            v-bind:value="value"
            v-on="inputListeners"
          >
        </label>
      `
    })
    

    现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的特性和监听器的都可以工作。

    1. 首先,在computed 计算属性里,创建一个 "inputListeners" 方法。这个方法返回的是这个组件的

    监听器。用 object.assign( ) 方法,合并了 this.listeners (之前创建的监听器) 和 { input: function(event)(vm.emit('input',event.target.value)) } ( input 就是新创建的事件/监听器)

    1. 在 template 模板中,子元素 <input> 用 v-on 直接绑定 "inputListeners" 方法 : <input v-on="inputListeners" >

    这样,"inputListeners" 方法里面返回的所有监听器,都被绑在了这个 <input> 这个子元素上了。

    $listeners属性

    Vue提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。
    这时,可以使用 listeners 属性,在组件的listeners 属性里面写好所有将会需要的监听器(原生事件),这些监听器就可以直接绑定在组件的子元素上(不是根元素)

    .sync修饰符

    插槽

    动态组件&异步组件

    处理边界情况

    相关文章

      网友评论

          本文标题:vue深入了解组件

          本文链接:https://www.haomeiwen.com/subject/oaavpqtx.html