Vue.component('my-component-name', { /* ... */ })
该组件名就是 Vue.component
的第一个参数。
当直接在 DOM 中使用一个组件 (而不是在字符串模板
或单文件组件
) 的时候,官方强烈推荐遵循W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符
)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。
你可以在风格指南中查阅到关于组件名的其它建议。
命名方式 | 定义 | 使用 |
---|---|---|
kebab-case 短横线分隔 | Vue.component(’my-component-name ’, { /* … */ }) |
<my-component-name > |
PascalCase首字母大写 | Vue.component(’MyComponentName ’, { /* … */ }) |
<my-component-name > 和 <MyComponentName > |
注意
直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的
注册之后新创建的任何根实例都可以用。
Vue.component('my-component-name', {
// ... 选项 ...
})
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
components
中每个property
的 key
是组件名value
是组件对象ComponentA
在 ComponentB
中可用,则需要这样写:var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'ComponentA': ComponentA // 可以直接简写为:ComponentA。组件名字和值都是 ComponentA
},
// ...
}
src\components
下为每个组件创建一个目录(同组件名)ComponentB.js
或 ComponentB.vue
文件中:import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
现在 ComponentA
和 ComponentC
都可以在 ComponentB
的模板中使用了。
如果有很多基本的通用组件,如上的一个个注册,太复杂。
如果你恰好使用了webpack
或 Vue CLI 3+
(在内部使用了 webpack),那么就可以使用 require.context
对想要的组件进行全局注册。
new Vue
实例之前才有意义。require.context
获取指定文件夹下所有文件,可以正则
过滤文件名
,可以控制是否查询其子目录
。Vue.component
进行注册。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
)
})
prop中用驼峰
,标签属性还是得写成分隔线
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '{{ postTitle }}
'
})
<blog-post post-title="hello!"></blog-post>
重申一次,如果你使用字符串模板,那么这个限制就不存在了。(这名我还没理解)
数组形式默认值为字符串
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 验证。
前面已经学过:
传值方式 | 写法 | 备注 |
---|---|---|
静态字符串 | 三国演义 "> |
直接使用 title 属性,默认字符串 |
变量 | book.title "> |
使用了v-bind: 指令此时值就跟着变量走了 |
表达式 | '《' + book.title + '》' "> |
同上 |
对象 | bookOjb "> |
同上 |
数字 | 500 "> |
这里 500 没有引号,是数字 |
布尔值 | 不赋值,存在就默认为true 赋值时当变量看就好了 |
|
数组 | ['大哥','二第','三弟] "> |
支持字面量,也支持变量 |
对象 | {name:'刘备'} "> |
支持字面量,也支持变量 |
prop
只从父组件
=》子组件
。父组件中的值更新,子也更新。prop
(逻辑上它表明你想这样 子组件
=》父组件
但这是禁止的,你会收到错误提示)数组
和对象
是引用传递的,对它们内容的修父子之间是同步的。改
父级数据,就触发事件
,让父级监听
来处理吧。详见:sync修饰符prop
的特殊场景:prop
computed
自己编写的组件给别人用时,需要明确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
}
}
}
})
注意顺序:先
验证后
实例化。 (如 data、computed 等 property ) 在 default 或 validator 函数中是不可用的。
type
可以是下列原生构造函数中的一个:
String、 Number、 Boolean、 Array、 Object、 Date、 Function、 Symbol
另外还支持自定义的构造函数,如下验证author
是否通过new Person
创建的
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
Vue.component('blog-post', {
props: {
author: Person
}
})
attribute
。prop
,那就它就有着落了。prop
,那么此attribute
就会被添加到组件的根元素
上。绝大多数 attribute
会用外部
提供给组件的值替换
掉组件内部
设置好的值。
所以如果传入 type="text"
就会替换掉 type="date"
并把它破坏!
幸好class
和style
稍微智能一些,会把两部分值合并起来。
设置 inheritAttrs: false
可以在组件中禁用 Attribute
继承(不会影响 style
和 class
)
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
inheritAttrs: false 和 $attrs
配合的例子没看懂
触发:this.$emit('my-event')
监听:v-on:my-event="clickHandler"
事件名会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent
将会变成 v-on:myevent
因此,官方推荐始终使用 kebab-case
的事件名,如:my-event
一个组件上的 v-model
默认会利用名为 value
的 prop 和名为 input
的事件,但是像单选框、复选框等类型的输入控件可能会将 value
attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
2.2.0+ 新增的model
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
`
})
<base-checkbox v-model="lovingVue">base-checkbox>
我们定义了一个叫checked
的prop
用来接收v-model
的lovingVue
值。当 change
事件并附带一个新的值的时候,这个 lovingVue
将会更新。
直接在自定义组件上监听事件要在事件后.native
,此事子组件中的input
元素的focus
事件能触发
<div id="app">
<base-input v-on:focus.native="onFocus"></base-input>
</div>
<script>
Vue.component('base-input', {
data: function(){
return {label : '标签:',value: '值123'}
},
template: ``
})
new Vue({
el: "#app",
methods:{
onFocus: function($event){console.info("获得焦点");}
}
});
</script>
但如果子组件中根元素不是input
元素。那么.native
也没用了。
为了解决这个问题,Vue 提供了一个 $listeners
property,它是一个对象,包含了作用在这个组件上的所有监听器。
<div id="app">
<base-input v-on:focus="onFocus" v-model="myValue"></base-input> {{myValue}}
</div>
<script>
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: `
`
})
new Vue({
el: "#app",
data: {myValue : "123"},
methods:{
onFocus: function($event){console.info("获得焦点");}
}
});
</script>
现在
组件完全透明
了,组件内部所有事件都传出来了。
并且重写了input
事件,配合v-model="myValue"
使用。
(2.3.0+ 新增)
子组件想改
父级数据,就触发事件
,让父级监听
来处理。 .sync 修饰符
就是这种操作的缩写形式。
注意:
.sync
修饰符的v-bind
不能和表达式一起使用 (例如 v-bind:title.sync="doc.title + '!'"
是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model
。prop
如v-bind.sync="doc"
会把 doc
对象中的每个property
(如 title) 都作为一个独立的prop
传进去,然后各自添加用于更新的v-on
监听器。v-bind.sync
用在一个字面量的对象上,例如 v-bind.sync="{ title: doc.title }"
无法正常工作。在 2.6.0
中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot
指令)。它取代了 slot
和 slot-scope
这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份 RFC。
它允许你像这样合成组件:
<navigation-link url="/profile">
Your Profile
</navigation-link>
然后你在 的模板中可能会写为:
<a> <slot></slot> </a>
将会被替换为“Your Profile”
。任何模板代码
,包括 HTML
,甚至其它的组件
。
元素,则该组件两个标签之间的内容会被抛弃。父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
<navigation-link url="/profile">
{{ url }} // 这里其实是读取不取 navigation-link 属性 url的。因为这句会先编译好,再传进去替换插槽。
navigation-link>
如果没有传值进来就会使用它。
自 2.6.0
起有所更新。已废弃
的使用 slot
attribute 的语法在这里。
上【v-slot
】指令对应
的【name
】。name
的
,来入所有没定的部分。(默认隐含名字"default"
也可以写出来)<div id="app">
<my-component >
<p>其它内容11111111111</p>
<template v-slot:header>
<h1>这里对应头部</h1>
</template>
<p>其它内容222222222222</p>
<p>正文内容正文内容正文内容正文内容正文内容正文内容</p>
<p>其它内容333333333333</p>
<template v-slot:footer>
<p>这里对应足部</p>
</template>
<p>其它内容444444444444</p>
</my-component>
</div>
<script>
Vue.component('my-component', {
data: function(){
return {label : '标签:',value: '值123'}
},
template: `
`
})
new Vue({
el: "#app",
methods:{
onFocus: function($event){console.info("获得焦点");}
}
});
</script>
2.6.0
中引入新语法
使用v-slot
指令v-slot
只能添加在
上 (只有一种例外情况)注意 v-slot
只能添加在 上 (只有一种例外情况),这一点和已经废弃的
slot
attribute 不同。
自 2.6.0 起有所更新。已废弃的使用 slot-scope
attribute 的语法在这里 。
组件:
<span>
<slot>{{ user.lastName }}</slot>
</span>
如果想这样换掉备用内容。是无效的
<current-user>
{{ user.firstName }}
</current-user>
想要实现这个目标,模板
和使用时
都要调整一下
user
在父级的插槽内容中可用,我们可以将user
作为
元素的一个 attribute
绑定上去,
组件如下调整:{{ user.lastName }}
元素上的 attribute
被称为插槽 prop
。v-slot
来定义我们提供的插槽 prop 的名字:(为包含所有default
的插槽prop
的对象取名slotProps
这个名字可以自己喜欢怎么取都行)<div id="app">
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.lastName }}{{ slotProps.user.firstName }}
</template>
</current-user>
</div>
<script>
Vue.component('current-user', {
data: function(){
return {user : {firstName:"jin", lastName:"jerry" }}
},
template: `{{ user.lastName }} `
})
new Vue({
el: "#app"
});
</script>
当被提供的内容只有默认插槽时v-slot
可以直接用在组件上。
<current-user v-slot:default="slotProps">
{{ slotProps.user.lastName }}{{ slotProps.user.firstName }}
</current-user>
还可以再简化,省掉:default
<current-user v-slot="slotProps">
{{ slotProps.user.lastName }}{{ slotProps.user.firstName }}
</current-user>
注意默认插槽的缩写语法
不能和具名插槽
混用,因为它会导致作用域不明确:
<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里:
function (slotProps) {
// 插槽内容
}
这意味着 v-slot
的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,如下:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 user
重命名为person
:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
2.6.0 新增
动态指令参数也可以用在 v-slot
上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
2.6.0 新增
v-slot:
替换为字符#
例如: v-slot:header
可以被重写为 #header
default
这样会触发一个警告。<current-user #="{ user }">
{{ user.firstName }}
</current-user>
#default="{ user }"
就可以了<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。这在设计封装数据逻辑同时允许父级组件自定义部分布局的可复用组件时是最有用的。
例如,我们要实现一个
组件,它是一个列表且包含布局和过滤逻辑:
<ul>
<li v-for="todo in filteredTodos" v-bind:key="todo.id">
{{ todo.text }}
</li>
</ul>
我们可以将每个 todo 作为父级组件的插槽,以此通过父级组件对其进行控制,然后将 todo
作为一个插槽 prop 进行绑定:
<ul>
<li v-for="todo in filteredTodos" v-bind:key="todo.id" >
<!-- 我们为每个 todo 准备了一个插槽, 将 `todo` 对象作为一个插槽的 prop 传入。 -->
<slot name="todo" v-bind:todo="todo">
{{ todo.text }} <!-- 后备内容 -->
</slot>
</li>
</ul>
现在当我们使用
组件的时候,我们可以选择为 todo 定义一个不一样的 作为替代方案,并且可以从子组件获取数据:
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
这只是作用域插槽用武之地的冰山一角。想了解更多现实生活中的作用域插槽的用法,我们推荐浏览诸如 Vue Virtual Scroller、Vue Promised 和 Portal Vue 等库。
v-slot
指令自 Vue 2.6.0 起被引入,提供更好的支持 slot
和 slot-scope
attribute 的 API 替代方案。v-slot
完整的由来参见这份 RFC。在接下来所有的 2.x 版本中 slot
和 slot-scope
attribute 仍会被支持,但已经被官方废弃且不会出现在 Vue 3 中。
slot
attribute 的具名插槽自 2.6.0
起被废弃
。新推荐的语法请查阅这里。
<base-layout>
<template slot="header">
<h1>Here might be a page titleh1>
template>
<p>A paragraph for the main content.p>
<template slot="footer">
<p>Here's some contact infop>
template>
base-layout>
或者直接把 slot
attribute 用在一个普通元素上:
<base-layout>
<h1 slot="header">Here might be a page title</h1>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</base-layout>
这里其实还有一个未命名插槽,也就是默认插槽
,捕获所有未被匹配的内容。
slot-scope
attribute 的作用域插槽自 2.6.0
起被废弃
。新推荐的语法请查阅这里。
在 上使用特殊的
slot-scope
attribute,可以接收传递给插槽的 prop (把这里提到过的
组件作为示例):
<slot-example>
<template slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
这里的 slot-scope
声明了被接收的 prop 对象会作为 slotProps
变量存在于 作用域中。你可以像命名 JavaScript 函数参数一样随意命名
slotProps
。
slot="default"
可以省略。<slot-example>
<template slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
slot-scope
attribute 也可以直接用于非 元素 (包括组件):
<slot-example>
<span slot-scope="slotProps">
{{ slotProps.msg }}
</span>
</slot-example>
slot-scope
的值可以接收任何有效的可以出现在函数定义的参数位置上的 JavaScript 表达式。这意味着在支持的环境下 (单文件组件或现代浏览器),你也可以在表达式中使用 ES2015 解构,如下:
<slot-example>
<span slot-scope="{ msg }">
{{ msg }}
</span>
</slot-example>
使用这里描述过的
作为示例,与它等价的使用 slot-scope
的代码是:
<todo-list v-bind:todos="todos">
<template slot="todo" slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
keep-alive
之前学过用
实现标签页,但切换标签时,组件都会重新渲染。
如果不想这样可以用:keep-alive
。 它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。。查看API文档了解更多详情
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
注意这个
要求被切换到的组件都有自己的名字,不论是通过组件的name
选项还是局部/全局注册。
你可以在这里查看完整示例代码。
为了简化分包懒加载。Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: 'I am async!'
})
}, 1000)
})
如你所见,这个工厂函数会收到一个 resolve
回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason)
来表示加载失败。这里的 setTimeout
是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
你也可以在工厂函数中返回一个Promise
,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
当使用局部注册的时候,你也可以直接提供一个返回 Promise
的函数:
2.3.0+ 新增
这里的异步组件工厂函数也可以返回一个如下格式的对象:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
注意如果你希望在Vue Router 的路由组件中使用上述语法的话,你必须使用 Vue Router 2.4.0+
版本。
这里记录的都是和处理边界情况有关的功能,即一些需要对 Vue 的规则做一些小调整的特殊情况。不过注意这些功能都是有劣势或危险的场景
的。我们会在每个案例中注明,所以当你使用每个功能的时候请稍加留意。
Vue 官方教程基数篇 - 本笔记的学习对象
Vue 官方CLI 官方文档
Vue 官方API 参考
Vue 官方API文档 - 生命周期钩子
vue-devtools 编译安装
我的 Vue CLI 学习笔记
Vue 学习笔记 - 基础(上)
Vue 学习笔记 - 基础(中)
Vue 学习笔记 - 基础(下)组件基础
Vue 学习笔记 - 深入了解组件
MDN 完整有效按键名 Key Values
千锋教育-李卫民 Vue 渐进式 JavaScript 框架