Vue.js学习笔记第二部分组件化

因为hexo会试图渲染页面的mastache语法 所以每个mastache语法间加入空格

vue版本:v2.6.12

什么是组件化

  • 人面对复杂问题的处理:拆分大问题为小问题,再将其放到整体

  • 组件化类似:将页面拆成一个个小功能块。

Vue组件化思想

  • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
  • 任何应用都会被抽象成一个组件树

组件化思想应用:

  • 充分利用组件
  • 尽可能将页面拆分成小的可复用组件
  • 方便组织和管理代码,扩展性强

注册组件基本步骤

  1. 创建组件构造器
    • 调动Vue.extend()方法
  2. 注册组件
    • 调用Vue.component()方法
  3. 使用组件
    • 在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
<body>
<div id="app">
<!-- 使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../../js/vue.js"></script>
<script>
//1.创建组件构造器对象
const cpnC=Vue.extend({
template: `
<div>
<h2>标题</h2>
<p>内容</p>
<p>内容2</p>
</div>
`
});
//2.注册组件
Vue.component('my-cpn',cpnC);

const app=new Vue({
el: '#app',
data: {
message: '你好啊'
}
});
</script>
</body>

全局组件和局部组件

全局组件可以在多个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
33
34
35
36
37
38
39
40
41
<body>
<div id="app">
<!-- 3使用组件 -->
<my-cpn></my-cpn>
<cpn></cpn>
</div>

<div id="appb">
<my-cpn></my-cpn>
</div>
<script src="../../js/vue.js"></script>
<script>
//1.创建组件构造器对象
const cpnC=Vue.extend({
template: `
<div>
<h2>标题</h2>
<p>内容</p>
<p>内容2</p>
</div>
`
});
//2.注册组件(全局组件:可在多个vue实例下使用)局部组件在vue对象中注册
Vue.component('my-cpn',cpnC);

const app=new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
//标签名:构造器 局部组件只能在该挂载实例下使用
cpn: cpnC
}
});

const app2=new Vue({
el: '#appb'
});
</script>
</body>

父组件和子组件

基本创建

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
<div id="app">
<cpn2></cpn2>
</div>
<script src="../../js/vue.js"></script>
<script>
//1.创建组件构造器对象 子组件
const cpnC1=Vue.extend({
template: `
<div>
<h2>标题</h2>
<p>内容</p>
</div>
`
});
//父组件
const cpnC2=Vue.extend({
template: `
<div>
<h2>标题2</h2>
<p>内容2</p>
<cpn1><cpn1>
</div>
`,
components: {
cpn1: cpnC1
}
});
//根组件
const app=new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
});
</script>

注册组件语法糖

全局

1
2
3
4
5
6
7
8
Vue.component('cpn1',{
template: `
<div>
<h2>标题</h2>
<p>内容</p>
</div>
`
});

局部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const app=new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
'cpn2': {
template: `
<div>
<h2>标题</h2>
<p>内容</p>
</div>
`
}
}
});

简单组件模板分离写法

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
<div id="app">
<cpn></cpn>
</div>

<!-- 方法一 知道一下以后不用 -->
<script type="text/x-template" id="cpn">
<div>
<h2>标题</h2>
<p>内容</p>
</div>
</script>

<!-- 方法二 -->
<template id="cpn">
<div>
<h2>标题</h2>
<p>内容</p>
</div>
</template>

<script src="../../js/vue.js"></script>
<script>
Vue.component('cpn',{
template: '#cpn'
});

const app=new Vue({
el: '#app',
data: {
message: '你好啊'
}
});
</script>

组件数据存放

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
<div id="app">
<cpn></cpn>
</div>

<template id="cpn">
<div>
<h2>{{title}}</h2>
<p>内容</p>
</div>
</template>

<script src="../../js/vue.js"></script>
<script>
Vue.component('cpn',{
template: '#cpn',
data(){
return {
title: 'abc'
}
}
});

