Vue学习笔记(二)组件化和模块化
前言
本章记录一下vue的组件化和模块化,这是vue比较重要的知识点。会在实际开发中大量的使用到,因此,会比较详细的记录。
如需转载,请标明出处。
若发现问题,还望指正。
组件化
什么是组件化
组件化是一种思想,将一个复杂的页面分解成多个小的组件,每个组件都是独立的个体,互不影响,这样管理和维护起来就非常的容易。
组件化也是vue.js中重要的思想。
1、它提供了一种抽象,我们开发出一个个的组件来构成我们的应用。
2、每一个应用都可以抽象成一颗组件树。
1、基础使用
分为三个步骤:创建组件构造器 -> 注册组件 -> 使用组件
<div id="app">
<!-- 步骤三:使用组件 -->
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 步骤一:创建组件构造器
const comp = Vue.extend({
template: '<div>这是组件</div>'
});
// 步骤二:注册组件
Vue.component('cpn', comp);
const app = new Vue({
el: '#app'
});
</script>
注意:前两个步骤必须放在创建vue实例之前,否者无效。
组件必须挂载在某个vue实例下,否者无效。
2、全局组件和局部组件
如上的使用方式即为全局组件的使用,它可以在任何的vue实例下使用,然后平时开发时通常都是使用局部组件。
局部组件的使用如下,在需要的vue中添加属性components,并进行注册,此时,只有在该vue实例里是可以使用该组件,其他地方皆不可使用。
<div id="app">
<!-- 步骤三:使用组件 -->
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 步骤一:创建组件构造器
const comp = Vue.extend({
template: '<div>这是组件2</div>'
});
// 步骤二:注册组件
// Vue.component('cpn', comp);
const app = new Vue({
el: '#app',
components: {
//cpn:组件的标签名
//创建的组件名。步骤一的对象
cpn: comp
}
});
3、语法糖和模板抽离
我们实际开发时是不会如上的方式去开发的,过于复杂了,我们会将代码进行简化,因为比较简单,此处直接上代码。
<div id="app">
<cpn></cpn>
</div>
<template id="mytemp">
<div>
我是组件
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#mytemp'
}
}
});
</script>
模板抽离除了这种方式,还可以使用script标签的方式,此处不详细说明。
template标签里必须有且只有一个根标签
4、组件的data为什么是函数
本来是记住的,但是记录的时候就又忘记了,因此这里记录下来。
组件,是会复用的,如果将data设计成属性,那么,他们将指向同一个对象,一旦一个地方改变,复用的地方都会改变,这是有问题的。
如果设计成函数,函数是有作用域,因此每个组件都有自己的作用域,返回的都是自己的对象。不会影响其他组件的值。
5、父子组件
5.1 父子组件
父子组件即父组件和子组件,当一个组件在另一个组件里时,外层的组件即为里层组件的父组件,里层组件为子组件
vue实例为root组件,即根组件,也是它里面组件的父组件。
5.2 父子组件通信
5.2.1 父传子值
父传子值主要使用到了props属性,它是一个数组,里面每一个值都是字符串,如下。props里的值为val,在父组件里使用时,需要用到v-bind。
<div id="app">
<cpn :val='vals'></cpn>
</div>
<template id="mytemp">
<div>
我是组件 {{val}}
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
vals: '这是父传子值'
},
components: {
cpn: {
template: '#mytemp',
props: ['val']
}
}
});
</script>
5.2.2 子传父值
需要注意两点:
1、按钮上的属性是@click,而非@onclick
2、this.$emit(‘bclick’, “hello”)需要加this
<div id="app">
<cpn @bclick="btnClick"></cpn>
</div>
<template id="mytemp">
<div>
我是组件
<button @click="ccc">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
vals: '这是父传子值'
},
methods: {
btnClick(val) {
console.log(val);
}
},
components: {
cpn: {
template: '#mytemp',
methods: {
ccc() {
this.$emit('bclick', "hello")
}
}
}
}
});
</script>
5.2.3 父访问子
父组件访问子组件的内容,如方法、属性等。
children:
children这个很少使用,通常我们获取子组件的内容不使用它,一般在获取所有子组件时才使用
<div id="app">
<button @click="btnClick">父访问子</button>
<cpn></cpn>
</div>
<template id="mytemp">
<div>
我是组件
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
vals: 'parent'
},
methods: {
btnClick(val) {
console.log(this.$children[0].va);
this.$children[0].ccc();
}
},
components: {
cpn: {
template: '#mytemp',
data() {
return {
va: 'son'
}
},
methods: {
ccc() {
console.log("this is child");
}
}
}
}
});
</script>
son
this is child
这里打印出this.$children[0]的值可见子组件的内容。如下
refs:
既然不适用children,那么肯定有其他的代替,没错,就是refs,它的使用可以规避掉children的弊端(children需要下标去获取对应的组件,一旦动态改变的组件的顺序等就无法准确获取到相应的子组件)。
它的使用需要在父组件的子组件标签中添加ref=“”的属性,类似于id的意思,需要唯一。然后再通过this.$refs获取对应的子组件。
<div id="app">
<button @click="btnClick">按钮</button>
<cpn></cpn>
<cpn ref="aa"></cpn>
<cpn ref="bb"></cpn>
</div>
<template id="mytemp">
<div>
我是组件
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
vals: 'parent'
},
methods: {
btnClick() {
this.$refs.aa.c1();
}
},
components: {
cpn: {
template: '#mytemp',
data() {
return {
va: 'son'
}
},
methods: {
c1() {
console.log("子组件1");
},
c2() {
console.log("子组件2");
},
c3() {
console.log("子组件3");
}
}
}
}
});
</script>
5.2.4 子访问父
子组件访问父组件的内容,如方法、属性等。
子访问父可以使用
p
a
r
e
n
t
,它的使用与
parent,它的使用与
parent,它的使用与children类似。这里就不再赘述。并且它使用的很少,因为如果使用了它就意味着于父组件的耦合度高了,不利于开发。
还有另一个属性$root,它可以获取到顶级父元素,也就是vue实例的内容。不过它同样使得的比较少,此处也不再多说。
6、插槽slot
6.1 插槽的基本使用
6.1.1 插槽的使用
以前的代码是不具备扩展的,但是有了插槽就不一样了,可以很好的扩展,也可以很好的自定义。
在子组件需要扩展的地方使用上slot,然后在父组件的子组件标签中写入需要的内容即可。
<div id="app">
<cpn><button>按钮</button></cpn>
<cpn><span>嘿嘿</span></cpn>
<cpn><i>hhha</i></cpn>
</div>
<template id="mytemp">
<div>
我是组件
<slot></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#mytemp'
}
}
});
</script>
6.1.2 插槽的默认值
如果插槽里有很多地方都需要使用相同的内容,此时我们就可使用默认值,那么不使用默认值的地方加入新内容即可,其他需要就可以不写内容了。
<div id="app">
<cpn></cpn>
<cpn><span>嘿嘿</span></cpn>
<cpn><i>hhha</i></cpn>
</div>
<template id="mytemp">
<div>
我是组件
<slot><button>按钮</button></slot>
</div>
</template>
6.1.3 多个值替换插槽
如果需要插入多个标签的内容,也是可以的,他会直接将这些内容整体替换。
<div id="app">
<cpn></cpn>
<cpn>
<span>嘿嘿</span>
<b>呵呵呵呵呵呵</b>
<span>哈哈</span>
</cpn>
<cpn><i>hhha</i></cpn>
</div>
<template id="mytemp">
<div>
我是组件
<slot><button>按钮</button></slot>
</div>
</template>
6.2 具名插槽
说简单点,就是给插槽取个名字,使用的时候指明替换哪一个,例如导航组件部分,html内容部分如下。
<div id="app">
<cpn><button slot="center">替换</button></cpn>
</div>
<template id="mytemp">
<div>
<slot name="left"><button>左</button></slot>
<slot name="center"><button>中</button></slot>
<slot name="right"><button>右</button></slot>
</div>
</template>
6.3 作用域插槽
**slot-scope: **被废弃了,但是也能使用
组件是有作用域的,父子之间也是可以进行通信的。
使用插槽的时候也会涉及到作用域。当父组件需要使用子组件的值时,需要将值抛出,父组件通过slot-scope进行获取。下面的方式可能有些过时,如下。
<div id="app">
<cpn></cpn>
<cpn><!--这种方式最后一项会多一个分隔符出来-->
<div slot-scope="slot2">
<span v-for="item in slot2.data">{{item}} </span>
</div>
</cpn>
<cpn><!--join()就可以规避上面那种的问题-->
<div slot-scope="slot3">
<span>{{slot3.data.join(" - ")}}</span>
</div>
</cpn>
</div>
<template id="mytemp">
<div>
<slot :data="comics">
<ul>
<li v-for="item in comics">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#mytemp',
data() {
return {
comics: ['斗破苍穹', '斗罗大陆', '灵笼', '完美世界', '雾山五行', '吞噬星空']
}
}
}
}
});
</script>
**v-slot:**新版本使用
<div id="app">
<cpn>
<template v-slot:default="slot2"><!--此处必须是template,否者无效-->
<span>{{slot2.data}} </span>
</template>
</cpn>
</div>
<template id="mytemp">
<div>
<slot :data="comics">
<ul>
<li v-for="item in comics">{{item}}</li>
</ul>
</slot>
</div>
</template>
独占默认插槽的缩写语法:
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:
<div id="app">
<cpn v-slot:default="slot2">
<span>{{slot2.data}} </span>
</cpn>
</div>
这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:
<div id="app">
<cpn v-slot="slot2">
<span>{{slot2.data}} </span>
</cpn>
</div>
只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:也就是简写前的方式
模块化
背景:JavaScript设计初期只是实现简单的页面交互逻辑,当时的js代码直接写在script标签里。但是随着各方面的发展,很多后端的业务逻辑交给了前端完成,比如表单验证等,特别是随着ajax的广泛应用,前端的代码量迅速增长,使得不能使用原来的方式,这时,大量的js代码放入了js文件中,通过外部引入进html中,但是也会出现一些问题,比如命名问题,数据共享等,模块化就应运而生。
什么是前端模块化
将一个复杂的程序依据一定的规范封装成多个部分(文件),每个部分的内部数据和实现都是独立的,对外暴露一部分内容供其他部分调用。
es5模块化解决方案
在es5(ECMAScript第五个版本)中,为了解决作用域的问题,很多时候我们都是通过闭包的形式。但是,使用闭包也会有它的问题,比如,一个js文件确实需要用到另一个js里的变量、函数等。不重写几乎是用不到的。因为此时每个js都是一个独立的作用域,无法相互使用。
我们会通过返回对象、再调用的方式去解决。如下。
a.js
var module1 = (function () {
let obj = {}
let name = '张三'
obj.name = name
return obj
})()
c.js
(function () {
console.log(module1.name);
})()
此处需要注意的是module1 不能重复,每一个模块使用唯一的名称
es6模块化解决方案
到了es6(ECMAScript第六个版本),自带了模块化。只需在html引用js时添加属性type=“module”,并在js使用export和import完成。
<script src="./a.js" type="module"></script>
<script src="./c.js" type="module"></script>
a.js
let name = '张三'
export {
name
}
c.js
import { name } from './a.js'
console.log(name);
export不仅可以导出变量,还可以将函数、类等导出,并且还能不在最后导出,声明时就导出。如下
export function sum(num1,num2){
return num1 + num2
}
联系题
1、完成如下功能。
2、加强功能,未完待续