Vue学习——2、深入了解组件

接上一节内容。

2.10组件基础:https://cn.vuejs.org/v2/guide/components.html

组件的意义是为了复用,因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

2.10.1组件里的data必须是一个函数:

因此使用的每个组件实例可以维护自己的数据,而不会影响到其他组件的数据:

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

2.10.2组件注册:

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的:

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

全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。

2.10.3通过-Prop-向子组件传递数据:

Prop 是在组件上注册的一些自定义 attribute(组件拥有的属性)。当一个值传递给 一个 prop的attribute 的时候,它就变成了那个组件实例property的值。

为了给博文组件传递一个标题,我们可以用一个 props 数组将标题包含在该组件可接受的 prop 列表中:

Vue.component('blog-post', {
  props: ['title'],
  template: '

{{ title }}

' })

一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样。

一个 prop 被注册之后,就可以使用了:



结果:

Vue学习——2、深入了解组件_第1张图片

2.10.4单个根元素:

当构建一个  组件时,模板包含的东西远不止一个标题(title),可能会包含这篇博文的正文(content)、发布日期(publictime)、评论(comment)等等。为每个相关的信息定义一个 prop 会变得很麻烦。

重构一下这个  组件了,让它变成接受一个单独的 post prop:【此时的post是一个对象

Vue.component('blog-post', {
  props: ['post'],
  template: `
    

{{ post.title }}

` })

现在,不论何时为 post 对象添加一个新的 property,它都会自动地在  内可用。

 

2.10.5监听子组件事件:https://cn.vuejs.org/v2/guide/components.html#监听子组件事件

在我们开发  组件时,它的一些功能可能要求我们和父级组件进行沟通。例如方法子组件内的博文字体。

父级组件可以像处理 native DOM 事件一样通过 v-on 监听子组件实例的任意事件:

同时子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个事件:

2.10.6 在组件上使用v-model:https://cn.vuejs.org/v2/guide/components.html#在组件上使用-v-model

自定义组件也可以使用 v-model,当用在组件上时,v-model 则会这样:

为了让它正常工作,这个组件内的  必须:

  • 将其 value attribute 绑定到一个名叫 value 的 prop 上。
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出。

新注册的组件:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    
  `
})

现在 v-model 就应该可以在这个组件上完美地工作起来了:

至此,Vue基础部分的重点知识记录完毕。下面深入了解组件。

 

一、深入了解组件

1.1 组件注册

1.1.1组件名

自定义组件名时最好是 字母全小写且必须包含一个连字符。这会帮助避免和当前以及未来的 HTML 元素相冲突。

1.1.2 全局注册

全局注册使用Vue.component 来创建组件:

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

注意:全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生(即使用之前)。

1.1.3局部注册

全局注册往往是不够理想的。比如,使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便已经不再使用一个组件了,它仍然会被包含在最终的构建结果中。这造成了无谓的增加用户下载的 JavaScript 的。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

然后在 components 选项中定义你想要使用的组件:

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

对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。

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

var ComponentA = { /* ... */ }

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

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

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

注意在 ES2015+ 中,在对象中放一个类似 ComponentA 的变量名其实是 ComponentA: ComponentA 的缩写,即这个变量名同时是:

  • 用在模板中的自定义元素的名称
  • 包含了这个组件选项的变量名

1.1.4模块系统

(1)在模块系统中局部注册

如果项目是通过 import/require 使用一个模块系统,说明使用了诸如 Babel 和 webpack 的模块系统。在这些情况下,推荐创建一个 components 目录,并将每个组件放置在其各自的文件中。

然后需要在局部注册之前导入每个想使用的组件。例如,在一个假设的 ComponentB.js  ComponentB.vue 文件中:

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}

现在 ComponentA 和 ComponentC 都可以在 ComponentB 的模板中使用了。

(2)基础组件的自动化全局注册 (不要求掌握)

可能许多组件只是包裹了一个输入框或按钮之类的元素,作为通用组件。我们把它们称为基础组件,它们会在其他组件中被频繁的使用。

所以会导致会有一个包含基础组件的长列表:

import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'

export default {
  components: {
    BaseButton,
    BaseIcon,
    BaseInput
  }
}

但在其他模板中只占一小部分的代码(即使用频率不高):



  

如果恰好项目是使用 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
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )

  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  )
})

1.2 Prop

1.2.1 Prop的大小写

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '

{{ postTitle }}

' })

注意:如果使用字符串模板,那么这个限制就不存在了。

1.2.2Prop的类型

以字符串数组形式列出的 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

如果希望每个 prop 都有指定的值类型。这时,可以使用对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

这样在遇到错误的类型时从浏览器的 JavaScript 控制台就能看到相应的信息。

1.2.3传递静态或动态 Prop:

prop可以接受任何类型的值。例如:

  • 数值类型
  • 布尔类型
  • 数组类型
  • 对象类型
  • 一个对象所有的property

1.2.4单向数据流(即数据只能从父组件往子组件里传递):

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

1.2.5Prop验证:

可以为组件的 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 将会产生一个控制台的警告。

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

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

1.2.6类型检查:略

1.2.7 非 Prop 的 Attribute:略

1.3自定义事件

1.3.1事件名:

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:

this.$emit('myEvent')

则监听这个名字的 kebab-case 版本是不会有任何效果的:


v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

因此推荐始终使用 kebab-case 的事件名

1.3.2自定义组件的v-model:略

1.3.3将原生事件绑定到组件:略

1.3.4.sync 修饰符:略

 

1.4插槽

插槽就是子组件中提供给父组件使用的一个占位符,用<v-slot>v-slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<v-slot>v-slot>标签。

 

在 2.6.x版本后,我们为具名插槽(起了名字的插槽)和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份 RFC。

目前最新版本的Vue是2.6.11。

如果对插槽的概念不是很理解,推荐先阅读一下:vue_插槽的理解和使用。

1.4.1插槽内容(略过,尚未理解透彻)

1.4.2编辑作用域

注意:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

1.4.3后备内容

后备内容:即给插槽设置默认内容,它只会在没有提供内容的时候被渲染。

1.4.4具名插槽

具名插槽:即给插槽设置了名称。

使用场景:当子组件有多个插槽,且需要有所区分时使用。

 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

注意:一个不带 name 的  出口会带有隐含的名字“default”。

在向具名插槽插入内容的时候,我们可以在一个