03vue组件化
vue组件
1.vue组件三板斧
1.创建组件构造器对象
2.注册组件
3.使用组件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<!-- 3.使用组件 -->
<my-cpn></my-cpn>
<h1>***</h1>
<cpnc></cpnc>
<cpnc2></cpnc2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
//1.创建组件构造器的对象
const cpnc = Vue.extend({
template: `
<div>
<h2>标题:我是cpnc组件</h2>
<p>内容1...</p>
<p>内容2...</p>
</div>`,
})
const cpnc2 = Vue.extend({
template: `
<div>
<h2>我是cpnc2组件</h2>
<p>内容1...我是一个局部组件</p>
<p>内容2...</p>
</div>`,
})
//2.全局注册组件
Vue.component('my-cpn', cpnc)
const app = new Vue({
el:"#app",
components:{
//局部组件创建
cpnc:cpnc,
cpnc2:cpnc2,
}
})
</script>
</body>
</html>
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 my-cpn。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用: <my-cpn></my-cpn>
。
2.vue 全局组件和局部组件
全局组件,可以在多个vue实例中使用,类似于全局变量。
使用Vue.component('my-cpn', cpnc)
方式注册,直接使用``调用。my-cpn
是全局组件的名字,cpnc
是定义的组件对象。
局部组件,只能在当前vue实例挂载的对象中使用,类似于局部变量,有块级作用域。
使用方式与全局变量一样,直接使用<cpnc></cpnc>
调用。cpnc:cpnc
第一个cpnc是给组件命名的名字,第二个是定义的组件对象。如果俩个同名也可以直接使用es6语法:
components:{//局部组件创建
cpnc
}
// 注册全局组件(全局组件,可以在多个vue实例中使用)
Vue.component('my-cpn', cpnc)
// 注册局部组件,components:{cpnc:cpnc}
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpnc:cpnc
}
})
3.vue 父组件和子组件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>父子组件</title>
</head>
<body>
<div id="app">
<!-- 使用组件 -->
<cpn2></cpn2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
//1.创建组件构造器对象
const cpn1 = Vue.extend({
template: `
<div>
<h2>我是子组件1</h2>
<p>哈哈</p>
</div>`,
})
//2.构建父组件,并使用子组件cpn1
const cpn2 = Vue.extend({
template: `
<div>
<h2>我是父组件</h2>
<p>哈哈<br>******</p>
<cpn1></cpn1>
</div>`,
components:{
cpn1:cpn1
}
})
const app = new Vue({
el:"#app",
components:{ //创建局部组件
cpn2:cpn2,
}
})
</script>
</body>
</html>
上述代码中定义了两个组件对象cpn1
和cpn2
,在组件cpn2
中使用局部组件注册了cpn1
,并在template
中使用了注册的cpn1
,然后在vue实例中使用注册了局部组件cpn2
,在vue实例挂载的div中调用了cpn2
,cpn2
与cpn1
形成父子组件关系。
注意:组件就是一个vue实例,vue实例的属性,组件也可以有,例如data、methods、computed等。
4、注册组件的语法糖写法
<script>
// 1.注册全局组件语法糖
Vue.component('cpn1', {
template:`
<div>
<h2>全局组件语法糖</h2>
<p>全局组件语法糖</p>
</div>`
})
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpn2:{
template:`
<div>
<h2>局部组件语法糖</h2>
<p>局部组件语法糖</p>
</div>`
}
}
})
</script>
注册组件时候可以不实例化组件对象,直接在注册的时候实例化。{}
就是一个组件对象。
5. 组件模板的分离写法
5.1 script标签
使用script
标签定义组件的模板,script
标签注意类型是text/x-template
。
<!-- 1.script标签注意类型是text/x-template -->
<script type="text/x-template" id="cpn1">
<div>
<h2>组件模板的分离写法</h2>
<p>script标签注意类型是text/x-template</p>
</div>
</script>
5.2 template标签
使用template
标签,将内容写在标签内。
<!-- 2.template标签 -->
<template id="cpn2">
<div>
<h2>组件模板的分离写法</h2>
<p>template标签</p>
</div>
</template>
使用分离的模板 ,使用template:'#cpn1'
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1:{
template:'#cpn1'
},
cpn2: {
template: '#cpn2'
}
}
})
</script>
//全局组件创建
Vue.component('cpn1', {
template:'#cpn1',
})
例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<!-- 3.使用组件 -->
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<!-- 1.script标签注意类型是text/x-template -->
<script type="text/x-template" id="cpn1">
<div>
<h2>组件模板的分离写法</h2>
<p>script标签注意类型是text/x-template</p>
</div>
</script>
<template id="cpn2">
<div>
<h2>组件模板的分离写法</h2>
<p>template标签</p>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
Vue.component('cpn1', {
template:'#cpn1',
})
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpn2:{
template:'#cpn2'
},
}
})
</script>
</body>
</html>
6. 组件的数据
6.1数据存放问题
前面说过vue组件就是一个vue实例,相应的vue组件也有data
属性来存放数据。
放在主app下面是没有效果的。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1:{
template:'<div>{{msg}}</div>',
data(){
return {
msg:"组件的数据存放必须要是一个函数"
}
}
}
}
})
</script>
</body>
</html>
在template
中使用组件内部的数据msg
。
6.2 组件data为啥必须是个函数
组件的思想是复用,定义组件当然是把通用的公共的东西抽出来复用。
计算器1
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<template id="cpn1">
<div>
当前计数:{{ count }}<br>
<button @click="decrement">-</button>
<button @click="increment">+</button>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1: {
template: '#cpn1',
data() {
return{
count:0,
}
},
methods:{
increment(){
this.count++
},
decrement(){
this.count--
}
}
}
}
})
</script>
</body>
</html>
计算器二
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<template id="cpn1">
<div>
当前计数:{{ count }}<br>
<button @click="count--">-</button>
<button @click="count++">+</button>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1: {
template: '#cpn1',
data() {
return{
count:0,
}
}
}
}
})
</script>
</body>
</html>
计数器1和计数器2本质是一样的,实现计数器的复用,各个计数器互不干扰。
计数器三,组件共用一个数字
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<template id="cpn1">
<div>
当前计数:{{ count }}<br>
<button @click="count--">-</button>
<button @click="count++">+</button>
</div>
</template>
<script>
const obj={
count:0
}
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1: {
template: '#cpn1',
data() {
return obj;
}
}
}
})
</script>
</body>
</html>
计数器3不使用函数data
,好像共用一个count
属性,而使用函数的data
的count是各自用各自的,像局部变量一样有块级作用域,这个块级就是vue组件的作用域。
我们在复用组件的时候肯定希望,各自组件用各自的变量,如果确实需要都用一样的,可以全局组件注册,也可以是用vuex来进行状态管理。
7、父子组件通信之父传子
使用props
属性,父组件向子组件传递数据
静态写法:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<child message="hello!父传子静态写法"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 "this.message" 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app'
})
</script>
</body>
</html>
动态v-bind props 写法
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<div>
<input v-model="parentMsg">
<br>
<child v-bind:message="parentMsg"></child>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 "this.message" 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app',
data: {
parentMsg: '父组件内容'
}
})
</script>
</body>
</html>
props 属性
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
}
}
}
})
综合使用
<div id="app">
<cpn :cMovies="movies" :cMessage="message"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="(item, index) in cmovies" :key="index">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
function Person(firstName,lastName) {
this.firstName = firstName
this.lastName = lastName
}
// 父传子:props
const cpn = {
template: "#cpn",
// props: ['cmovies', 'cmessage'],//数组写法
props: { //对象写法
// 1.类型限制(多个类使用数组)
// cmovies:Array,
// cmessage:String,
// cmessage:['String','Number'],
// 2.提供一些默认值,以及必传值
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
},
//类型是Object/Array,默认值必须是一个函数
cmovies: {
type: Array,
default () {
return [1, 2, 3, 4]
}
},
// 3.自定义验证函数
// vaildator: function (value) {
// //这个传递的值必须匹配下列字符串中的一个
// return ['zzzzz', 'ttttt', 'yyy'].indexOf(value) !== -1
// }
// 4.自定义类型
// cmessage:Person,
},
data() {
return {
}
},
methods: {
},
};
const app = new Vue({
el: "#app",
data: {
message: "你好",
movies: ["复仇者联盟", "钢铁侠", "星际穿越", "哪吒传奇"]
},
components: {
cpn
}
})
</script>
补充:props的驼峰标识
v-bind是 不支持使用驼峰标识的,例如cUser
要改成c-User
<div id="app">
<!-- v-bind不支持驼峰 :cUser改成 :c-User-->
<!-- <cpn :cUser="user"></cpn> -->
<cpn :c-User="user"></cpn>
<cpn :cuser="user" ></cpn>
</div>
<template id="cpn">
<div>
<!-- 使用驼峰 -->
<h2>{{cUser}}</h2>
<!-- 不使用 -->
<h2>{{cuser}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
props: { //对象写法
//驼峰
cUser:Object,
//未使用驼峰
cuser:Object
},
data() {return {}},
methods: {},
};
const app = new Vue({
el: "#app",
data: {
user:{
name:'zzz',
age:18,
height:175
}
},
components: {
cpn
}
})
</script>
8、子传父
子组件向父组件传值,使用自定义事件$emit
。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<!-- 不写参数默认传递btnClick的item -->
<cpn @itemclick="cpnClcik"></cpn>
</div>
<!-- 子组件模板 -->
<template id="cpn">
<div>
<button v-for="(item, index) in categoties" :key="index" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
//子组件
const cpn = {
template: "#cpn",
data() {
return {
categoties: [{
id: 'aaa',
name: '热门推荐'
},
{
id: 'bbb',
name: '手机数码'
},
{
id: 'ccc',
name: '家用家电'
},
{
id: 'ddd',
name: '电脑办公'
},
]
}
},
methods: {
btnClick(item) {
this.$emit('itemclick', item)
}
},
};
//父组件
const app = new Vue({
el: "#app",
data() {
return {
}
},
methods: {
cpnClcik(item) {
console.log('cpnClick' + item.name);
}
},
components: {
cpn
},
})
</script>
</body>
</html>