const app=new Vue({
el: '#app',
data: {
message: '你好啊'
}
});
</script>

组件对象也有data属性 也有method等属性

为什么组件的data属性是函数,而vue实例data属性是对象

调用组件的时候,每次返回一个新的对象给新组件,避免组件共用数据

父子组件通信-父传子props(properties缩写)

请求数据一般是最外面的组件请求数据传递给里面的小组件。

父组件通过prop向子组件传递数据,子组件通过事件向父组件通信

props基本用法(父向子

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
<div id="app">
<!-- 第三步:v-bind绑定 -->
<cpn v-bind:sonmovies="movies" :sonmessage="message"></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>

<template id="cpn">
<div>
<h2>子组件</h2>
<p>{{sonmovies}}</p>
<p>{{sonmessage}}</p>
</div>
</template>

<script src="../../js/vue.js"></script>
<script>
//子组件
const cpn={
template: '#cpn',
//第二步:子组件props属性,改名
props: ['sonmovies','sonmessage']
}
//根组件
const app=new Vue({
el: '#app',
data: {
//第一步:父组件数据
movies: ['电影1','电影2','电影3'],
message: '你好啊'
},
//局部组件语法糖
components: {
// 'cpn': cpn
//对象字面量属性增强写法
cpn
}
});
</script>

props属性除数组之外也可以使用对象,当需要对props进行类型等验证时,就需要对象写法

验证支持哪些数据类型:

  • String
  • Number
  • Boolean
  • Array
  • Objecy
  • Date
  • Function
  • Symbol

当我们有自定义构造函数时,验证也支持自定义类型

1
2
3
4
props:{
cmovies: Array,
cmessage: String
}

默认值

1
2
3
4
sonmessage:{
type: String,
default: '默认值'
}

对象和数组默认值必须由一个工厂函数

1
2
3
4
5
6
sonmovies: {
type: Array,
default: function(){
return []
}
}

必须传值,不传报错

1
2
3
sonmessage:{
required: true
}

自定义验证

1
2
3
4
5
6
7
sonmovies: {
type: Array,
validator: function(value){
//这个值必须匹配下列字符串中的一个
return ['success','warning','danger'].indexOf(value) !==-1
}
}

自定义类型

1
2
3
4
5
6
7
8
9
function Person(firstName,lastName){
this.firstName=firstName;
this.lastName=lastName;
}
Vue.component('blog-post',{
props:{
author: Person
}
})

vue在dom中不支持驼峰命名:驼峰的地方用-分割 例如:cInfo在v-bind中写成c-info

子组件向父组件传递数据(自定义事件)

按钮点击时向父组件传递一些信息

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
<div id="app">
<!-- 父组件中通过v-on监听组件间事件 -->
<cpn @itemclick="cpnClick"></cpn>
</div>

<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>

<script src="../../../js/vue.js"></script>
<script>
//子组件
const cpn={
template: '#cpn',
data(){
return {
categories: [
{id: 'aaa',name: '热门推荐'},
{id: 'bbb',name: '手机数码'},
{id: 'ccc',name: '家用家电'},
{id: 'ddd',name: '电脑办公'}
]
}
},
methods: {
btnClick(item){
//子组件通过$emit自定事件传值 发送
this.$emit('itemclick',item)
}
}

}

//父组件
const app=new Vue({
el: '#app',
data: {

},
components: {
cpn
},
methods: {
cpnClick(){
console.log('cpnClick',item);
}
}
});
</script>

以上仅学习使用,真实开发实践不会这样写(真实开发实践到使用脚手架部分)

子组件双向绑定父组件的值

子组件通过绑定子组件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
<div id="app">
<cpn :number1="num1" :number2="num2">
</div>

<template id="cpn">
<div>
<!-- 错误 直接绑定访问 -->
<h2>{{number1}}</h2>
<input type="text" v-model="number1">
<h2>{{number2}}</h2>
<input type="text" v-model="number2">
<hr>
<!-- 正确 通过绑定子组件data()函数返回的值 -->
<h2>{{dnumber1}}</h2>
<input type="text" v-model="dnumber1">
<h2>{{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>

<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const app=new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
components: {
cpn: {
template:'#cpn',
props: {
number1: Number,
number2: Number
},
data(){
return {
dnumber1: this.number1,
dnumber2: this.number2
}
}
}
}
});
</script>

父访问子-children-refs

$children

父组件访问子组件数据并操作

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
46
47
48
49
50
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<button @click="btnClick">按钮</button>
</div>

<template id="cpn">
<div>
我是子组件
</div>
</template>
<script src="../../js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const app=new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
methods: {
showMessage(){
console.log('showMessage');
}
},
data() {
return {
name: '子组件name'
}
}
}
},
methods: {
btnClick(){
console.log(this.$children);
//直接调用到子组件的方法
this.$children[0].showMessage();
//直接获取子组件数据
console.log(this.$children[0].name);
//循环遍历每个子组件的数据
for(let c of this.$children){
console.log(c.name);
c.showMessage()
}
}
}
});
</script>

