组件注册

1
2
3
4
Vue.component(组件名称,{
data:组件数据,
template:组件模板内容
})

简单用法

1
2
3
4
5
6
7
8
9
10
11
12
13
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button @click="add">点击了{{count}}次</button>',
methods: {
add() {
this.count++
}
},
})
1
2
3
<div id="app">
<button-counter><button-counter>
</div>

dbd73b9c-bbcf-4746-a292-102c72f8a4db

组件可以多次复用,并且每个组件之间互不影响。

组件注意事项

  1. 定义组件时data属性为一个函数,函数内返回一个对象。这个对象中包含数据

    1
    2
    3
    4
    5
    data: function () {
    return {
    count: 0
    }
    }
  2. 组件模板内容必须是单个根元素

    1
    template: '<button @click="add">点击了{{count}}次<p>p标签</p></button>',
  3. 组件模板内容可以是模板字符串

  4. 组件命名方式

    • 横线

      1
      2
      3
      4
      5
      6
      7
      8
      Vue.component('button-counter', {
      data: function () {
      return {
      count: 0
      }
      },
      template: '',
      })
    • 驼峰式

      1
      2
      3
      4
      5
      6
      7
      8
      Vue.component('HelloWrold', {
      data: function () {
      return {
      count: 0
      }
      },
      template: '',
      })

      驼峰式命名只能在模板中使用,不能在HTML中使用,如果使用需转换成横线式。

      1
      <hello-wrold></hello-wrold>

局部组件注册方式

1
2
3
4
5
6
7
var ComponentA = {}
var vm = new Vue({
el: '#app',
components: {
'component-a': ComponentA
}
});

image-20201101205711037

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<body>
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
var ComponentA = {
data: function () {
return {
msg: 'HelloWorld'
}
},
template: '<div>{{msg}}</div>'
}
var ComponentB = {
data: function () {
return {
msg: 'HelloTOM'
}
},
template: '<div>{{msg}}</div>'
}
var ComponentC = {
data: function () {
return {
msg: 'HelloVue'
}
},
template: '<div>{{msg}}</div>'
}
var vm = new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB,
'component-c': ComponentC
}

});
</script>
</body>

Vue调试工具

可以直接使用谷歌商城进行安装:https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd/related?hl=zh

也可以参考官方自行构建后安装:https://github.com/vuejs/vue-devtools

如果遇到Vue.js not detected问题可能导致的原因:

  1. 没有开启权限

    image-20201102082049749

    image-20201102082107655

组件间的交互

父组件向子组件传值

在组建中定义props属性,值为一个数组,用于接受来自父组件的值

1
2
3
4
5
6
7
8
9
Vue.component('menu-item', {
props: ['title'],
data: function () {
return {
msg: '子组件'
}
},
template: '<div>{{msg}}---{{title}}</div>'
})

在使用时可以有两种写法,

  1. 静态传入

    1
    <menu-item title='来自父组件的值'></menu-item>
  2. 通过属性绑定

    1
    <menu-item :title='ptitle'></menu-item>
    1
    2
    3
    4
    5
    6
    7
    var vm = new Vue({
    el: '#app',
    data: {
    pmsg: "父组件",
    ptitle: '来自父组件的值'
    }
    });

image-20201102083255090

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<div id="app">
<menu-item title='来自父组件的值'></menu-item>
<menu-item :title='ptitle'></menu-item>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
Vue.component('menu-item', {
props: ['title'],
data: function () {
return {
msg: '子组件'
}
},
template: '<div>{{msg}}---{{title}}</div>'
})
var vm = new Vue({
el: '#app',
data: {
pmsg: "父组件",
ptitle: '来自父组件的值'
}
});
</script>
</body>

props属性名

规则

  1. props使用驼峰式传值,那么模板中需要使用短横线的形式

    1
    2
    3
    4
    Vue.component('menu-item', {
    props: ['TitleMsg'],
    template: '<div>{{TitleMsg}}</div>'
    })
    1
    <menu-item title-msg='nihao'></menu-item>

    在HTML中大小写是不敏感的。

  2. 字符串模板中没有这个限制

    1
    2
    3
    4
    Vue.component('menu-item', {
    props: ['TitleMsg'],
    template: '<div><menu-item TitleMsg="nihao"></menu-item></div>'
    })

属性值

以下类型均可以传递

  1. 字符串

  2. 数值类型

    如果通过v-bind绑定,那么数值为数字类型

  3. 布尔类型

    如果通过v-bind绑定,那么数值为布尔类型

  4. 数组类型

  5. 对象

子组件向父组件传值

  1. 子组件通过自定义事件向父组件传递信息

    1
    <menu-item :parr='parr' @enlarge-text='handle'></menu-item>

    自定义事件名为enlarge-text,触发的函数名为handle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Vue.component('menu-item', {
    props: ['parr'],
    template: `
    <div>
    <ul>
    <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
    </ul>
    <button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button>
    </div>
    `
    });

    对按钮进行单击事件绑定,绑定的事件为enlarge-text$emit为固定用法。

  2. 父组件通过监听子组件的事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var vm = new Vue({
    el: '#app',
    data: {
    pmsg: '父组件中内容',
    parr: ['apple', 'orange', 'banana'],
    fontSize: 10
    },
    methods: {
    handle: function () {
    // 扩大字体大小
    this.fontSize += 5;
    }
    }
    });

