一、组件
组件(Component)是 Vue.js 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:
注册一个全局组件语法格式如下:
Vue.component(tagName, options)
tagName 为组件名,options 为配置选项。注册后,我们可以使用以下方式来调用组件:
<tagName></tagName>
- 全局组件
所有实例都能用全局组件。
main.js
Vue.config.productionTip = false
//全局组件
Vue.component("my-component",{
template:"<h1>我是全局组件</h1>"
});
new Vue({
el:"#app"
});
new Vue({
el:"#app1"
})
index.html
<div id="app">
<my-component></my-component>
</div>
<div id="app1">
<my-component></my-component>
</div>
- 局部组件
可以在实例选项中注册局部组件,这样组件只能在这个实例中使用:
<div id="app">
<hello></hello>
</div>
<script>
var Child = {
template: '<h1>自定义组件!</h1>'
}
// 创建根实例
new Vue({
el: '#app',
components: {
// <hello> 将只在父模板可用
'hello': Child
}
})
</script>
- Prop
1)父传子
prop 是父组件用来传递数据的一个自定义属性。
父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 "prop":
父组件:
<template>
<div id="app">
<child message="hello!"></child>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
"child":HelloWorld
}
};
</script>
子组件:
<template>
<div class="hello">
子组件:
<span>{{message}}</span>
</div>
</template>
<script>
export default {
name: "HelloWorld",
// 接受父组件的值
props: {
message: String
}
};
</script>
- 子传父
子组件:
<template>
<div class="hello">
子组件:
<span>{{childValue}}</span>
<!-- 定义一个子组件传值的方法 -->
<input type="button" value="点击触发" @click="childClick">
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
childValue: "我是子组件的数据"
};
},
methods: {
childClick() {
// childByValue是在父组件on监听的方法
// 第二个参数this.childValue是需要传的值
this.$emit("childByValue", this.childValue);
}
}
};
</script>
父组件:
<template>
<div id="app">
父组件:
<span>{{name}}</span>
<br>
<br>
<!-- 引入子组件 定义一个on的方法监听子组件的状态-->
<child v-on:childByValue="childByValue"></child>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
child: HelloWorld
},
data() {
return {
name: ""
};
},
methods: {
childByValue: function(childValue) {
// childValue就是子组件传过来的值
this.name = childValue;
}
}
};
</script>
3)非父子组件进行传值
非父子组件之间传值,需要定义个公共的公共实例文件bus.js,作为中间仓库来传值,不然路由组件之间达不到传值的效果。
组件A:
<template>
<div>
A组件:
<span>{{elementValue}}</span>
<input type="button" value="点击触发" @click="elementByValue">
</div>
</template>
<script>
// 引入公共的bug,来做为中间传达的工具
import Bus from "./bus.js";
export default {
data() {
return {
elementValue: 4
};
},
methods: {
elementByValue: function() {
Bus.$emit("val", this.elementValue);
}
}
};
</script>
组件B:
<template>
<div>
B组件:
<input type="button" value="点击触发" @click="getData">
<span>{{name}}</span>
</div>
</template>
<script>
import Bus from './bus.js'
export default {
data () {
return {
name: 0
}
},
mounted: function () {
var vm = this
// 用$on事件来接收参数
Bus.$on('val', (data) => {
console.log(data)
vm.name = data
})
},
methods: {
getData: function () {
this.name++
}
}
}
</script>
bus.js
import Vue from 'vue'
export default new Vue()
App.vue调用:
<template>
<div id="app">
<comA/>
<comB/>
</div>
</template>
<script>
import comA from "./components/comA";
import comB from "./components/comB";
export default {
name: "App",
components: {
comA,
comB
},
data() {
return {
name: ""
};
},
methods: {}
};
</script>
4 .动态 Prop
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件:
<div id="app">
<div>
<input v-model="parentMsg">
<br>
<child v-bind:message="parentMsg"></child>
</div>
</div>
<script>
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 "this.message" 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app',
data: {
parentMsg: '父组件内容'
}
})
</script>
将 v-bind 指令将 todo 传到每一个重复的组件中:
<div id="app">
<ol>
<todo-item v-for="item in sites" v-bind:todo="item"></todo-item>
</ol>
</div>
<script>
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
new Vue({
el: '#app',
data: {
sites: [
{ text: 'Baidu' },
{ text: 'Google' },
{ text: 'Taobao' }
]
}
})
</script>
意: prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。
- Prop 验证
组件可以为 props 指定验证要求。
prop 是一个对象而不是字符串数组时,它包含验证要求:
Vue.component('example', {
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
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 value > 10
}
}
}
})
二、 自定义事件
父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件!
我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:
使用 emit(eventName) 触发事件
另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
实例中子组件已经和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件。
<div id="app">
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
</div>
<script>
Vue.component('button-counter', {
template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementHandler: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
</script>
如果你想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on 。
<my-component v-on:click.native="doTheThing"></my-component>
三、data 必须是一个函数
data: function () {
return {
count: 0
}
}
这样的好处就是每个实例可以维护一份被返回对象的独立的拷贝,如果 data 是一个对象则会影响到其他实例:
<div id="components-demo3" class="demo">
<button-counter2></button-counter2>
<button-counter2></button-counter2>
<button-counter2></button-counter2>
</div>
<script>
var buttonCounter2Data = {
count: 0
}
Vue.component('button-counter2', {
/*
data: function () {
// data 选项是一个函数,组件不相互影响
return {
count: 0
}
},
*/
data: function () {
// data 选项是一个对象,会影响到其他实例
return buttonCounter2Data
},
template: '<button v-on:click="count++">点击了 {{ count }} 次。</button>'
})
new Vue({ el: '#components-demo3' })
</script>
为什么vue中data必须是一个函数:
类比引用数据类型
Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响
举个🌰
const MyComponent = function() {};
MyComponent.prototype.data = {
a: 1,
b: 2,
}
const component1 = new MyComponent();
const component2 = new MyComponent();
component1.data.a === component2.data.a; // true;
component1.data.b = 5;
component2.data.b // 5
如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改;
两个实例应该有自己各自的域才对,需要通过下面的方法来进行处理
const MyComponent = function() {
this.data = this.data();
};
MyComponent.prototype.data = function() {
return {
a: 1,
b: 2,
}
};
这样么一个实例的data属性都是独立的,不会相互影响了.
附:
组件命名约定
当注册组件 (或者 prop) 时,可以使用 kebab-case (短横线分隔命名)、camelCase (驼峰式命名) 或 PascalCase (单词首字母大写命名)。
// 在组件定义中
components: {
// 使用 kebab-case 注册
'kebab-cased-component': { /* ... */ },
// 使用 camelCase 注册
'camelCasedComponent': { /* ... */ },
// 使用 PascalCase 注册
'PascalCasedComponent': { /* ... */ }
}
在 HTML 模板中,请使用 kebab-case:
<!-- 在 HTML 模板中始终使用 kebab-case -->
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<pascal-cased-component></pascal-cased-component>
当使用字符串模式时,可以不受 HTML 大小写不敏感的限制。这意味实际上在模板中,你可以使用下面的方式来引用你的组件:
kebab-case
camelCase 或 kebab-case (如果组件已经被定义为 camelCase)
kebab-case、camelCase 或 PascalCase (如果组件已经被定义为 PascalCase)
components: {
'kebab-cased-component': { /* ... */ },
camelCasedComponent: { /* ... */ },
PascalCasedComponent: { /* ... */ }
}
以下方式全可:
<kebab-cased-component></kebab-cased-component>
//带中杠
<camel-cased-component></camel-cased-component>
//首字母小写
<camelCasedComponent></camelCasedComponent>
<pascal-cased-component></pascal-cased-component>
<pascalCasedComponent></pascalCasedComponent>
<PascalCasedComponent></PascalCasedComponent>