实际开发不会通过$children获取数据,而是使用$refs属性

$refs

  • 实际开发中使用的多
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></cpn>
<cpn></cpn>
<!-- 给标签ref属性命名为aaa aaa会作为对象键(key) -->
<cpn ref='aaa'></cpn>
<button @click="btnClick">按钮</button>
</div>

<template id="cpn">
<div>
我是子组件
</div>
</template>
<script src="../../js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const app=new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
methods: {
showMessage(){
console.log('showMessage');
}
},
data() {
return {
name: '子组件name'
}
}
}
},
methods: {
btnClick(){
console.log(this.$refs.aaa);//获取键为aaa对应子组件的对象
}
}
});
</script>

子访问父-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
39
40
41
42
43
44
45
46
47
48
49
50
51
<div id="app">
<cpn></cpn>
</div>

<template id="cpn">
<div>
<ccpn></ccpn>
</div>
</template>

<template id="ccpn">
<div>
我是子组件
<button @click='btnClick'>按钮</button>
</div>
</template>

<script src="../../js/vue.js" type="text/javascript" charset="utf-8"></script>
<script>
const app=new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data(){
return{
name: '我是cpn组件的name'
}
},
components: {
ccpn: {
template: '#ccpn',
methods: {
//访问父组件
btnClick() {
console.log(this.$parent);
console.log(this.$parent.name);

//2.访问root
console.log(this.$root);
}
}
}
}
}
}
});
</script>

插槽slot

为什么使用slot

  • slot翻译为插槽:
    • 生活中的插槽:usb扩展插槽,插板店员插槽
    • 插槽的目的是让我们原来的设备具有更多的扩展性
    • 比如电脑的usb我们可以插入u盘、硬盘、手机、等设备
  • 组件的插槽:
    • 组件的插槽也是为了让我们封装的组件更加具有扩展性
    • 让使用者可以决定组件内部的一些内容到底展示什么
  • 例子:移动网站中的导航栏
    • 移动开发中,几乎每个页面都有导航栏
    • 导航栏我们必然会封装成一个插件,比如nav-bar组件
    • 一旦有了这个组件,我们就可以在多个页面复用了。
  • 但是,每个页面的导航是不一样的,需要预留空间显示其他不同组件
  • 这个时候就可以用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
<div id="app">
<cpn><button type="button">按钮</button></cpn>
<cpn><span>哈哈哈</span></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>组件</h2>
<p>内容</p>
<slot><p>默认值</p></slot>
</div>
</template>

<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const app=new Vue({
el: '#app',
date: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
});
</script>

具名插槽slot