子组件向父组件传值-携带参数

定义组件时可以通过$emit的第二个参数进行传值。组件调用时通过$event来接受。

使用组件

1
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>

定义组件

1
2
3
4
5
6
7
8
9
10
11
Vue.component('menu-item', {
props: ['parr'],
template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button>
</div>
`
});

父组件接受值

1
2
3
4
5
6
methods: {
handle: function (val) {
// 扩大字体大小
this.fontSize += val;
}
}

非父子组件间传值

  1. 单独的事件中心管理组件间的通信

    1
    var hub = new Vue()
  2. 监听事件与销毁事件

    1
    2
    3
    hub.$on('tom-event', (val) => {
    this.num += val
    })
    1
    hub.$off('tom-event')
  3. 触发事件

    1
    hub.$emit('jerry-event', 1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<body>
<div id="app">
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
// 提供事件中心
var hub = new Vue()
Vue.component('test-tom', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>TOM:{{num}}</div>
<div><button @click='handle'>点击</button></div>
</div>
`,
methods: {
handle: function () {
// 触发对方的事件
hub.$emit('jerry-event', 1)
}
},
mounted: function () {
hub.$on('tom-event', (val) => {
this.num += val
})
},
});
Vue.component('test-jerry', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>jerry:{{num}}</div>
<div><button @click='handle'>点击</button></div>
</div>
`,
methods: {
handle: function () {
hub.$emit('tom-event', 2)
}
},
mounted: function () {
hub.$on('jerry-event', (val) => {
this.num += val
})
},
});
var vm = new Vue({
el: '#app',
});
</script>
</body>

组件插槽

父组件向子组件传递内容(模板内容)

image-20201102091617610

  1. 定义插槽

    1
    2
    3
    4
    5
    6
    7
    8
    Vue.component('alert-box', {
    template: `
    <div>
    <strong>Error:<strong>
    <slot>默认内容</slot>
    </div>
    `
    })

    定义插槽使用slot标签,如果没有传值,那么显示定义时的默认内容。

  2. 使用插槽

    1
    2
    <alert-box>有bug发生</alert-box>
    <alert-box></alert-box>

    image-20201102092132259

    当传递值时显示值的内容,否则显示默认内容(如果有)

具名插槽

即对slot标签添加name属性,表示当前插槽的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Vue.component('base-layout', {
template: `
<div>
<header>
<slot name='header'></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name='footer'></slot>
</footer>
</div>
`
})

调用时,通过slot属性指定填充插槽名。没有指定的则添加到默认插槽

1
2
3
4
5
<base-layout>
<p slot="header">标题</p>
<p>主要内容</p>
<p slot="footer">尾部</p>
</base-layout>

image-20201102092621027

关于调用也可以使用如下方法

1
2
3
4
5
6
7
8
9
10
11
<base-layout>
<template slot="header">
<p slot="header">标题</p>
</template>
<template>
<p>主要内容</p>
</template>
<template slot="footer">
<p slot="footer">尾部</p>
</template>
</base-layout>

作用域插槽

父组件对子组件的内容进行加工处理。

  1. 插槽定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Vue.component('fruit-list', {
    props: ['list'],
    template: `
    <div>
    <li :key='item.id' v-for='item in list'>
    <slot :info='item'>{{item.name}}</slot>
    </li>
    </div>
    `
    });
    var vm = new Vue({
    el: '#app',
    data: {
    list: [{
    id: 1,
    name: 'apple'
    }, {
    id: 2,
    name: 'orange'
    }, {
    id: 3,
    name: 'banana'
    }]
    }
    });
  2. 插槽内容

    1
    2
    3
    4
    5
    6
    <fruit-list :list='list'>
    <template v-slot='slotProps'>
    <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
    <span v-else>{{slotProps.info.name}}</span>
    </template>
    </fruit-list>

值的传递:

  1. 插槽中定义了属性infoitem(每次遍历的结果)

  2. 使用v-slot指令接受,其值可以为任意喜欢的名字。

    1
    <template v-slot='slotProps'></template>
  3. slotProps实际上就是info的值。

    1
    2
    3
    4
    5
    6
    7
    8
    <fruit-list :list='list'>
    <template v-slot='slotProps'>

    <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
    <span v-else>{{slotProps.info.name}}</span>
    <span>{{slotProps}}</span>
    </template>
    </fruit-list>

    image-20201102100458309

购物车案例

组件化重构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<body>
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">

var CartTitle = {
template: `
<div class="title">我的商品</div>
`
}
var CartList = {
template: `
<div>
<div class="item">
<img src="img/a.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/b.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/c.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/d.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/e.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
</div>
`
}
var CartTotal = {
template: `
<div class="total">
<span>总价:123</span>
<button>结算</button>
</div>
`
}
Vue.component('my-cart', {
template: `
<div class='cart'>
<cart-title></cart-title>
<cart-list></cart-list>
<cart-total></cart-total>
</div>
`,
components: {
'cart-title': CartTitle,
'cart-list': CartList,
'cart-total': CartTotal
}
});
var vm = new Vue({
el: '#app',
data: {

}
});

</script>
</body>

标题和总价

标题的实现很简单,为了演示功能,定义一个属性记录用户名,并传递给header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.component('my-cart', {
data: function () {
return {
uname: '张三',
}
},
template: `
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list></cart-list>
<cart-total></cart-total>
</div>
`,
}

将值显示出来

1
2
3
4
5
6
var CartTitle = {
props: ['uname'],
template: `
<div class="title">{{uname}}的商品</div>
`
}

对于总价,提供一个数据用于记录购物车的列表。并将值传递给total

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Vue.component('my-cart', {
data: function () {
return {
uname: '张三',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
}, {
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
}, {
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
}, {
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
}, {
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
template: `
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list></cart-list>
<cart-total :list='list></cart-total>
</div>
`,
}

在CartTotal组件中根据列表中的内容计算总价。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var CartTotal = {
props: ['list'],
template: `
<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>
`,
computed: {
total: function () {
// 计算总价
var sum = 0
console.log(this.list);
this.list.forEach(item => {
sum += item.price * item.num
})
return sum
}
}
}

列表展示和删除

列表展示只需要在父组件中将数组列表传递给子组件,子组件在进行循环遍历即可。

1
2
3
4
5
6
7
8
9
10
Vue.component('my-cart', {
template: `
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list :list='list'></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
}
)

