文章目录
Vue.js 组件
组件用于封装页面的部分功能,将功能的结构、样式、逻辑代码封装为整体
提高功能的复用性与可维护性,更好地专注于业务逻辑
组件使用时为自定义 HTML 标签形式,通过组件名作为自定义标签名
<div id="app">
<!-- 普通 HTML 标签 -->
<p>P标签内容</p>
<!-- Vue.js 组件 -->
<my-com></my-com>
</div>
全局注册
全局注册的组件在注册后可以用于任意实例或组件中
Vue.component('组件名', { /* 选项对象 */ })
注意:全局注册必须设置在根 Vue 实例创建之前
组件基础
本质上,组件是可复用的 Vue 实例,所以它们可以与 new Vue 接收相同的选项,例如 data、methods
以及生命周期钩子等
仅有的例外是像 el 这样的根实例特有的选项
组件命名规则
- kebab-case: “my-component”
- PascalCase: “MyComponent”
Vue.component('my-component', {/* 选项对象 */})
Vue.component('MyComponent', {/* 选项对象 */})
注意:无论采用那种命名方式,在 DOM 中都只有 Kebab-case
可以使用
<div id="app">
<my-com-a></my-com-a>
<!-- <MyComA></MyComA> -->
<my-com-b></my-com-b>
<!-- <MyComB></MyComB> -->
</div>
<!-- ============================== -->
<script>
// kebab-case 进行注册
Vue.component('my-com-a', {
template: '<div>这是a组件的内容</div>'
});
// PascalCase 进行注册
Vue.component('MyComB', {
template: '<div>这是b组件的内容</div>'
});
new Vue({
el: '#app',
data: {
}
})
</script>
template 选项
template 选项用于设置组件的结构,最终被引入根实例或其他组件中
Vue.component('my-com-a', {
template: `
<div>
<h3>组件A的标题内容</h3>
</div>
`
});
注意:组件必须只有一个根元素
data 选项
data 选项用于存储组件的数据,与根实例不同,组件的 data 选项必须为函数,数据设置在返回值对象中
Vue.component('my-com-a', {
template: `
<div>
<h3>组件A的标题内容:{{ title }}</h3>
</div>
`,
data: function () {
return {
title: "示例内容"
}
}
});
这种实现方式是为了确保每个组件实例可以维护一份被返回对象的独立的拷贝,不会互相影响
局部注册
局部注册的组件只能用在当前实例或组件中
new Vue({
components: {
'my-component-a': {
template: `<h3> {{ title }} </h3>`,
data () {
return {title: "a 组件示例内容"}
}
},
'my-component-b': {
template: `<h3> {{ title }} </h3>`,
data () {
return {title: "b 组件示例内容"}
}
}
}
})
单独配置组件的选项对象
var MyComponentA = { /* ... */ };
var MyComponentB = { /* ... */ };
new Vue({
el: "#app",
components: {
"my-component-a" : MyComponentA,
"my-component-b" : MyComponentB
}
})
ES6 的对象属性简写
new Vue({
el: "#app",
components: {
MyComponentA,
MyComponentB
}
})
组件通信
问题:
子组件如何获取父组件中的数据?
父组件如何得知子组件的数据变更?
如果是更加复杂的组件关系呢?
在组件间传递数据的操作,称为组件通信
- 父传子
- 子传父
- 非父子传
- 其他通信方式
父组件向子组件传值
通过子组件的 props 选项接收父组件的传值
Vue.component('my-component', {
props: ['title'],
template: '<h3> {{ title }} </h3>'
})
注意:props 不要与 data 存在同名属性
父组件设置方式如下:
<div id="app">
<my-component-a title="示例内容1"></my-component-a>
<my-component-a :title="'示例内容2'"></my-component-a>
<my-component-a :title="item.title"></my-component-a>
</div>
<!-- ======================= -->
<script>
new Vue({
el: "#app",
data: {
item: {
title: "父组件的数据"
}
}
})
</script>
props 命名规则
建议 prop 命名使用 camelCase,父组件绑定时使用 kebab-case
Vue.component('my-component', {
props: ['myTitle'],
template: "<h3> {{ myTitle }} </h3>"
})
<div id="app">
<my-component-a title="示例内容1"></my-component-a>
<my-component-a :title="'示例内容2'"></my-component-a>
<my-component-a :title="item.title"></my-component-a>
</div>
单项数据流
父子组件间的所有 prop 都是单向下行绑定的
如果子组件要处理 prop 数据,应当存储在 data 中后操作
Vue.component({
props: ['initialTitle'],
template: '<h3> {{myTitle}} </h3>',
data () {
return {
myTitle: this.initialTitle
}
}
})
注意:如果 prop 为数组或对象时,子组件操作将会影响到父组件的状态
props 类型
prop 可以设置类型检查,这时需要将 props 更改为一个带有验证需求的对象,并指定对应类型
Vue.component({
props: {
parStr: String,
parArr: Array,
parAny: null // parAny: undefined
},
template: `
<div>
{{ parStr }}
{{ parArr }}
{{ parAny }}
</div>
`
})
<script>
new Vue({
el: "#app",
data: {
str: '示例内容',
arr: [1, 2, 3],
any: "任意类型均可"
}
})
</script>
<!-- ======================= -->
<div id="app">
<my-component-a
:par-str = "str"
:par-arr = "arr"
:par-any = "any"
></my-component-a>
</div>
prop 还可以同时指定多个类型,通过数组方式保存即可
Vue.component('MyComponentA', {
props: {
parData: [String, Number]
},
template: `
<div>
{{ parData }}
</div>
`
})
props 验证
当 prop 需要设置多重规则时,可以将 prop 的值设置为选项对象
之前的类型检测功能通过 type 选项设置
Vue.component('MyComponentA', {
props: {
parNum: {
type: Number
},
parData: {
type: [String, Boolean]
}
},
template: `
<div>
{{ parNum }}
{{ parData }}
</div>
`
})
required 用于设置数据为必填项
Vue.component('MyComponentA', {
props: {
parNum: {
type: Number,
required: true
}
},
template: `
<div>
{{ parNum }}
</div>
`
})
default 用于给可选项指定默认值,当父组件未传递数据时生效
Vue.component('MyComponentA', {
props: {
parNum: {
type: Number,
dafault: 100
}
},
template: `
<div>
{{ parNum }}
</div>
`
})
注意:当默认值为数组或对象是,必须为工厂函数返回的形式
Vue.component('MyComponentA', {
props: {
parArr: {
type: Array,
default: function () {
return [1, 2, 3]
}
}
},
template: `
<div>
{{ parArr }}
</div>
`
})
validator 用于给传入的 prop 设置校验函数,return 值为 false 时 Vue.js 会发生警告
Vue.component('MyComponentA', {
props; {
type: String,
validator: function (value) {
return value.startsWitn('lagou');
}
},
template: `
<div>
{{ parStr }}
</div>
`
})
注意:验证函数中无法使用实例的 data、methods 等功能
非 props 属性
当父组件给子组件设置了属性,但此属性在 props 中不存在,这时会自动绑定到子组件的根元素上
<script>
Vue.component('MyComponentA', {
template: `<p>子组件内容</p>`
})
</script>
<!-- ================== -->
<div id="app">
<my-component-a
demo-attr="示例属性"
title="示例title"
style="height: 200px"
class="colorBlue"
></my-component-a>
</div>
如果组件根元素已经存在了对应属性,则会替换组件内部的值
class 与 style 是例外,当内外都设置时,属性会自动合并
Vue.component('MyComponentA', {
template: `
<p title="原始title" class="fl" style="width: 200px;">子组件内容</p>
`
})
如果不希望继承父组件设置的属性,可以设置 inheritAttrs: false
,但是只适用于普通书写,class 与 style 不受影响
Vue.component('MyComponentA', {
inheritAttrs: false,
template: `
<p title="原始title" class="fl" style="width: 200px;">子组件内容</p>
`
})
子组件向父组件传值
子向父传值需要通过自定义事件实现
商品为子组件,购物车为父组件,父组件需统计商品个数,就需要在子组件个数变化时传值给父组件
<script>
new Vue({
el: "app",
data: {
products: [
{ id; 1, title: '苹果1斤' },
{ id; 2, title: '橙子2个' },
{ id; 3, title: '香蕉3根' }
],
totalCount: 0
}
})
</script>
<!-- ========================== -->
<div id="app">
<h3>购物车</h3>
<product-item
v-for="product in products"
:title="product.title"
:key="product.id"
></product-item>
<p>总数为:{{ totalCount }} </p>
</div>
<!-- 子组件 -->
<script>
Vue.component("product-item", {
props: ['title'],
template: `
<div>
<span>商品名称:{{ title }};商品个数:{{ count }}</span>
<button @click="countIns"> +1 </button>
</div>
`,
data () {
return { count: 0 }
}.
methods: {
countIns () {
this.count++
}
}
})
</script>
子组件数据变化时,通过 $emit()
触发自定义事件
Vue.component('product-item', {
// ...
methods: {
countIns () {
this.$emit('count-change');
this.count++
}
}
})
子组件向父组件传值
父组件监听子组件自定义事件,并设置处理程序
<div id="app">
<!-- ... -->
<product-item
@count-change="totalCount++"
></product-item>
<!-- ... -->
</div>
自定义事件传值
子组件触发事件时可以向父组件传值
Vue.component("product-item", {
props: ['title'],
template: `
<div>
<span>商品名称:{{ title }};商品个数:{{ count }}</span>
<button @click="countIns1"> +1 </button>
<button @click="countIns2"> +5 </button>
</div>
`,
data () {
return { count: 0 }
}.
methods: {
countIns1 () {
this.$emit('count-change', 1);
this.count++
}
countIns5 () {
this.$emit('count-change', 5);
this.count += 5
}
}
})
父组件在监听事件时需要接受子组件传递的数据
<div id="app">
<!-- ... -->
<product-item
@count-change="totalCount += $event"
></product-item>
<!-- ... -->
</div>
父组件在监听事件时需要接受子组件传递的数据
<div id="app">
<!-- ... -->
<product-item
@count-change="onCountChange"
></product-item>
<!-- ... -->
</div>
<!-- ======================================= -->
<script>
new Vue({
methods: {
onCountChange (productCount) {
this.totalCount += productCount;
}
}
})
</script>
组件与 v-model
v-model
用于组件时,需要通过 props 与自定义事件实现
<div id="app">
<p>输入内容为:{{ iptValue }}</p>
<com-input v-model="iptValue"></com-input>
</div>
<script>
new Vue({
el: "#app",
data: {
iptValue: ''
},
components: {
ComInput
}
})
</script>
<!-- ======================= -->
<script>
var ComInput = {
props:['value'],
template: `
<input
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
>
`
}
</script>
非父子组件传值
兄弟组件或完全无关的两个组件
- 兄弟组件传值
- EventBus
- 其他传值方式
兄弟组件传值
兄弟组件可以通过父组件进行数据中转
<!-- 父 -->
<script>
new Vue({
el: "#app",
data: {
value: ''
}
})
</script>
<div id="app">
<com-a
@value-change="value = $event"
></com-a>
<com-b
:value="value"
></com-b>
</div>
<!-- 子级A -->
<script>
Vue.component('ComA', {
template: `
组件A的内容:{{ value }}
<button
@click="$emit('value-change', value)"
>发送</button>
`,
data () {
return {
value: '示例内容'
}
}
})
</script>
<!-- 子级B -->
<script>
Vue.component('ComA', {
props: ['value'],
template: `
<div>
组件B接收到:{{ value }}
</div>
`
})
</script>
EventBus
当组件嵌套关系复杂时,根据组件关系传值会较为繁琐
组件为了数据中转,data 中会存在许多与当前组件功能无关的数据
- EventBus(事件总线)是一个独立的事件中心,用于管理不同组件间的传值操作
- EventBus 通过一个新的 Vue 实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递
// Event.js
var bus = new Vue()
发送数据的组件触发 bus 事件,接收的组件给 bus 注册对应事件
Vue.component("product-item", {
template: `
<div>
<span>商品名称:苹果;商品个数:{{ count }}</span>
<button @click="countIns"> +1 </button>
</div>
`,
data () {
return { count: 0 }
}.
methods: {
countIns () {
bus.$emit('countChange', 1);
this.count++;
}
}
})
给 bus 注册对应事件通过 $on()
操作
Vue.component("product-total", {
template: `
<p> 总个数为:{{ totalCout }} </p>
`,
data () {
return { totalCout: 0 }
}.
created () {
bus.$on('countChange', (productCount) => {
this.totalCount += productCount;
})
}
})
最后创建根实例执行代码即可
其他通信方式
- $root
- $refs
$root
$root 用于访问当前组件树根实例,设置简单的 Vue 应用时可以通过此方式进行组件传值
<div id="app">
<p>父组件数据:{{ count }}</p>
<com-a></com-a>
<com-b></com-b>
</div>
<script>
var ComA = {
template: `
<div>
组件 A :{{ $root.count }}
<button @click="clickFn"> +1 </button>
</div>
`,
methods: {
clickFn () {
this.$root.count++;
}
}
}
var ComB = {
template: `
<div>
组件 B :{{ $root.count }}
<button @click="clickFn"> +1 </button>
</div>
`,
methods: {
clickFn () {
this.$root.count++;
}
}
}
new Vue({
el: "#app",
data: {
count: 0
},
components: {
ComA,
ComB
}
})
</script>
除了 $root,Vue.js 中还提供了 $parent 与 $children 用于便捷访问父子组件
$refs
$refs 用于获取设置了 ref 属性的 HTML 标签或子组件
给普通 HTML 标签设置 ref 属性,$refs 可以获取 DOM 对象
<div id='app'>
<input type="text" ref="inp">
<button @click="fn">按钮</button>
</div>
<!-- ================= -->
<script>
new Vue({
el: '#app',
methods: {
fn () {
this.$refs.inp.focus();
}
}
})
</script>
给子组件设置 ref 属性,渲染后可通过 $refs 获取子组件实例
<div id='app'>
<com-a ref="comA">按钮</com-a>
</div>
<!-- ================= -->
<script>
new Vue({
el: '#app',
components: {
ComA
},
mounted () {
console.log(this.$refs);
this.$refs.comA.value = '修改了子组件数据'
}
})
</script>
给子组件设置 ref 属性,渲染后可通过 $refs 获取子组件实例
<div id='app'>
<com-a ref="comA">按钮</com-a>
</div>
<!-- ================= -->
<script>
var ComA = {
template: `<p>组件 A:{{ value }}</p>`,
data () {
return {
value: '这是组件A的数据'
}
}
}
new Vue({
el: '#app',
components: {
ComA
},
mounted () {
console.log(this.$refs);
this.$refs.comA.value = '修改了子组件数据'
}
})
</script>
组件插槽
组件插槽可以便捷的设置组件内容
<div id='app'>
<com-a>
示例内容
<span>组件的主体内容</span>
</com-a>
</div>
单个插槽
如果我们希望组件标签可以像 HTML 标签一样设置内容,那么组件的使用灵活度会很高
<div id='app'>
<p>示例内容1</p>
<com-a>示例内容2</com-a>
</div>
但平常我们书写的组件,组件首尾标签中书写的内容会被抛弃
<script>
Vue.component('com-a', {
template: `
<div>
<h3>组件标题</h3>
</div>
`
})
</script>
<!-- ==================== -->
<div id='app'>
<com-a>
示例内容
<span>组件的主体内容</span>
</com-a>
</div>
我们需要通过 <slot> 进行插槽设置
<script>
Vue.component('com-a', {
template: `
<div>
<h3>组件标题</h3>
<slot></slot>
</div>
`
})
</script>
<!-- ==================== -->
<div id='app'>
<com-a>
示例内容
<span>组件的主体内容</span>
</com-a>
</div>
需要注意模板内容的渲染位置
<div id='app'>
<com-a>
这里只能访问父组件的数据
{{ parValue }}
</com-a>
</div>
<!-- ==================== -->
<script>
new Vue({
el: "#app",
data: {
parValue: '父组件数据'
},
components: {
ComA
}
})
</script>
我们可以在 <slot> 中为插槽设置默认值,也称为后备内容
<scripiiiiiiiiii>
var ComA = {
template: `
<div>
<p>组件 A:</p>
<slot>这是默认文本</slot>
</div>
`,
data () {
return {
value: '子组件数据'
}
}
}
</scripiiiiiiiiii>
<!-- ==================== -->
<div id='app'>
<com-a></com-a>
</div>
具名插槽
如果组件中有多个位置需要设置插槽,据需要给 <slot> 设置 name,称为具名插槽
<com-a>
<template v-slot:header>
<h1>组件头部内容</h1>
</template>
<template v-slot:default>
<p>组件主体内容第一段</p>
<p>组件主体内容第二段</p>
</template>
<template v-slot:footer>
<h1>组件底部内容</h1>
</template>
</com-a>
<!-- 简写 -->
<com-a>
<template #header>
<h1>组件头部内容</h1>
</template>
<template #default>
<p>组件主体内容第一段</p>
<p>组件主体内容第二段</p>
</template>
<template #footer>
<h1>组件底部内容</h1>
</template>
</com-a>
<!-- =================================== -->
<script>
Vue.component('ComA', {
template: `
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
});
</script>
作用域插槽
用于让插槽可以使用子组件的数据
组件将需要被插槽使用的数据通过 v-bind
绑定给 <slot>,这种用于给插槽传递数据的属性称为插槽 prop
var ComA = {
template: `
<div>
<p>组件 A:</p>
<slot :value="value">这是默认文本</slot>
</div>
`,
data () {
return {
value: '子组件数据'
}
}
}
组件绑定数据后,插槽中需要通过 v-slot 接收数据
<div id="app">
<com-a>
<template v-slot:default="dataObj">
{{ dataObj.value }}
</template>
</com-a>
</div>
如果只存在默认插槽,同时又需要接收数据,可以进行简写
<div id="app">
<com-a v-slot:default="dataObj">
{{ dataObj.value }}
</com-a>
</div>
<!-- 更加简化的写法 -->
<div id="app">
<com-a v-slot="dataObj">
{{ dataObj.value }}
</com-a>
</div>
还可以通过 ES6 的解构操作进行数据接收
<div id="app">
<com-a v-slot:default="{ value }">
{{ value }}
</com-a>
</div>
动态组件
动态组件适用于多个组件频繁切换的处理
<component> 用于将一个 ‘元组件’ 渲染为动态组件,以 is 属性值决定渲染那个组件
<div id='app'>
<component :is=" 'ComA' "></component>
</div>
用于实现多个组件的快速切换,例如选项卡效果
<div id="app">
<button
v-for="title in titles"
:key="title"
@click="currentCom = title"
> {{ title }} </button>
<component :is="currentCom"></component>
</div>
<script>
var ComA = { template: `<div> A组件内容 </div>` };
var ComC = { template: `<div> B组件内容 </div>` };
var ComB = { template: `<div> C组件内容 </div>` };
new Vue({
el: "#app",
data: {
titles: ['ComA', 'ComB', 'ComC'],
currentCom: 'ComA'
},
components: {
ComA,
ComB,
ComC
}
})
</script>
is 属性会在每次切换组件时,Vue 都会创建一个新的组件实例
var ComA = {
template: `<div> A组件内容:<input type="text"> </div>`
};
var ComC = {
template: `<div> B组件内容:<input type="text"> </div>`
};
var ComB = {
template: `<div> C组件内容:<input type="text"> </div>`
};
keep-alive 组件
主要用于保留组件状态或避免组件重新渲染
<keep-alive>
<component :is="currentCom"></component>
</keep-alive>
include 属性用于指定那些组件会被缓存,具有多种设置方式
<keep-alive include="ComA,ComB,ComC">
<component :is="currentCom"></component>
</keep-alive>
<keep-alive include="['ComA', 'ComB', 'ComC']">
<component :is="currentCom"></component>
</keep-alive>
<keep-alive include="/Com[ABC]/">
<component :is="currentCom"></component>
</keep-alive>
exclude 属性用于指定哪些组件不会被缓存
<keep-alive exclude="ComD">
<component :is="currentCom"></component>
</keep-alive>
max 属性用于设置最大缓存个数
<keep-alive max="5">
<component :is="currentCom"></component>
</keep-alive>
过渡组件
用于在 Vue 插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡、动画效果
transition 组件
- 用于给元素和组件添加进入/离开过渡
- 条件渲染(v-if)
- 条件展示(v-show)
- 动态组件
- 组件根节点
- 组件提供了6个 class 用于设置过渡的具体效果
- 进入的类名
- v-enter
- v-enter-to
- v-enter-active
- 离开的类名
- v-leave
- v-leave-to
- v-leave-active
- 进入的类名
<transition>
<p v-if="showDemo"></p>
</transition>
<!-- =================== -->
<style>
.v-enter-active, .v-leave-active {
transition: all .5s;
}
.v-enter, .v-leave-to {
opacity: 0;
}
</style>
transition 组件的相关属性
给组件设置 name 属性,可以用于给多个元素、组件设置不同的过渡效果,这时需要将 v-
更改为对应的 name-
的形式
例如:
<transiton name="demo">
的对应类名前缀为:demo-enert
、demo-leave
<transition name="demo">
<p v-if="showDemo"></p>
</transition>
<!-- =================== -->
<style>
.demo-enter-active, .demo-leave-active {
transition: all .5s;
}
.demo-enter, .demo-leave-to {
opacity: 0;
}
</style>
通过 appear 属性,可以让组件在初始渲染时实现过渡
<transition appear>
<p v-if="showDemo"></p>
</transition>
自定义过渡类名
自定义类名比普通类名优先级更高,在使用第三方 CSS 动画库时非常有用
- 用于设置自定义过渡类名的属性如下:
- enter-…
- enter-class
- enter-active-class
- enter-to-class
- leave-…
- leave-class
- leave-active-class
- leave-to-class
- appear-…
- appear-class
- appear-to-class
- appear-active-class
- enter-…
<transition
enter-active-class = "test"
leave-active-class = "test"
>
<p v-if="showDemo"></p>
</transition>
<!-- =================== -->
<style>
.v-enter-active, .v-leave-active {
transition: all .5s;
}
.v-enter, .v-leave-to {
opacity: 0;
}
.test {
transition: all 3s;
}
</style>
Animate.css 是一个第三方 CSS 动画库,通过设置类名来给元素添加各种动画效果
<transition
enter-active-class = "animate_bounceInDown"
leave-active-class = "animate_bounceOutDown"
>
<p v-if="showDemo"></p>
</transition>
- 使用注意:
- animate_ 前缀与 compat 版本
- 基础类名 animated
transition-group 组件
- <transition-group> 用于给列表统一设置过渡动画
- tag 属性用于设置容器元素,默认为<span>
- 过渡会应用于内部元素,而不是容器
- 子节点必须有独立的 key 动画才能正常工作
当列表元素变更导致元素位移,可以通过 .v-move
类名设置移动时的效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
ul {
position: relative;
}
.v-enter, .v-leave-to {
opacity: 0;
transform: translateX(100px);
}
.v-enter-active, .v-leave-active {
transition: all .5s;
}
/* 让元素在离场的过程中脱离标准流 */
.v-leave-active {
position: absolute;
}
.v-move {
transition: all .5s;
}
</style>
</head>
<body>
<div id="app">
<input type="text"
v-model="newTitle"
@keyup.enter="addItem"
>
<transition-group
tag="ul"
>
<li
v-for="item in items"
:key="item.id"
@click="removeItem(item)"
>
{{ item.title }}
</li>
</transition-group>
</div>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
items: [
{ id: 1, title: '示例内容1'},
{ id: 2, title: '示例内容2'},
{ id: 3, title: '示例内容3'},
{ id: 4, title: '示例内容4'},
{ id: 5, title: '示例内容5'},
],
newTitle: '',
latestId: 5
},
methods: {
addItem () {
this.items.push({
id: this.latestId + 1,
title: this.newTitle
});
this.latestId++;
this.newTitle = '';
},
removeItem (item) {
var i = this.items.indexOf(item);
this.items.splice(i, 1);
}
}
});
</script>
</body>
</html>