vue深入了解组件
目录:
组件注册
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