Vue 组件化-父组件访问子组件?

🎉 父组件访问子组件 children、refs?

有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。

父组件访问子组件:使用refs

子组件访问父组件:使用$parent

使用$children:

通过$children 访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。

但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。

有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs

使用$refs:

$refs 和 ref 指令通常是一起使用的。

首先,我们通过 ref 给某一个子组件绑定一个特定的 ID。

其次,通过 this.$refs.ID 就可以访问到该组件了。

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<button @click="btnClick">按钮获取子组件fuc</button>
<cpn ref="aaa"></cpn>
</div>
<script src="../js/vue.js"></script>
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script>
const app = new Vue({
el:'#app',
data:{
message:'你好!'
},
methods: {
btnClick(){
console.log(this.$children);
this.$children[0].showMessage();
console.log(this.$children[0].name);
//根据顺序取值
console.log(this.$refs.aaa);
//根据key取值
}
},
components:{
cpn :{
template: "#cpn",
data(){
return {
name:'233'
}
},
methods:{
showMessage(){
console.log('showmessage')
}
},
}
}
})
</script>
</body>
</html>

🎉 子组件访问父组件 parent、root?

如果我们想在子组件中直接访问父组件,可以通过$parent

注意事项:

尽管在 Vue 开发中,我们允许通过$parent 来访问父组件,但是在真实开发中尽量不要这样做。

子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。

如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。

另外,更不好做的是通过$parent 直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。

1
2
this.$parent
this。$root

Vue 组件化-组件插槽?

🎉 组件插槽 slot?

组件的插槽也是为了让我们封装的组件更加具有扩展性。

让使用者可以决定组件内部的一些内容到底展示什么。

一样的写死,不一样的预留插槽。

1
2
3
<cpn><button>我是插槽中的自定义的代码</button></cpn>
<slot></slot>
<slot><button>我是插槽中的默认代码</button></slot>

🎉 什么是具名插槽?

1
2
3
4
5
6
7
8
9
10
<cpn><button slot="left">我是插槽中的自定义的代码</button>
<!--这时会替换模板中left插槽的内容-->
</cpn>
<template>
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>

🎉 什么是编译的作用域?

官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

而我们在使用的时候,整个组件的使用过程是相当于在父组件中出现的。

那么他的作用域就是父组件,使用的属性也是属于父组件的属性。

因此,isShow 使用的是 Vue 实例中的属性,而不是子组件的属性。

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
<div id="app">
<cpn v-show="isShow"></cpn>
<!--这里使用的是父组件的isShow-->
</div>
<script src="../js/vue.js"></script>
<template id="cpn">
<div v-show="isShow">
<!--这里使用的是子组件的isShow-->
<h2>我是子组件</h2>
<p>我是内容哈哈哈</p>
</div>
</template>
<script>
const cpn = {
template:"#cpn",
data(){
return {
isShow:false
}
}
}
const app = new Vue({
el:'#app',
data:{
message:'你好!',
isShow:true
},
components:{
cpn
}
})
</script>

🎉 什么是作用域插槽?

父组件替换插槽的标签,但是内容由子组件来提供。

我们先提一个需求:

子组件中包括一组数据,比如:pLanguages: [‘JavaScript’, ‘Python’, ‘Swift’, ‘Go’, ‘C++’]

需要在多个界面进行展示:

某些界面是以水平方向一一展示的,

某些界面是以列表形式展示的,

某些界面直接展示一个数组

内容在子组件,希望父组件告诉我们如何展示,怎么办呢?

利用 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
37
38
39
40
41
42
<div id="app">
<cpn>
<template slot-scope="slot">
<span v-for="item in slot.data">{{item}}-</span>
</template>
</cpn>
</div>
<script src="../js/vue.js"></script>
<template id="cpn">
<div>
<ul>
<slot :data="pLanguages">
<li v-for="item in pLanguages">{{item}}</li>
</slot>
</ul>
</div>
</template>
<script>
const cpn = {
template: "#cpn",
data() {
return {
pLanguages: [
"JavaScript",
"Python",
"Swift",
"Go",
"C++",
]
}
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好!',
},
components: {
cpn
}
})
</script>

JS 基础-模块化开发?

🎉JavaScript 原始功能?

在网页开发的早期,js 制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的。那个时候直接将代码写在 script 标签中即可。

随着 ajax 异步请求的出现,慢慢形成了前后端的分离,客户端需要完成的事情越来越多,代码量也是与日俱增。

为了应对代码量的剧增,我们通常会将代码组织在多个 js 文件中,进行维护。但是这种维护方式,依然不能避免一些灾难性的问题。比如全局变量同名问题。

另外,这种代码的编写方式对 js 文件的依赖顺序几乎是强制性的。但是当 js 文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。

🎉 匿名函数的解决方案?

我们可以使用匿名函数来解决方面的重名问题

1
2
3
4
(function () {
var flag = true;
})();
//匿名函数闭包

🎉 模块化出口?

我们可以使用将需要暴露到外面的变量,使用一个模块作为出口,什么意思呢?

来看下对应的代码:

出口:

1
2
3
4
var module = (function () {
var flag = true;
})();
//匿名函数闭包

使用:

1
2
3
if (module.flag) {
console.log("成功使用了flag!");
}

这里做了什么事情呢?

非常简单,在匿名函数内部,定义一个对象。

给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。

最后将这个对象返回,并且在外面使用了一个 moudle 接受。

接下来,我们在 man.js 中怎么使用呢?

我们只需要使用属于自己模块的属性和方法即可

这就是模块最基础的封装,事实上模块的封装还有很多高级的话题:

但是我们这里就是要认识一下为什么需要模块,以及模块的原始雏形。

幸运的是,前端模块化开发已经有了很多既有的规范,以及对应的实现方案。

常见的模块化规范:

CommonJS、AMD、CMD,也有 ES6 的 Modules

🎉CommonJS 的模块化?

1
2
3
4
5
6
module.exports = {
自定义属性:值
}
//输出
var {自定义属性1,自定义属性2,自定义属性3} = require("/js/xxx.js")
//输入

🎉ES6 自带模块化?

导出:

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
let name ="xiaoming";
let age = "18";
let flag = true;
function sum(a,b){
return a + b
}
if(flag){
console.log(sum(2,3))
}
//1.导出方法1,直接导出
export {
flag,
sum,
}
//2.导出方法2,边定义边导出
export let number = 6;
//3.导出函数和类
export class Person{
run(){
console.log("在奔跑")
}
}
export function mul(a,b){
return a + b
}
//4.导出default,但default只能有一个
const address = "北京市"
export defult address

我们使用 export 指令导出了模块对外提供的接口,下面我们就可以通过 import 命令来加载对应的这个模块了

首先,我们需要在 HTML 代码中引入两个 js 文件,并且类型需要设置为 module。

如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦:

通过*可以导入模块中所有的 export 变量

但是通常情况下我们需要给*起一个别名,方便后续的使用

导入:

1
2
import {flag,sum} from "./aaa.js";
import *as from "./aaa.js";