vue深入了解组件

时间:June 6, 2019 分类:

目录:

组件注册

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

第一个参数就是组件名了,最好遵循w3c中的规范,字母全小写并且包含一个连字符

组件名大小写

有两种,一种就是刚在所提到的字母全小写并且包含一个连字符,使用kebeb-case

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

另一种就是驼峰式的PascalCase

Vue.component('MyComponentName', { /* ... */ })

使用PascalCase定义的组件在引用的时候以上两种命名法都可以使用,就是my-component-name和MyComponentName都有效,但是在直接DOM中使用还是要用my-component-name

总结就是用my-component-name不会错

全局注册

全局注册就是在之后任何创建新的vue根实例的时候都可以使用

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

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

并且可以在各自内部也可以使用

局部注册

使用JavaScript注册,然后在vue实例中定义需要使用的组件

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

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

局部组件在其他组件中是不能使用的,如果希望使用其他组件需要在组件中定义

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

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

如果通过Bebal或者webpack使用ES2015模块,就可以写成

import ComponentA from './ComponentA.vue'

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

这里的ComponentA等价于ComponentA: ComponentA

模块系统

在ComponentB.js或ComponentB.vue文件

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

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

这样ComponentB模板中就可以使用ComponentA和ComponentC了

基础组件自动化全局注册

对于按钮,输入框等基础组件,可能每个模块都会有一个上边模块系统提到的长列表

如果使用了webpack或内部使用了webpack的vueCli3+就可以使用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
  )
})

全局注册必须要在创建vue实例之前

Prop

prop的大小写

html中对大小写是不敏感的,当使用DOM模板的时候,camelCase(驼峰命名法)要使用等价的kebab(短横线分割命名法)命名

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', 'likes', 'isPublished', 'commentIds', 'author']

如果希望指定类型就要以对象的形式列出

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

传递静态或者动态prop

静态的prop,这样实质就是传入一个静态的值

<blog-post title="My journey with Vue"></blog-post>

动态的prop可以是一个变量,也可以是一个表达式

<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
  v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

传入数字

<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>

传入布尔值

<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>

<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>

传入数组

<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>

传入一个对象

<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
  v-bind:author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>

传入一个对象的所有属性

传入一个对象的所有属性可以不带参数的v-bind=

post: {
  id: 1,
  title: 'My Journey with Vue'
}

对于绑定post对象

<blog-post v-bind="post"></blog-post>

等价于

<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

单向数据流

所有的prop都是父级prop向下流动数据到子级prop,但是反过来不行,目的是为了防止子级改变父级组件的状态

当父级的组件发生更新,子组件的prop都会被刷新为最新的值

常见的更改prop的两种示例

1.将父组件的数据当做一个本地prop的数据使用

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

2.prop以原始值传入并且需要进行转换

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

prop验证

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
      }
    }
  }
})

类型检查

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

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

type也可以是一个自定义的构造函数,通过instanceof来确认

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
Vue.component('blog-post', {
  props: {
    author: Person
  }
})

非prop的特性

对于使用第三方库组件,例如<bootstrap-date-input>需要正在input上添加data-date-picker

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

替换或者合并已有特性

说实话没看懂,参考components-props

禁用特征继承

不希望根元素继承特性可以使用组件的inheritAttrs: false

Vue.component('my-component', {
  inheritAttrs: false,
  // ...
})

也没看懂

自定义事件

事件名

对于事件名,也是应用在html上的,所以不区分大小写

this.$emit('myEvent')

事件其实在v-on的时候v-on:myEvent将会变成v-on:myevent,导致不生效,所以就老老实实的用短横线的方式即可

自定义组件的v-model

在2.2.0新增

一个组件上的v-model默认会使用名为value的prop和名为input的事件,但是单选框和复选框等的value会用作其他的目的

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-model的时候

<base-checkbox v-model="lovingVue"></base-checkbox>

lovingVue的值会传入名为checked的prop,当触发change事件lovingVue的值会更改

将原生事件绑定到组件(没看懂)

没看懂

.sync修饰符

子组件触发父组件事件

this.$emit('update:title', newTitle)

父组件监听

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

缩写就是.sync

<text-document v-bind:title.sync="doc.title"></text-document>

当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:(没懂)

<text-document v-bind.sync="doc"></text-document>

插槽(没看懂)

在2.6.0版本引入了v-slot来取代slot和slot-scope

插槽内容

如果想合成

<navigation-link url="/profile">
  Your Profile
</navigation-link>

在navigation-link的模板会写

<a
  v-bind:href="url"
  class="nav-link"
>
  <slot></slot>
</a>

动态组件&异步组件

动态组件使用keep-alive

可以使用is来切换组件

<component v-bind:is="currentTabComponent"></component>

而keep-alive目的是方式组件切换的时候造成重复渲染的性能问题

<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

异步组件(没看懂)

为了切分为更细的代码块,vue允许以工厂函数的方式定义组件,工厂函数会异步解析组件定义,在组件需要被渲染的时候触发工厂函数,并将结果缓存起来供未来渲染

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

处理边界情况

访问元素&组件

在绝大多数情况下最好不要触达另一个组件内部或者手动修改DOM元素

访问根实例

根实例

// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})

所有子组件都可以将这个实例作为一个全局的store来方位或者使用

// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

如果系统很大需要使用Vuex来管理这些应用的状态

访问父组件实例

$parent支持从一个子组件访问父组件的实例

例如谷歌地图

<google-map>
  <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map>

所有子组件都可能需要访问其

如果google-map中有一个map属性,可能就需要this.$parent.map来获取

例如在google-map组件中添加google-map-region组件

<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

就需要

var map = this.$parent.map || this.$parent.$parent.map

访问子组件实例或者子元素(从这往下根本看不懂)

依赖注入

程序化的事件侦听器

循环引用

模板定义的替代品