简谈父子组件、插槽与编译作用域

继续学习vue。近期是新冠肺炎高发期,继续在家待着吧。

父子组件的通信

vue官方不推荐v-model双向绑定到组件传递信息的props里面,默认props是父传子的单向传递,一般不可修改,不能绑定到props,需要修改的话应绑定到data。

watch属性可以用于监听某个属性值的改变,假设监听某个名字为x的属性值,那么监听的函数名字也要叫x,带一个参数代表改变后的值,然后可以增加一些操作。(这个写法实测有点bug,等老师讲到这里我再详细记录吧。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue测试</title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<cpn :cnum1="num1"
:cnum2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props: {{cnum1}}</h2>
<h2>data: {{dnum1}}</h2>
<input type="number" v-model="dnum1" @input="num1Input"/>
<h2>props: {{cnum2}}</h2>
<h2>data: {{dnum2}}</h2>
<input type="number" v-model="dnum2" @input="num2Input"/>
</div>
</template>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
num1: 0,
num2: 0,
},
methods: {
num1change(value) {
this.num1 = parseInt(value);
this.num1 = value;
},
num2change(value) {
this.num2 = parseInt(value);
this.num2 = value;
},
},
components: {
cpn: {
template: '#cpn',
props: {
cnum1: Number,
cnum2: Number,
},
data() {
return {
dnum1: this.num1 == undefined ? 0 : this.num1,
dnum2: this.num2 == undefined ? 0 : this.num2,
}
},
methods: {
num1Input(event) {
this.dnum1 = event.target.value; // 获取监听事件中传回的值
this.$emit('num1change', this.dnum1); // 子传父
this.dnum2 = this.dnum1 * 100;
this.$emit('num2change', this.dnum2);

},
num2Input(event) {
this.dnum2 = event.target.value;
this.$emit('num2change', this.dnum2);
this.dnum1 = this.dnum2 * 100;
this.$emit('num2change', this.dnum1);
},
},
}
}
});
</script>
</body>
</html>

父子组件的访问方式

有时候需要父组件直接访问子组件,子组件直接访问父组件,或者子组件访问根组件,就需要用此类方法。父组件访问子组件需要用$children或者$refs,子组件访问父组件使用$parent

this.$children是一个数组类型,它包含所有子组件对象。在访问多个子组件中的一个的时候需要使用到下标(数组嘛,有些时候这不太方便。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue测试</title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<button type="button" @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: '你好',
},
methods: {
btnClick() {
console.log(this.$children);
this.$children[0].showMessage();
}
},
components: {
cpn: {
template: '#cpn',
methods: {
showMessage() {
console.log('showCTRL');
}
}
}
}
});
</script>
</body>
</html>

通过$refs拿东西的话就方便多了,它是一个对象类型,默认为空,只需要给想拿东西的某个组件加上一个ref=“名字”,然后像下面代码这样使用,就可以了。有点像getElementById()的感觉,用起来很方便。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue测试</title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="test"></cpn>
<button type="button" @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: '你好',
},
methods: {
btnClick() {
console.log(this.$refs.test.name);
}
},
components: {
cpn: {
template: '#cpn',
methods: {
showMessage() {
console.log('showCTRL');
}
},
data() {
return {
name: '我是子组件的name,看到这条消息说明已成功访问',
};
}
}
}
});
</script>
</body>
</html>

还有一种子组件向上获取父组件的方法$parent,甚至可以$root直接获取根组件,但是不推荐这样使用,因为如果父组件没有获取的这种属性,就会报错,这样会提高组件的耦合度,降低重用性。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue测试</title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<button type="button" @click="btnClick">按钮</button>
</div>
</template>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: '你好',
},
methods: {},
components: {
cpn: {
template: '#cpn',
methods: {
btnClick() {
console.log(this.$parent.message);
}
},
}
}
});
</script>
</body>
</html>

插槽

英文名slot,翻译为插槽(轨迹系列游戏中称为结晶回路插孔),插槽的目的是让原来的设备具备更多的扩展性,比如电脑的USB接口,就能插很多外接设备。组件的插槽是为了让封装的组件更加具有扩展性,让使用者可以决定组件内部的的一些内容到底展示什么。比如移动网站中的导航栏,几乎每个页面都会用到导航栏,导航栏一般来说是封装成一个插件,一旦有了这个组件,就可以在多个页面复用了。但是,不同的页面之间导航栏也是不一样的,使用者可以决定组件到底以怎样的形式展示。

如何封装组件?它们也有很多区别,但是也有很多共性。如果每一个单独去封装一个组件,显然不合适。比如每个页面都返回的话,这部分内容我们就要重复去封装。如果封装成一个的话好像也不太合理。有些左侧是菜单,有些是返回,有些中间是搜索,有些中间是文字,等等。封装的基本原则是:抽取共性,保留不同。最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。一旦预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么样的内容。无论是搜索框,导航栏,还是文字,还是按钮,由使用者自己决定。

使用<slot></slot>标签设定一个插槽,如果想在插槽里放东西,就在组件的标签里面写对应的HTML代码。

如果一种功能(比如插槽里插一个按钮)用的比较多,插得多的话就会导致代码复用率增加,降低代码量可以使用插槽默认值,让它如果什么也不插就默认是一个按钮。做法是在template里面的slot标签里写上默认值的代码,就ok了。如果有多个标签同时放入到组件进行替换时,会将全部进行替换。

当子组件的功能复杂时,插槽很可能是多个。那么在有多个插槽的时候,我怎么知道插入哪一个呢?这时候需要使用具名插槽,给插槽起个名字(name=“xxx”)就可以。替换某个东西的时候,加入属性slot=“相同的名字”,就可以插入相同名字的插槽。

使用插槽功能,可以封装功能性比较强的组件了。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue测试</title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<cpn>
<span slot="left">返回</span>
<span slot="center">主页</span>
<span slot="right">菜单</span>
</cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {},
methods: {},
components: {
cpn: {
template: '#cpn',
}
}
});
</script>
</body>
</html>

编译作用域

组件中使用的变量首先会从其所在的实例中寻找,而不是先寻找组件中。模板中的代码里面的变量作用域就会去组件里寻找了,因为模板的作用域是组件而不是实例。官方给出了一条准则:父组件模板的所有东西都会在父级作用域里面编译,子组件模板的所有东西都会在子级作用域里面编译。

作用域插槽:父组件替换插槽的标签,但是内容由子组件来提供。比如子组件中包括一组数据,需要在多个页面进行展示,某些界面是水平方向的,某些界面是列表形式的,某些界面是直接展示一个数组的,内容在子组件,希望父组件告诉它如何展示,这种时候就需要利用slot作用域插槽。使用<template slot-scope="slotProps">就可以获取到slotProps属性,然后访问它的data就可以获取。(注意这个实例已经被废弃了,现在我写这个代码编译不过,不知道是写的问题还是弃用问题。

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
<!DOCTYPE html>
<!-- 这段代码编译不过 -->
<html>
<head>
<meta charset="utf-8">
<title>Vue测试</title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<template slot-scope="slot">
<span v-for="item in slot.data"></span>
</template>
</cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<slot :data="lang">
<ul>
<li v-for="item in lang">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {},
methods: {},
components: {
cpn: {
template: '#cpn',
},
data() {
return {
lang: ['Java', 'C++', 'C#', 'Python', 'JavaScript', 'Go'],
}
},
}
});
</script>
</body>
</html>
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2018-2020 Shawn Zhou
  • Powered by Hexo Theme Ayer
  • PV: UV:

感谢打赏~

支付宝
微信