子组件中循环遍历数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var CartList = {
props: ['list'],
template: `
<div>
<div :key='item.id' v-for='item in list' class="item">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del"'>×</div>
</div>
</div>
`
}

对于删除操作来说,由于数据来源于父组件,所以不建议直接删除,而是通过自定义事件通知父级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var CartList = {
props: ['list'],
template: `
<div>
<div :key='item.id' v-for='item in list' class="item">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del" @click='del(item.id)'>×</div>
</div>
</div>
`,
methods: {
del: function (id) {
// 通知父组件进行删除
this.$emit('cart-del', id)
}
},
}

父级中定义删除的实际操作,并监听事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.component('my-cart', {
methods: {
delCart: function (id) {
// 根据ID删除list中的数据
// 1. 找到索引
var index = this.list.findIndex(item => {
return item.id == id
})
// 2. 根据索引删除
this.list.splice(index, 1)
}
},
template: `
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list :list='list' @cart-del='delCart($event)'></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
});

商品数量的变更

商品数量变更同样涉及到修改列表内容,同样的交给父组件去修改。

在子组件中定义自定义事件并对input标签绑定失去焦点时触发这个自定义事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var CartList = {
props: ['list'],
template: `
<div>
<div :key='item.id' v-for='item in list' class="item">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" :value='item.num' @blur='changeNum(item.id,$event)'/>
<a href="">+</a>
</div>
<div class="del" @click='del(item.id)'>×</div>
</div>
</div>
`,
methods: {
del: function (id) {
// 通知父组件进行删除
this.$emit('cart-del', id)
},
changeNum: function (id, event) {
// 修改商品数量
// 通知父组件进行删除
let value = event.target.value
this.$emit('change-num', { id: id, num: value })
}
},
}

自定义事件中,将商品id和修改的值传递给父组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Vue.component('my-cart', {
template: `
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
methods: {
changeNum: function (val) {
console.log(val);
// 根据子组件传递的数据更新list中对应的数据
this.list.some(item => {
if (item.id == val.id) {
item.num = val.num
// 终止
return true
}
})
}
},
});

父组件监听子组件定义的自定义方法,并触发父组件定义的自定义方法。

按钮操作

按钮操作同样是需要利用父组件来修改数据。为了复用changeNum事件,将其在添加一个值type用于记录当前的操作。子组件修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
changeNum: function (id, event) {
// 修改商品数量
// 通知父组件进行删除
let value = event.target.value
this.$emit('change-num', {
id: id,
num: value,
type: 'change'
})
},
sub: function (id) {
this.$emit('change-num', {
id: id,
type: 'sub'
})
},
add: function (id) {
this.$emit('change-num', {
id: id,
type: 'add'
})
},

父组件的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
changeNum: function (val) {
if (val.type == 'change') {
// 根据子组件传递的数据更新list中对应的数据
this.list.some(item => {
if (item.id == val.id) {
item.num = val.num
// 终止
return true
}
})
} else if (val.type == 'sub') {
// 根据子组件传递的数据更新list中对应的数据
this.list.some(item => {
if (item.id == val.id) {
item.num -= 1
// 终止
return true
}
})
} else {
// 根据子组件传递的数据更新list中对应的数据
this.list.some(item => {
if (item.id == val.id) {
item.num += 1
// 终止
return true
}
})
}