组件用于封装页面的部分功能,将功能的结构、样式、逻辑代码封装为整体。
提高功能的复用性与可维护性,更好的专注于业务逻辑。
组件使用时为自定义 HTML 标签形式,通过组件名作为自定义标签名。
组件注册
全局注册
全局注册的组件在注册后可以用于任意实例或组件中。
注意:全局注册必须设置在根 Vue 实例创建之前。
<body>
<div id="app">
<p>这是p标签</p>
<my-component></my-component>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-component', {
template: '<div>这是我们全局注册的组件</div>'
});
// 根实例
new Vue({
el: '#app',
data: {
}
})
</script>
</body>
复制代码
组件基础
本质上,组件是可复用的 Vue 实例,所以它们可与 new Vue 接收相同的选项,例如 data
、methods
以及生命周期钩子等。
注意 仅有的例外是像 el
这样根实例特有的选项。因为根实例是需要挂载到页面上一个元素中使用的, 而组件是被根实例或被其他组件使用的, 不需要挂载到页面中, 所以没有el.
组件命名规则
组件具有两种命名规则:
- kebab-case:
'my-component'
- PascalCase:
'MyComponent'
注意:无论采用哪种命名方式,在 DOM 中都只有 kebab-case
可以使用。
<body>
<div id="app">
<my-com-a></my-com-a>
<!-- <MyComA></MyComA> 会报错-->
<my-com-b></my-com-b>
<!-- <MyComB></MyComB> 会报错-->
</div>
<script src="lib/vue.js"></script>
<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>
</body>
复制代码
template 选项
template 选项用于设置组件的结构,最终被引入根实例或其他组件中。
注意:组件必须只有一个根元素。也就是说不能在<div>
同级的位置再设置其他元素了.
<body>
<div id="app">
<my-com-a></my-com-a>
</div>
<script src="lib/vue.js"></script>
<script></script>
</body>
复制代码
data 选项
data 选项用于存储组件的数据,与根实例不同,组件的 data 选项必须为函数,数据设置在返回值对象中。
这种实现方式是为了确保每个组件实例可以维护一份被返回对象的独立的拷贝,不会相互影响。因为函数的作用域只在它内部有效.
ES6简写方式:
<body>
<div id="app">
<my-com-a></my-com-a>
</div>
<script src="lib/vue.js"></script>
<script></script>
</body>
复制代码
局部注册
局部注册的组件只能用在当前实例或组件中。
有两种注册方式:
1.直接在Vue实例中添加
<body>
<div id="app">
<my-com-a></my-com-a>
<my-com-b></my-com-b>
</div>
<div id="app2">
<my-com-a></my-com-a>
<my-com-b></my-com-b>
</div>
<script src="lib/vue.js"></script>
<script></script>
</body>
复制代码
2.单独配置组件的选项对象
更便于维护
ES6简写方式: (兼容性不如上面的方式)
<body>
<div id="app">
<my-component-a></my-component-a>
<my-component-b></my-component-b>
</div>
<script src="lib/vue.js"></script>
<script></script>
</body>
复制代码
组件通信
在组件间传递数据的操作,称为组件通信。根据参与组件的不同, 传递方式不同.
父组件向子组件传值
通过子组件的 props
选项接收父组件的传值. 接受后可以直接在template
中使用.
注意:props
不要与 data
存在同名属性, 否则会出现覆盖问题.
父组件设置方式如下:
-
没有加冒号的title是静态传递的, 没法更改.
-
加了冒号的title是动态传递的, 文字要加引号
<body>
<div id="app">
<my-component-a title="这是静态标题" content="这是静态内容"></my-component-a>
<!-- 绑定动态内容, 常用 -->
<my-component-a :title="item.title" :contnet="item.content"></my-component-a>
</div>
<script src="lib/vue.js"></script>
<script></script>
</body>
复制代码
Props 命名规则
建议 prop 命名使用 camelCase,父组件绑定时使用 kebab-case
<body>
<div id="app">
<!-- 通过 v-for 遍历数据 items,创建组件并生成内容 -->
<demo-item v-for="item in items" :key="item.title"
:item-title="item.title" :item-content="item.content"></demo-item>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component("demoItem",{
props: ["itemTitle","itemContent"],
template: `
<div>
<h3>{{ itemTitle }}</h3>
<p> {{ itemContent }} </p>
</div>
`
})
new Vue({
el: '#app',
data: {
// 准备给子组件使用的数据
items: [
{
title: '示例标题1',
content: '示例内容1'
},
{
title: '示例标题2',
content: '示例内容2'
},
{
title: '示例标题3',
content: '示例内容3'
},
]
}
})
</script>
</body>
复制代码
单向数据流
父子组件间的所有 prop 都是单向下行绑定的。父组件的数据修改会影响到子组件, 但子组件中的数据修改不会影响到父组件.
如果子组件要处理 prop 数据,应当存储在 data 中(或使用计算属性)后操作。
注意:
如果 prop 为数组或对象时,子组件操作将会影响到父组件的状态。因为是一种引用的形式, 获取了地址.
解决方法:
- 将数据存在data中再进行操作
- 避免传入整个数组或者对象, 而是只传入对象的某个属性值.
<body>
<div id="app">
<my-component
:initial-title="title"
:obj="obj"
></my-component>
</div>
<script src="lib/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['initialTitle', 'obj'],
template: `
<div>
{{ title }}
<button @click="fn">按钮</button>
</div>
`,
data () {
return {
title: this.initialTitle
// title: this.initialTitle = "这是新的 标题"
}
},
methods: {
fn () {
// this.title = '这是新的标题';
// this.initialTitle = '这是新的标题'; // 不会影响父组件
this.obj.name = 'jack'; //会导致root中的名称也改为jack
}
}
});
new Vue({
el: '#app',
data: {
title: '这是示例标题',
obj: {
name: 'william',
age: 18
}
}
});
</script>
</body>
复制代码
Props 类型
Prop 可以设置类型检查,这时需要将 props 更改为一个带有验证需求的对象,并指定对应类型。
-
在后面设置上对应的构造函数就可以了.
String
和Array
都是构造函数. -
如果不限制类型, 可以设置为
null
或者undefined
prop 还可以同时指定多个类型,通过数组方式保存即可。
<body>
<div id="app">
<my-component
:par-str="str"
:par-num="num"
:par-arr="arr"
:par-obj="obj"
:par-any="any"
:par-data="str"
></my-component>
</div>
<script src="lib/vue.js"></script>
<script></script>
</body>
复制代码
Props 验证
当 prop 需要设置多种规则时,可以将 prop 的值设置为选项对象。
- 之前的类型检测功能通过
type
选项设置。
其他的选项:
-
required
用于设置数据为必填项。(eg.required: true
) -
default
用于给可选项指定默认值,当父组件未传递数据时生效。(eg.default: 100
) -
required
和default
是不能同时作用于一个prop
的, 因为required
要求必须传值.
注意:当默认值为数组或对象时,必须为工厂函数返回的形式。
或者:
validator
用于给传入的 prop 设置校验函数,return 值为false
时 Vue.js 会发出警告。具体内容规则的检测.
注意:验证函数中无法使用实例的 data、methods 等功能。
props
是在实例创建完之前做验证, 所以它没有办法使用data, methods之类的内容. 这时候的this指的是window, 而不是vue实例.
非 Props 属性
当父组件给子组件设置了属性,但此属性在 props
中没有接收,这时会自动绑定到子组件的根元素上。自定义指令也会绑定到根元素上.
比如说父元素中绑定了这些值:
而在子元素内部并没有接受这些值, 那么这些值会绑定到 <div>
上.
特殊情况
-
如果组件根元素已经存在了对应属性,则新属性会替换原来的值。
-
class
与style
是例外,当内外都设置时,属性会自动合并。 -
如果不希望继承父组件设置的属性,可以设置
inheritAttrs:false
,但只适用于普通属性,class 与 style 不受影响。
例1
<div id="app">
<my-component
data-index="3"
:title="'新的标题内容'"
style="height: 200px;"
class="colorRed"
></my-component>
</div>
Vue.component('MyComponent', {
template: `
<div data-index="7"
title="旧的title"
class="abc"
style="width: 200px;">
<p>这是组件的内容</p>
</div>
`
});
复制代码
例2
<div id="app">
<my-component
data-index="3"
:title="'新的标题内容'"
style="height: 200px;"
class="colorRed"
></my-component>
</div>
Vue.component('MyComponent', {
inheritAttrs: false,
template: `
<div data-index="7"
title="旧的title"
class="abc"
style="width: 200px;">
<p>这是组件的内容</p>
</div>
`
});
复制代码
子组件向父组件传值
子向父传值需要通过自定义事件实现。子组件进行数据修改的时候触发一个事件, 父组件监听.
例 商品为子组件,购物车为父组件,父组件需要统计商品个数,就需要在子组件个数变化时传值给父组件。
子组件数据变化时,通过 $emit()
触发自定义事件。$emit()
是vue实例的一个方法, 它内部可以传入一个名称, 就可以触发这个名称的自定义事件. 不需要手动触发.
- 自定义事件名称建议使用 kebab-case。在父元素中写成
@count-change
父组件监听子组件的自定义事件,并设置处理程序。
- 父组件监听到
count-change
事件触发后, 就将totalCount
自加1
自定义事件传值
如果每次只告诉父组件触发了事件, 不太方便. 比如每次+5, 因此可以向父组件传值.
子组件触发事件时可以向父组件传值。
$emit()
的第一个参数是触发的事件名称, 第二个参数就是传递的值(对象或者变量等都可以)
方式一: 直接给自定义事件设置代码
父组件在监听事件时需要接收子组件传递的数据。
方式二: 通过处理函数
- 这里的
productCount
就是$emit()
传递的值.
组件与 v-model
v-model 用于组件时,需要通过 props
与自定义事件实现。因为组件之间是相互独立的.
v-model
本身就有传入数据和数据接受的功能, 所以不需要写:value="..."
,所以子组件只需要用props
接收就可以了.
-
value
就是v-model
绑定的那个值 -
通过
@input
方式让输入框在进行输入的时候进行$emit()
事件触发 (input事件是实时监控的,每次输入都会调用),$emit()
不仅可以触发自定义事件还可以触发内部固有的一些事件 -
$event
是事件对象,target
是当前的输入框,value
是输入的数据.
方法一:
<body>
<div id="app">
<p>输入框内容为:{{ iptValue }}</p>
<com-input v-model="iptValue"></com-input>
</div>
<script src="lib/vue.js"></script>
<script>
var comInput = {
props: ["value"],
template: `
<input type="text"
:value="value"
@input="$emit('input', $event.target.value)">
`
}
// 根实例
new Vue({
el: '#app',
data: {
iptValue: ''
},
components: {
comInput
}
});
</script>
</body>
复制代码
方法二:
<body>
<div id="app">
<p>输入框内容为:{{ iptValue }}</p>
<com-input v-model="iptValue"></com-input>
</div>
<script src="lib/vue.js"></script>
<script>
var comInput = {
props: ["value"],
template: `
<input type="text"
:value="value"
@input="onInput">
`,
methods: {
onInpt(event){
this.$emit('input', $event.target.value)
}
}
}
// 根实例
new Vue({
el: '#app',
data: {
iptValue: ''
},
components: {
comInput
}
});
</script>
</body>
复制代码
非父子组件传值
非父子组件指的是兄弟组件或完全无关的两个组件。
兄弟组件传值
兄弟组件可以通过父组件进行数据中转.
<body>
<div id="app">
<!-- 父组件接收子组件A的数据 -->
<com-a
@value-change="value = $event"
></com-a>
<!-- 父组件将数据传递给子组件B -->
<com-b
:value="value"
></com-b>
</div>
<script src="lib/vue.js"></script>
<script>
// 子组件A:发送数据
Vue.component('ComA', {
template: `
<div>
组件A的内容: {{ value }}
<button
@click="$emit('value-change', value)"
>发送</button>
</div>
`,
data () {
return {
value: '这是组件A中的数据'
}
}
});
// 子组件B:接收数据
Vue.component('ComB', {
props: ['value'],
template: `
<div>
组件B接收到: {{ value }}
</div>
`
});
// 根实例(父组件)
new Vue({
el: '#app',
data: {
// 用于数据中转
value: ''
}
})
</script>
</body>
复制代码
EventBus (任意组件传值)
EventBus
(事件总线)是一个独立的事件中心,用于管理不同组件间的传值操作。只负责传值, 本身不存储数据.
EventBus 通过一个新的 Vue 实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递。通常被存储在一个单独的文件中.
优点:
- data 中不会存在许多与当前组件功能无关的数据。
- 不需要寻找组件之间的关系.
做法:
发送数据的组件触发 bus 事件,接收的组件给 bus 注册对应事件。
- 发送数据的组件触发 bus 事件
- 之前整理用的是
this.$emit()
, 指的是当前组件的vue实例. 这里使用bus.$emit()
是用的bus
组件的vue实例, 而bus
可以被任意组件访问.
- 接收的事件给 bus 注册对应事件通过
$on()
操作。
-
created()
是生命周期函数, 因为要使用totalCount
, 所以要在实例创建完毕后进行处理. -
参数一: 事件名称
-
参数二: 事件处理程序. 箭头函数中的
this
指的是当前这个product-total
组件.因为箭头函数不会修改this
, 因为它本身没有this
, 它所用的this
是当前环境(creat()
)内部的this
, 也就是当前函数的实例. 如果这里使用普通函数的话, 需要先在外部做一个存储.
<body>
<div id="app">
<h3>购物车</h3>
<product-item v-for="product in products" :key="product.id" :title="product.title"></product-item>
<product-total></product-total>
</div>
<script src="lib/vue.js"></script>
<script src="EventBus.js"></script>
<script>
Vue.component("productItem",{
props: ['title'],
template: `
<div>
<span>商品名称: {{ title }}, 商品个数: {{count}}</span>
<button @click="onChange">+1</button>
</div>
`,
data: function(){
return {
count: 0
}
},
methods: {
onChange(){
// 给bus触发自定义事件,传递数据
bus.$emit("countChange",1);
this.count++;
}
}
})
Vue.component("productTotal",{
template:`
<span>总个数为: {{totalCount}}</span>
`,
data: function(){
return {
totalCount: 0
}
},
created(){
// 给 bus 注册事件,并接收数据
bus.$on("countChange", (productCount) => {
this.totalCount += productCount;
})
}
})
var bus = new Vue();
// 根实例
new Vue({
el: '#app',
data: {
products: [
{
id: 1,
title:"苹果"
},
{
id:2,
title:"香蕉"
},
{
id: 3,
title:"橙子"
}
]
}
});
</script>
</body>
复制代码
其他传值方式
如果没有特殊需求, 不建议使用. 出错时比较难排除错误.
$root
$root
用于访问当前组件树根实例,设置简单的 Vue 应用时可以通过此方式进行组件传值。
除了 $root
, Vue.js 中还提供了 $parent
与 $children
用于便捷访问父子组件。
- A和B组件都是直接访问vue根实例中的
count
.
<body>
<div id="app">
<com-a></com-a>
</div>
<script src="lib/vue.js"></script>
<script></script>
</body>
复制代码
点击A按钮, count都变为100; 点击B按钮, count都变为200.
$refs
$refs
用于获取设置了 ref
属性的 HTML 标签或子组件。
给普通 HTML 标签设置 ref 属性,$refs
可以获取 DOM
对象。
- 通过
this.$refs.inp
可以获取到<input>
标签, 给它设置focus()
可以使<input>
自动获取焦点.
组件插槽
后续补充
内置组件
后续补充