forked from qianguyihao/Web
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Vue组件.md
618 lines (423 loc) · 15.8 KB
/
Vue组件.md
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
## 子组件的定义和注册
我们在本文的第一段中,通过`Vue.component`形式定义的是**全局组件**。这一段中,我们来讲一下**子组件**。
### 在父组件中定义子组件
比如说,一个`账号`模块是父组件,里面分为`登陆`模块和`注册`模块,这两个晓得模块就可以定义为子组件。
需要注意的是作用域的问题:我们在父组件中定义的子组件,只能在当前父组件的模板中使用;在其他的组件,甚至根组件中,都无法使用。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<account>
</account>
</div>
<script>
//定义、注册组件
Vue.component('account', {
template: '<div><h2>账号模块</h2> <login></login></div>',
components: {
'login': {
template: '<h3>登录模块</h3>'
}
}
});
new Vue({
el: '#app'
});
</script>
</body>
</html>
```
我们发现,既然是定义父亲`<account>`的子组件,那么,子组件`<login>`的调用,只能写在父组件`<account>`的`template`模板中。
显示效果:
![](http://img.smyhvae.com/20180423_1029.png)
### 在 Vue 根实例中定义子组件
当然,我们还可以这样做:把整个 Vue 对象当成父亲,这样的话,就可以在 Vue 示例中定义一个子组件。如下:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<login> </login>
</div>
<script>
new Vue({
el: '#app',
//在Vue实例中定义子组件
components: { // components 是关键字,不能改
'login': {
template: '<h3>登录模块</h3>'
}
}
});
</script>
</body>
</html>
```
这样写的话,我们定义的子组件`<login>`在整个`#app`区域,都是可以使用的。
上面的代码,还有**另外一种写法**:(把子组件的模板定义,存放到变量中)【重要】
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<login> </login>
</div>
<script>
//通过变量接收定义的子组件
var myLogin = {
template: '<h3>登录模块</h3>' // template 是关键字,不能改
}
new Vue({
el: '#app',
//在Vue实例中定义子组件
components: { // components 是关键字,不能改
'login': myLogin
}
});
</script>
</body>
</html>
```
注意,在定义子组件时,关键字`components`不要写错了。
## 组件之间的动态切换(暂略)
我们可以利用`<component> `标签的`:is`参数来进行组件之间的切换。
## 父组件向子组件传递数据
我们要记住:父组件通过**属性**的形式,向子组件传递数据。
**引入**:
我们先来看这样一段代码:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script src="vue2.5.16.js"></script>
<body>
<div id="app">
<!-- 这里是父组件的范围,自定义一个数值 number -->
<counter number="1+2"> </counter>
<counter number="10+20"> </counter>
</div>
<script>
var myCounter = {
//【重要】这里是子组件的范围,无法直接获取父组件中的 number
template: '<div>我是子组件。{{number}}</div>'
}
var vm = new Vue({
el: '#app',
components: {
'counter': myCounter
}
});
</script>
</body>
</html>
```
上方代码中,我想把父组件里 number 的数值传递给子组件,直接这样写,是看不到效果的:
![](http://img.smyhvae.com/20180424_1520.png)
**1、父组件传值给子组件**:
要通过 props 属性将number进行传递给子组件才可以。代码如下:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script src="vue2.5.16.js"></script>
<body>
<div id="app">
<!-- 这里是父组件的范围,自定义一个数值 number -->
<counter :number="1+2"> </counter>
<counter :number="10+20"> </counter>
</div>
<script>
var myCounter = {
//这里是子组件的范围
props: ['number'], //通过 props 属性将父亲的 number 数据传递给子组件
template: '<div>我是子组件。{{number}}</div>'
}
var vm = new Vue({
el: '#app',
components: {
'counter': myCounter
}
});
</script>
</body>
</html>
```
在`<counter>`标签中,要注意`:number`里的冒号。加上冒号,那么引号里的内容就是表达式(期望的结果);否则,引号的内容只是字符串:
![](http://img.smyhvae.com/20180424_1530.png)
**2、子组件获取了父组件的数据后,进行求和操作**:
上方代码中,子组件已经获取了父组件的两个number,现在要求:每点击一次子组件,在子组件中将数据加 1。
一般人可能会这样写:(不推荐的写法:子组件直接修改父组件中的数据)
```javascript
var myCounter = {
//这里是子组件的范围
props: ['number'], //通过 props 属性将父亲的数据传递给子组件
template: '<div @click="addClick">我是子组件。{{number}}</div>',
methods: {
addClick: function () {
this.number ++; //这种写法不推荐。不建议直接操作父组件中的数据
}
}
}
```
上方代码的写法不推荐,因为不建议直接操作父组件中的数据。虽然数据操作成功,但是控制台会报错:
img.png
这样涉及到**单向数据流**的概念:
- 父组件可以传递参数给子组件,但是反过来,子组件不要去修改父组件传递过来的参数。因为同一个参数,可能会传递给多个子组件,避免造成修改的冲突。
既然如此,我可以把父组件中的数据,在子组件中创建**副本**,然后我们去修改这个副本,就不会造成影响了。最终,完整版代码如下:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script src="vue2.5.16.js"></script>
<body>
<div id="app">
<!-- 这里是父组件的范围,自定义一个数值 number -->
<counter :number="1+2"> </counter>
<counter :number="10+20"> </counter>
</div>
<script>
var myCounter = {
//这里是子组件的范围
props: ['number'], //通过 props 属性将父亲的数据传递给子组件
data: function () {
return {
number2: this.number
}
},
template: '<div @click="addClick">我是子组件。{{number2}}</div>',
methods: {
addClick: function () {
this.number2 ++; //操作和修改number的副本
}
}
}
var vm = new Vue({
el: '#app',
components: {
'counter': myCounter
},
});
</script>
</body>
</html>
```
## 子组件向父组件传值
我们要记住:子组件通过**事件触发**的形式,向父组件传值。
**案例1:**子组件给父组件传递数据
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<!-- 在外层监听`mysend`事件,进而出发外层的 getData 方法 -->
<counter v-on:mysend="getData"> </counter>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">发送数据给父组件</div>', //当组件被点击时,触发 addClick 方法
methods: {
addClick: function () {
//第一个参数为键(注意,要小写,不能大写),第二个参数为值
this.$emit('mysend', 'smyhvae'); //通过键`mysend`事件通知外面,将值`smyhvae`传给父组件
}
}
});
new Vue({
el: '#app',
methods: {
getData: function (input) { //通过括号里的参数,获取子组件传递过来的值
console.log(input); //打印结果:smyhvae
}
}
});
</script>
</body>
</html>
```
**案例2**:获取子组件的DOM对象
题目:给两个相同的子组件定义计数器,每点击一次,数值加1。然后在父组件中求和。
步骤(1):给两个相同的子组件定义计数器,每点击一次,数值加1。代码如下:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<counter> </counter>
<counter> </counter>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">当前计数:{{number}}</div>', //当组件被点击时,调用 addClick 方法
data: function () {
return {
number: 0 //给组件定义一个数据:number
}
},
methods: {
addClick: function () {
this.number++; //定义方法:每点击一次,number 的数值加 1
}
}
});
new Vue({
el: '#app'
});
</script>
</body>
</html>
```
步骤(2):两个子组件的数值加 1 后,通知父组件。代码如下:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<!-- 在外层监听`change`事件,进而出发外层的 myClick 方法 -->
<counter @change="myMethod"> </counter>
<counter @change="myMethod"> </counter>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">当前计数:{{number}}</div>', //当组件被点击时,
data: function () {
return {
number: 0 //给组件定义一个数据:number
}
},
methods: {
addClick: function () {
this.number++; //定义方法:每点击一次,number 的数值加 1
this.$emit('change'); //通过这一行的`change`,通知外面,内部的 addClick 方法已经执行了
}
}
});
new Vue({
el: '#app',
methods: {
myMethod: function () {
console.log('触发父组件');
}
}
});
</script>
</body>
</html>
```
上方代码中,通过关键字`emit`通知父组件,子组件里的 addClick 方法被执行了。父组件得知后,执行`myMethod()`方法(这个方法是在Vue实例中定义的,很好理解)
步骤(3):在父组件中求和
既然父组件已经得知子组件的 addClick 事件,那我们直接在父组件的`myMethod()`里定义求和的方法即可。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="vue2.5.16.js"></script>
</head>
<body>
<div id="app">
<!-- 在外层监听`change`事件,进而触发外层的 myMethod 方法 -->
<counter ref="one" @change="myMethod"> </counter>
<counter ref="two" @change="myMethod"> </counter>
<div>{{totalData}}</div>
</div>
<script>
Vue.component('counter', {
template: '<div @click = "addClick">当前计数:{{number}}</div>', //当组件被点击时,触发 addClick方法
data: function () {
return {
number: 0 //给组件定义一个数据:number
}
},
methods: {
addClick: function () {
this.number++; //定义方法:每点击一次,number 的数值加 1
this.$emit('change'); //通过这一行自定义的`change`,通知外面,内部的 addClick 方法已经执行了
}
}
});
new Vue({
el: '#app',
data: {
totalData: 0
},
methods: {
myMethod: function () {
console.log('触发父组件');
//通过`$refs`获取子组件中各自的number数值
var a1 = this.$refs.one.number;
var a2 = this.$refs.two.number;
//求和,存放在父组件的 totalData 中
this.totalData = a1 + a2;
console.log();
}
}
});
</script>
</body>
</html>
```
代码的关键:
- 在`<counter>`标签中,通过 `ref = "xxx"`属性,给各个组件起一个别名,代表组件的引用
- 在父函数`myMethod()`中,通过`this.$refs.xxx`获取组件的引用。我们看一下最后两行代码在控制台的输出便知:(组件里有 number 属性)
![](http://img.smyhvae.com/20180424_1455.png)