多个插槽情况下,通过给子组件模板中的slot插槽写上name属性,做到分别控制

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
<div id="app">
<cpn><span slot="left">标题</span></cpn>
<cpn>aaa</cpn>
</div>

<template id="cpn">
<div>
<slot name="left"><span></span></slot>
<slot name="center"><span></span></slot>
<slot name="right"><span></span></slot>
<slot></slot>
</div>
</template>

<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const app=new Vue({
el: '#app',
date: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
});
</script>

编译作用域

根组件中的模板用到变量会去根组件找,子组件template模板使用到变量会去子组件找

作用域插槽

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

需求:

  • 子组件中包括一组数据,比如:pLanguages:[‘JavaScript’,’Python’,’Swift’,’Go’,’C++’]
  • 需要再多个界面进行展示:
    • 某些界面是以水平方向展示
    • 某些是列表形式
    • 某些直接展现数组
  • 内容在子组件,希望父组件告诉我们如何展示
    • 利用slot作用域插槽

模块化开发

JavaScript原始功能

  • 网页开发早期,js制作作为一种脚本语言,做一些简单表单验证或动画

  • 随着ajax异步请求出现,慢慢形成前后端分离

    • 客户端需求越来越多,代码量与日俱增
    • 为了应对代码量剧增,通常将代码组织在多个js文件
    • 但这种维护方式,依然不能避免一些灾难性问题。
  • 比如全局变量同名的问题

    • js最初设定时只有两种作用域,导致变量很容易出现冲突,必须用闭包来进行函数封装,解决冲突问题。
    • 引出了一个解决办法:闭包。匿名闭包能解决原因是创建了一个作用域,作用域里调用到不到作用域外的变量
    1
    2
    3
    ;(function(){
    //代码
    })()
    • 闭包虽然能解决变量命名冲突问题,但又会带来另一个问题:代码复用性无了

    • 那如果我们需要防止全局变量冲突又不想牺牲复用性的话怎么办呢?这就要用到模块化了

  • 模块化es5,es6有专门的模块化改进,使用一个对象作为模块出口

    1
    2
    3
    4
    5
    6
    7
    8
    var moduleA = (function(){
    //导出的对象
    var obj = {}
    //代码
    obj.flag=flag;//给对象添加属性
    obj.sum=sum;
    return obj;
    })()

    通过模块对象引用模块的变量,解决变量冲突和复用问题

  • 前端发展至今,已有很多人规定了前端模块化开发的规范及实现方案,我们只需要遵守规范,现在一般在别人规范好的模块化规范上进行开发

  • 常见模块化规范:

    • CommonJS(nodejs按此规范实现)、AMD、CMD、也有ES6的Modules

稍微了解CommoJS

  • 模块化两个核心:导出和导入

  • CommonJS的导出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    flag: true,
    test(a, b) {
    return a + b
    },
    demo(a, b) {
    return a * b
    }
    }
  • CommonJS的导入

    1
    2
    3
    4
    5
    6
    7
    8
    //CommonJS模块
    let{test,demo,flag} = require('moduleA');

    //等同于
    let _mA = require('mouduleA');
    let test = _mA.test;
    let demo = _mA.demo;
    let flag = _mA.flag;

ES6模块化实现

导出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 导出方式1
export {
flag,sum
}

// 导出方式2
export var num1=1000;
export var height = 1.88;

// 导出函数类
export function mul(num1,num2){
return num1+num2
}
export class Class{
run(){
console.log('run');
}
}

//export default
//某些情况下,一个模块中包含某个功能,我们并不希望给这个功能命名,而是让导入者自己命名
const address = '北京';
export default address;

导入:

1
2
3
4
5
6
7
8
9
10
11
import {flag} from "./aaa.js";
inport {Class} from "./aaa.js";
const p = new Class();

//export default 导入
import addr from "./aaa.js";

//统一全部导入
import * as aaa from "./aaa.js";
//拿变量
console.log(aaa.flag);

评论