本文标题中的数字表示B站视频内容对应的分P数。

【组件化开发思想】

  • 现实中的组件化思想体现:标准、分治、重用、组合

  • 编程中的组件化思想体现

    bb1bz8.jpg

  • 组件化规范: Web Components(了解一下即可,很多浏览器不支持)

    1. 我们希望尽可能多的重用代码,但自定义组件的方式不太容易(html、css和js),并且多次使用组件可能导致冲突。
    2. Web Components 通过创建封装好功能的定制元素解决上述问题.
    3. Vue部分实现了上述规范

【组件的注册和使用】

1.全局组件注册语法

  • 语法:

    1
    2
    3
    4
    Vue.component(组件名称, {
    data: 组件数据,
    template: 组件模板内容
    })
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 注册一个名为 button-counter 的新组件
    Vue.component('button-counter', {
    data: function () {
    return {
    count: 0
    }
    },
    template: '<button v-on:click="count++">点击了{{ count }}次.</button>'
    })

2.组件用法

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
组件注册
*/
Vue.component('button-counter', {
data: function(){
return {
count: 0
}
},
template: '<button @click="handle">点击了{{count}}次</button>',
methods: {
handle: function(){
this.count += 2;
}
}
})
// 新建Vue
var vm = new Vue({
el: '#app',
data: {

}
});
</script>
</body>
</html>

3.组件注册注意事项

  1. data必须是一个函数(而Vue实例的data是一个对象)
    • 分析函数与普通对象的对比

      保证每个组件的数据是相互独立的

  2. 组件模板内容必须是单个根元素(而不能是两个并列的标签)
    • 分析演示实际的效果
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      Vue.component('button-counter', {
      data: function(){
      return {
      count: 0
      }
      },
      //两标签并列,报错
      //template: '<button @click="handle">点击了{{count}}次</button><button>测试</button>',
      //外面包一个div,形成一个根元素,不报错
      template: '<div><button @click="handle">点击了{{count}}次</button><button>测试</button></div>',
      methods: {
      handle: function(){
      this.count += 2;
      }
      }
      })
  3. 组件模板内容可以是模板字符串
    • 模板字符串需要浏览器提供支持(ES6语法)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      Vue.component('button-counter', {
      data: function(){
      return {
      count: 0
      }
      },
      //若模板内容复杂,就用模板字符串的方式进行处理,反引号包起来
      template: `
      <div>
      <button @click="handle">点击了{{count}}次</button>
      <button>测试123</button>
      </div>
      `,
      methods: {
      handle: function(){
      this.count += 2;
      }
      }
      })
  4. 组件命名方式
    • 短横线方式(推荐)
      1
      Vue.component('my-component', { /* ... */ })
    • 驼峰方式
      1
      Vue.component('MyComponent', { /* ... */ })
      注意:驼峰命名组件,只能用于模板字符串中,否则直接用在页面中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
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>Document</title>
      </head>
      <body>
      <div id="app">
      <button-counter></button-counter>
      <hello-world></hello-world>
      </div>
      <script type="text/javascript" src="js/vue.js"></script>
      <script type="text/javascript">
      /*
      组件注册注意事项
      如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,但是
      在普通的标签模板中,必须使用短横线的方式使用组件
      */
      Vue.component('HelloWorld', {
      data: function(){
      return {
      msg: 'HelloWorld'
      }
      },
      template: '<div>{{msg}}</div>'
      });
      Vue.component('button-counter', {
      data: function(){
      return {
      count: 0
      }
      },
      template: `
      <div>
      <button @click="handle">点击了{{count}}次</button>
      <button>测试123</button>
      <HelloWorld></HelloWorld>
      </div>
      `,
      methods: {
      handle: function(){
      this.count += 2;
      }
      }
      })
      var vm = new Vue({
      el: '#app',
      data: {

      }
      });
      </script>
      </body>
      </html>

4.局部组件注册

就是在Vue组件之中引入一个额外的属性components,它里面可以注册一系列的局部组件

局部组件只能在父组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 组件内容可以抽取到对象当中,和全局组件Vue.component第二个参数是类似的
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

new Vue({
el: '#app'
components: {
// '组件名称' : 组件内容
'component-a': ComponentA,
'component-b': ComponentB,
'component-c': ComponentC,
}
})
  • 示例:
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <div id="app">
    <hello-world></hello-world>
    <hello-tom></hello-tom>
    <hello-jerry></hello-jerry>
    <test-com></test-com>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
    /*
    局部组件注册
    局部组件只能在注册他的父组件中使用
    */
    Vue.component('test-com',{
    template: '<div>Test<hello-world></hello-world></div>'
    });
    var HelloWorld = {
    data: function(){
    return {
    msg: 'HelloWorld'
    }
    },
    template: '<div>{{msg}}</div>'
    };
    var HelloTom = {
    data: function(){
    return {
    msg: 'HelloTom'
    }
    },
    template: '<div>{{msg}}</div>'
    };
    var HelloJerry = {
    data: function(){
    return {
    msg: 'HelloJerry'
    }
    },
    template: '<div>{{msg}}</div>'
    };
    var vm = new Vue({
    el: '#app',
    data: {

    },
    components: {
    'hello-world': HelloWorld,
    'hello-tom': HelloTom,
    'hello-jerry': HelloJerry
    }
    });
    </script>
    </body>
    </html>

【Vue调试工具】Devtools

vue-dev-tools是一款可以方便我们在浏览器调试我们vue项目的工具
工具地址:Vue官网右上角“生态系统”下拉菜单的Devtools工具

项目仓库:https://github.com/vuejs/devtools
使用文档:https://devtools.vuejs.org/

1.调试工具安装

视频教程中给的安装步骤是如下六步,

我先说好,我照着试了不管用,因为第二步npm install安装依赖包的时候,因为版本问题报错!!!

  1. 克隆仓库
    1
    git clone https://github.com/vuejs/devtools.git
  2. 在vue-devtools目录下安装依赖包
    1
    2
    3
    cd devtools        //进入当前目录
    npm install       //下载依赖(报错解决方法看下面)
    //使用npm需科学上网,或者cnpm国内镜像下载更快
  3. 构建
    1
    npm run build         //打包
  4. 打开Chrome扩展页面
  5. 选中开发者模式
  6. 加载已解压的扩展,选择shells/chrome

那既然这种方法行不通,最后我怎么解决的?

我把我的踩坑过程记录下来了:安装Vue调试工具Devtools踩的坑

忙活小半天,最后用的同学给我的压缩包,才安装成功…

2.调试工具用法

打开下方页面,浏览器打开控制台,往后拉就可以看到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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
.root {
background-color: orange;
}
.second {
background-color: lightgreen;
}
.third {
background-color: lightblue;
}
</style>
</head>
<body>
<div id="app" class="root">
<div>{{root}}</div>
<second-com></second-com>
<second-com></second-com>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
Vue调试工具安装与基本使用
*/
Vue.component('second-com',{
data: function(){
return {
second: '二级组件'
}
},
template: `<div class='second'>
<div>{{second}}</div>
<third-com></third-com>
<third-com></third-com>
<third-com></third-com>
</div>`
});
Vue.component('third-com',{
data: function(){
return {
third: '三级组件'
}
},
template: '<div class="third"><div>{{third}}</div></div>'
});

var vm = new Vue({
el: '#app',
data: {
root: '顶层组件'
}
});
</script>
</body>
</html>

【组件间的数据交互】(P60~65)

1.父组件向子组件传值

(1)组件内部通过props接收传递值

引入一个新的属性props,用来接受从父组件传递过来的数据,值是数组。

传过来后,我们在模板template中就能使用这个属性了。

1
2
3
4
5
// 子组件
Vue.component('menu-item', {
props: ['title'],
template: '<div>{{ title }}</div>'
})

(2)父组件通过属性传值给子组件

1
2
3
4
// 1. 静态写死的方式,通过属性传递
<menu-item title="来自父组件的数据"></menu-item>
// 2. 动态绑定属性的方式
<menu-item :title='ptitle'></menu-item>
  • 示例:
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <div id="app">
    <!-- 子组件本身的数据 -->
    <div>{{pmsg}}</div>

    <!-- 1. 静态写死,通过属性传递 来自父组件的值 -->
    <menu-item title='来自父组件的值'></menu-item>
    <!-- 2. 动态绑定属性的方式 -->
    <menu-item :title='ptitle' content='hello'></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
    /*
    父组件向子组件传值-基本使用
    */
    Vue.component('menu-item', {
    props: ['title', 'content'],
    data: function() {
    return {
    msg: '子组件本身的数据'
    }
    },
    template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
    });

    var vm = new Vue({
    el: '#app',
    data: {
    pmsg: '父组件中内容',
    ptitle: '动态绑定属性'
    }
    });
    </script>
    </body>
    </html>

(3)props属性名规则

  • 在props中使用驼峰形式,模板中需要使用短横线的形式
  • 字符串形式的模板中没有这个限制
1
2
3
4
5
6
7
8
Vue.component(‘menu-item', {
// 在 JavaScript 中是驼峰式的
props: [‘menuTitle'],
template: '<div>{{ menuTitle }}</div>'
})

// 在html中是短横线方式的
<menu-item menu-title="nihao"></menu-item>

(4)props属性值类型

  • 字符串 String
  • 数值 Number
  • 布尔值 Boolean
  • 数组 Array
  • 对象 Object

这一部分没看太懂,视频指路→父组件向子组件传值-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
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{pmsg}}</div>
<menu-item :pstr='pstr' :pnum='12' pboo='true' :parr='parr' :pobj='pobj'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
父组件向子组件传值-props属性值类型
*/

Vue.component('menu-item', {
props: ['pstr','pnum','pboo','parr','pobj'],
template: `
<div>
<div>{{pstr}}</div>
<div>{{12 + pnum}}</div>
<div>{{typeof pboo}}</div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<span>{{pobj.name}}</span>
<span>{{pobj.age}}</span>
</div>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
pstr: 'hello',
parr: ['apple','orange','banana'],
pobj: {
name: 'lisi',
age: 12
}
}
});
</script>
</body>
</html>

2.子组件向父组件传值

视频教程

props传递参数原则:单向数据流

意思是只允许父组件向子组件传递数据,不允许子组件向父组件传递数据

所以子组件向父组件传递信息最好不要用props

(1)子组件通过自定义事件向父组件传递信息

1
2
3
4
5
<!-- 
$emit是个固定的方法名
携带的参数名称enlarge-text就是自定义事件(自己起的名字)
-->
<button v-on:click='$emit("enlarge-text") '>扩大字体</button>

(2)父组件监听子组件的事件

1
2
3
4
<!-- 
绑定子组件事件名称,后面是事件的处理逻辑
-->
<menu-item v-on:enlarge-text='fontSize += 0.1'></menu-item>

(3)子组件通过自定义事件向父组件传递信息(携带参数)

1
2
3
4
5
6
<!-- 
$emit是个固定的方法名
携带的参数名称enlarge-text就是自定义事件(自己起的名字)
后面数字是触发事件时携带的参数
-->
<button v-on:click='$emit("enlarge-text", 0.1) '>扩大字体</button>

(4)父组件监听子组件的事件(携带参数)

1
2
3
4
<!-- 
$event是固定用法
-->
<menu-item v-on:enlarge-text='fontSize += $event'></menu-item>

3.非父子组件间传值

兄弟组件之间无法直接交互,须通过“事件中心”来进行通信。

(1)设置单独的事件中心管理组件间的通信

1
2
// 新建一个Vue实例当事件中心
var eventHub = new Vue()

(2)监听事件与销毁事件

1
2
3
// 监听事件('事件名称',事件函数)
eventHub.$on('add-todo', addTodo)
eventHub.$off('add-todo')

(3)触发事件

1
2
// 触发事件('事件名称', 携带的参数比如id)
eventHub.$emit('add-todo', 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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>父组件</div>
<div>
<button @click='handle'>销毁事件</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
兄弟组件之间数据传递
*/
// 提供事件中心
var hub = new Vue();

Vue.component('test-tom', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>TOM:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function(){
hub.$emit('jerry-event', 2);
}
},
mounted: function() {
// 钩子函数监听事件
hub.$on('tom-event', (val) => {
this.num += val;
});
}
});
Vue.component('test-jerry', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>JERRY:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function(){
// 触发兄弟组件的事件
hub.$emit('tom-event', 1);
}
},
mounted: function() {
// 钩子函数监听事件
hub.$on('jerry-event', (val) => {
this.num += val;
});
}
});
var vm = new Vue({
el: '#app',
data: {

},
methods: {
handle: function(){
// 销毁事件,销毁之后两个按钮会失效
hub.$off('tom-event');
hub.$off('jerry-event');
}
}
});
</script>
</body>
</html>

【组件插槽】(P66~68)

1.组件插槽的作用

组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力.

  • 父组件向子组件传递内容

    bL9ejf.png

2.组件插槽基本用法

(1)插槽的位置

1
2
3
4
5
6
7
8
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot> //设置插槽,预留一个位置
</div>
`
})

(2)插槽内容

1
<alert-box>Something bad happened.</alert-box>
  • 插槽基本用法示例:
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <div id="app">
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
    /*
    组件插槽:父组件向子组件传递内容
    */
    Vue.component('alert-box', {
    template: `
    <div>
    <strong>ERROR:</strong>
    <slot>默认内容</slot>
    </div>
    `
    });
    var vm = new Vue({
    el: '#app',
    data: {

    }
    });
    </script>
    </body>
    </html>

上方演示的插槽没有名字(name值),可叫作匿名插槽。

3.具名插槽用法

就是有名字(name值)的插槽

(1)插槽定义

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

(2)插槽内容

可以定义多个插槽,会根据名称进行匹配,没匹配到/没名字的会填入默认值。

1
2
3
4
5
6
7
8
<base-layout>
<h1 slot="header">标题内容</h1>

<p>主要内容1</p>
<p>主要内容2</p>

<p slot="footer">底部内容</p>
</base-layout>
  • 具名插槽的使用方法:
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <div id="app">
    <base-layout>
    <p slot='header'>标题信息</p>
    <p>主要内容1</p>
    <p>主要内容2</p>
    <p slot='footer'>底部信息信息</p>
    </base-layout>

    <base-layout>
    <template slot='header'>
    <p>标题信息1</p>
    <p>标题信息2</p>
    </template>
    <p>主要内容1</p>
    <p>主要内容2</p>
    <template slot='footer'>
    <p>底部信息信息1</p>
    <p>底部信息信息2</p>
    </template>
    </base-layout>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
    /*
    具名插槽
    */
    Vue.component('base-layout', {
    template: `
    <div>
    <header>
    <slot name='header'></slot>
    </header>
    <main>
    <slot></slot>
    </main>
    <footer>
    <slot name='footer'></slot>
    </footer>
    </div>
    `
    });
    var vm = new Vue({
    el: '#app',
    data: {

    }
    });
    </script>
    </body>
    </html>

4.作用域插槽

应用场景:父组件对子组件的内容进行加工处理

(1)插槽定义

1
2
3
4
5
6
7
<ul>
<li v-for= "item in list" v-bind:key= "item.id" >
<slot v-bind:item="item">
{{item.name}}
</slot>
</li>
</ul>

(2)插槽内容

1
2
3
4
5
6
7
<fruit-list v-bind:list= "list">
<template slot-scope="slotProps">
<strong v-if="slotProps.item.current">
{{ slotProps.item.text }}
</strong>
</template>
</fruit-list>
  • 作用域插槽示例:
    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>Document</title>
    </head>
    <style type="text/css">
    .current {
    color: orange;
    }
    </style>
    <body>
    <div id="app">
    <fruit-list :list='list'>
    <template slot-scope='slotProps'>
    <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
    <span v-else>{{slotProps.info.name}}</span>
    </template>
    </fruit-list>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
    /*
    作用域插槽
    */
    Vue.component('fruit-list', {
    props: ['list'],
    template: `
    <div>
    <li :key='item.id' v-for='item in list'>
    <slot :info='item'>{{item.name}}</slot>
    </li>
    </div>
    `
    });
    var vm = new Vue({
    el: '#app',
    data: {
    list: [{
    id: 1,
    name: 'apple'
    },{
    id: 2,
    name: 'orange'
    },{
    id: 3,
    name: 'banana'
    }]
    }
    });
    </script>
    </body>
    </html>

【基于组件的案例】(P69~74)

购物车案例:

qPu5QJ.png

代码素材:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
.container {
}
.container .cart {
width: 300px;
/*background-color: lightgreen;*/
margin: auto;
}
.container .title {
background-color: lightblue;
height: 40px;
line-height: 40px;
text-align: center;
/*color: #fff;*/
}
.container .total {
background-color: #FFCE46;
height: 50px;
line-height: 50px;
text-align: right;
}
.container .total button {
margin: 0 10px;
background-color: #DC4C40;
height: 35px;
width: 80px;
border: 0;
}
.container .total span {
color: red;
font-weight: bold;
}
.container .item {
height: 55px;
line-height: 55px;
position: relative;
border-top: 1px solid #ADD8E6;
}
.container .item img {
width: 45px;
height: 45px;
margin: 5px;
}
.container .item .name {
position: absolute;
width: 90px;
top: 0;left: 55px;
font-size: 16px;
}

.container .item .change {
width: 100px;
position: absolute;
top: 0;
right: 50px;
}
.container .item .change a {
font-size: 20px;
width: 30px;
text-decoration:none;
background-color: lightgray;
vertical-align: middle;
}
.container .item .change .num {
width: 40px;
height: 25px;
}
.container .item .del {
position: absolute;
top: 0;
right: 0px;
width: 40px;
text-align: center;
font-size: 40px;
cursor: pointer;
color: red;
}
.container .item .del:hover {
background-color: orange;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<div class="cart">
<div class="title">我的商品</div>
<div>
<div class="item">
<img src="img/a.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/b.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/c.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/d.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/e.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
</div>
<div class="total">
<span>总价:123</span>
<button>结算</button>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">

var vm = new Vue({
el: '#app',
data: {

}
});

</script>
</body>
</html>

1.需求分析

  • 按照组件化方式实现业务需求
    • 根据业务功能进行组件化划分
      1. 标题组件(展示文本)
      2. 列表组件(列表展示、商品数量变更、商品删除)
      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
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
<!DOCTYPE html>
<html lang="zh-Ch">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
/* CSS内容略 */
</style>
</head>
<body>
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>

<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">


// 定义局部组件的内容(下方全局组件my-cart的子组件)
var CartTitle = {
// 模板内容
template: `
<div class="title">我的商品</div>
`
}
var CartList = {
// 模板内容
template: `
<div>
<div class="item">
<img src="img/a.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/b.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/c.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/d.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/e.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
</div>
`
}
var CartTotal = {
// 模板内容
template: `
<div class="total">
<span>总价:123</span>
<button>结算</button>
</div>
`
}



// 全局组件注册,命名为my-cart(我的购物车)
// 该全局组件是它内部三个局部组件的父组件
Vue.component('my-cart',{
// 组件的模板内容
template: `
<div class="cart">
<cart-title></cart-title>
<cart-list></cart-list>
<cart-total></cart-total>
</div>
`,
// 局部组件的注册(组件名称:组件内容)
components: {
'cart-title':CartTitle,
'cart-list':CartList,
'cart-total':CartTotal
}
});



// Vue根组件
var vm = new Vue({
el: '#app',
data: {

}
});

</script>
</body>
</html>

2.实现步骤

  • 功能实现步骤
    • 实现整体布局和样式效果
    • 划分独立的功能组件
    • 组合所有的子组件形成整体结构
    • 逐个实现各个组件功能
      • 标题组件
      • 列表组件
      • 结算组

实现标题和结算组件功能,之后的代码:

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
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>

<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">


// 定义局部组件的内容(下方全局组件my-cart的子组件)
var CartTitle = {
// 接受从父组件传递过来的“用户名”数据,值是数组
props: ['uname'],
// 模板内容
template: `
<div class="title">{{uname}}的商品</div>
`
}
var CartList = {
// 模板内容
template: `
<div>
<div class="item">
<img src="img/a.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/b.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/c.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/d.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/e.jpg"/>
<div class="name"></div>
<div class="change">
<a href=""></a>
<input type="text" class="num" />
<a href=""></a>
</div>
<div class="del">×</div>
</div>
</div>
`
}
var CartTotal = {
// // 接受从父组件传递过来的“商品”数据,值是数组
props: ['list'],
// 模板内容
template: `
<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>
`,
// 计算属性,是个对象,里面可以定义计算属性的名称
computed: {
total: function() {
// 计算商品的总价:先遍历商品单价和数量,再累加
var t = 0;
this.list.forEach(item => {
t += item.price * item.num;
})
return t;
}
}
}



// 全局组件注册,命名为my-cart(我的购物车)
// 该全局组件是它内部三个局部组件的父组件
Vue.component('my-cart',{
// 数据,必须是一个函数
data: function(){
return {
uname: '百里飞洋',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
},{
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
},{
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
},{
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
},{
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
// 组件的模板内容
template: `
<div class="cart">
<cart-title :uname='uname'></cart-title>
<cart-list></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
// 局部组件的注册(组件名称:组件内容)
components: {
'cart-title':CartTitle,
'cart-list':CartList,
'cart-total':CartTotal
}
});



// Vue根组件
var vm = new Vue({
el: '#app',
data: {

}
});

</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
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
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>

<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">


// 定义局部组件的内容(下方全局组件my-cart的子组件)
var CartTitle = {
// 接受从父组件传递过来的“用户名”数据,值是数组
props: ['uname'],
// 模板内容
template: `
<div class="title">{{uname}}的商品</div>
`
}
var CartList = {
// 接受从父组件传递过来的“商品”数据,值是数组
props: ['list'],
// 模板内容:删除重复模块,遍历商品数据
template: `
<div>
<div :key='item.id' v-for='item in list' class="item">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del" @click='del(item.id)'>×</div>
</div>
</div>
`,
// 定义函数方法
methods: {
del: function(id){
// console.log(id)
// 把id传递给父组件,让父组件进行删除操作。其中emit是API,cart-del是自定义事件的名称
this.$emit('cart-del', id)
}
}
}
var CartTotal = {
// 接受从父组件传递过来的“商品”数据,值是数组
props: ['list'],
// 模板内容
template: `
<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>
`,
// 计算属性,是个对象,里面可以定义计算属性的名称
computed: {
total: function() {
// 计算商品的总价:先遍历商品单价和数量,再累加
var t = 0;
this.list.forEach(item => {
t += item.price * item.num;
})
return t;
}
}
}



// 全局组件注册,命名为my-cart(我的购物车)
// 该全局组件是它内部三个局部组件的父组件
Vue.component('my-cart',{
// 数据,必须是一个函数
data: function(){
return {
uname: '百里飞洋',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
},{
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
},{
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
},{
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
},{
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
// 组件的模板内容
// 其中 @cart-del 表示监听子组件的那个事件
// 后面 delCart 是自己起的事件处理函数,用来接受子组件传过来的id(通过$event接受,传递给前面的delCart)
template: `
<div class="cart">
<cart-title :uname='uname'></cart-title>
<cart-list :list='list' @cart-del='delCart($event)'></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
// 局部组件的注册(组件名称:组件内容)
components: {
'cart-title':CartTitle,
'cart-list':CartList,
'cart-total':CartTotal
},
// 定义函数方法
methods: {
// 拿到子组件传递过来的id,去删除list中对应的数据
delCart: function(id) {
// 1.找到id所对应的数据的索引:其中 findIndex() 是找索引的方法
var index = this.list.findIndex(item => {
return item.id == id; //拿着商品列表的id,比对接收到的id,返回处理结果
});
// 2.根据索引,删除对应数据:其中 splice() 是删除数组元素的方法,两参数分别表示要删除的索引值和删除个数
this.list.splice(index, 1);
}
}
});



// Vue根组件
var vm = new Vue({
el: '#app',
data: {

}
});

</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
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
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>

<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">


// 定义局部组件的内容(下方全局组件my-cart的子组件)
var CartTitle = {
// 接受从父组件传递过来的“用户名”数据,值是数组
props: ['uname'],
// 模板内容
template: `
<div class="title">{{uname}}的商品</div>
`
}
var CartList = {
// 接受从父组件传递过来的“商品”数据,值是数组
props: ['list'],
// 模板内容:删除重复模块,遍历商品数据
template: `
<div>
<div :key='item.id' v-for='item in list' class="item">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
<a href="">+</a>
</div>
<div class="del" @click='del(item.id)'>×</div>
</div>
</div>
`,
// 定义函数方法
methods: {
// @blur 是当元素失去焦点时所触发的事件
// 监听事件@blur,当鼠标失去焦点时,修改商品数量:其中event传递的事件对象,event.target.value可以拿到输入域最新的值
changeNum: function(id, event){
// console.log(id, event.target.value)
this.$emit('change-num',{
id: id,
num: event.target.value
}) //其中$emit触发自定义事件change-num,传递两个参数:商品id和商品数量
},
// 删除商品
del: function(id){
// console.log(id)
// 把id传递给父组件,让父组件进行删除操作。其中$emit是API,cart-del是自定义事件的名称
this.$emit('cart-del', id)
}
}
}
var CartTotal = {
// 接受从父组件传递过来的“商品”数据,值是数组
props: ['list'],
// 模板内容
template: `
<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>
`,
// 计算属性,是个对象,里面可以定义计算属性的名称
computed: {
total: function() {
// 计算商品的总价:先遍历商品单价和数量,再累加
var t = 0;
this.list.forEach(item => {
t += item.price * item.num;
})
return t;
}
}
}



// 全局组件注册,命名为my-cart(我的购物车)
// 该全局组件是它内部三个局部组件的父组件
Vue.component('my-cart',{
// 数据,必须是一个函数
data: function(){
return {
uname: '百里飞洋',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
},{
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
},{
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
},{
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
},{
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
// 组件的模板内容
// 其中 @cart-del 表示监听子组件的那个事件
// 后面 delCart 是自己起的事件处理函数,用来接受子组件传过来的id(通过$event接受,传递给前面的delCart)
template: `
<div class="cart">
<cart-title :uname='uname'></cart-title>
<cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
// 局部组件的注册(组件名称:组件内容)
components: {
'cart-title':CartTitle,
'cart-list':CartList,
'cart-total':CartTotal
},
// 定义函数方法
methods: {
// 用参数val表示传过来那俩参数和值
changeNum: function(val){
// console.log(val) //找个输入框改个数,失去焦点,看控制台打印
// 根据子组件传递过来的数据,更新list中对应的数据:其中用到的了数组的some()遍历方法
this.list.some(item=>{
if(item.id == val.id) {
item.num = val.num;
// 终止遍历
return true; //some()方法的规则
}
})
},
// 拿到子组件传递过来的id,去删除list中对应的数据
delCart: function(id) {
// 1.找到id所对应的数据的索引:其中 findIndex() 是找索引的方法
var index = this.list.findIndex(item => {
return item.id == id; //拿着商品列表的id,比对接收到的id,返回处理结果
});
// 2.根据索引,删除对应数据:其中 splice() 是删除数组元素的方法,两参数分别表示要删除的索引值和删除个数
this.list.splice(index, 1);
}
}
});



// Vue根组件
var vm = new Vue({
el: '#app',
data: {

}
});

</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
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
<!DOCTYPE html>
<html lang="zh-Ch">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
/* .container {
} */
.container .cart {
width: 300px;
margin: auto;
}
.container .title {
background-color: lightblue;
height: 40px;
line-height: 40px;
text-align: center;
/*color: #fff;*/
}
.container .total {
background-color: #FFCE46;
height: 50px;
line-height: 50px;
text-align: right;
}
.container .total button {
margin: 0 10px;
background-color: #DC4C40;
height: 35px;
width: 80px;
border: 0;
}
.container .total span {
color: red;
font-weight: bold;
}
.container .item {
height: 55px;
line-height: 55px;
position: relative;
border-top: 1px solid #ADD8E6;
}
.container .item img {
width: 45px;
height: 45px;
margin: 5px;
}
.container .item .name {
position: absolute;
width: 90px;
top: 0;left: 55px;
font-size: 16px;
}

.container .item .change {
width: 100px;
position: absolute;
top: 0;
right: 50px;
}
.container .item .change a {
font-size: 20px;
width: 30px;
text-decoration:none;
background-color: lightgray;
vertical-align: middle;
}
.container .item .change .num {
width: 40px;
height: 25px;
}
.container .item .del {
position: absolute;
top: 0;
right: 0px;
width: 40px;
text-align: center;
font-size: 40px;
cursor: pointer;
color: red;
}
.container .item .del:hover {
background-color: orange;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>

<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">


// 定义局部组件的内容(下方全局组件my-cart的子组件)
var CartTitle = {
// 接受从父组件传递过来的“用户名”数据,值是数组
props: ['uname'],
// 模板内容
template: `
<div class="title">{{uname}}的商品</div>
`
}
var CartList = {
// 接受从父组件传递过来的“商品”数据,值是数组
props: ['list'],
// 模板内容:删除重复模块,遍历商品数据
template: `
<div>
<div :key='item.id' v-for='item in list' class="item">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="" @click.prevent='sub(item.id)'>-</a>
<input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
<a href="" @click.prevent='add(item.id)'>+</a>
</div>
<div class="del" @click='del(item.id)'>×</div>
</div>
</div>
`,
// 定义函数方法
methods: {
// @blur 是当元素失去焦点时所触发的事件
// 监听事件@blur,当鼠标失去焦点时,修改商品数量:其中event传递的事件对象,event.target.value可以拿到输入域最新的值
changeNum: function(id, event){
// console.log(id, event.target.value)
this.$emit('change-num',{
id: id,
type: 'change', //因为用的都是同一个事件change-num,所以要区分类型
num: event.target.value
}); //其中$emit触发自定义事件change-num,传递两个参数:商品id和商品数量
},
// 减法
sub: function (id){
this.$emit('change-num', {
id: id,
type: 'sub' //因为用的都是同一个事件change-num,所以要区分类型
});
},
// 加法
add: function (id){
this.$emit('change-num', {
id: id,
type: 'add' //因为用的都是同一个事件change-num,所以要区分类型
});
},
// 删除商品
del: function(id){
// console.log(id)
// 把id传递给父组件,让父组件进行删除操作。其中$emit是API,cart-del是自定义事件的名称
this.$emit('cart-del', id)
}
}
}
var CartTotal = {
// 接受从父组件传递过来的“商品”数据,值是数组
props: ['list'],
// 模板内容
template: `
<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>
`,
// 计算属性,是个对象,里面可以定义计算属性的名称
computed: {
total: function() {
// 计算商品的总价:先遍历商品单价和数量,再累加
var t = 0;
this.list.forEach(item => {
t += item.price * item.num;
})
return t;
}
}
}



// 全局组件注册,命名为my-cart(我的购物车)
// 该全局组件是它内部三个局部组件的父组件
Vue.component('my-cart',{
// 数据,必须是一个函数
data: function(){
return {
uname: '百里飞洋',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
},{
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
},{
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
},{
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
},{
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
// 组件的模板内容
// 其中 @cart-del 表示监听子组件的那个事件
// 后面 delCart 是自己起的事件处理函数,用来接受子组件传过来的id(通过$event接受,传递给前面的delCart)
template: `
<div class="cart">
<cart-title :uname='uname'></cart-title>
<cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
// 局部组件的注册(组件名称:组件内容)
components: {
'cart-title':CartTitle,
'cart-list':CartList,
'cart-total':CartTotal
},
// 定义函数方法
methods: {
// 用参数val表示传过来那俩参数和值
changeNum: function(val){
// 分为三种情况:1.输入域变更;2.减号变更;3.加号变更
if(val.type == 'change') {
//1.输入域变更
// console.log(val) //找个输入框改个数,失去焦点,看控制台打印
// 根据子组件传递过来的数据,更新list中对应的数据:其中用到的了数组的some()遍历方法
this.list.some(item=>{
if(item.id == val.id) {
item.num = val.num;
// 终止遍历
return true; //some()方法的规则
}
})
}else if(val.type == 'sub') {
//2.减号变更
this.list.some(item=>{
if(item.id == val.id) {
item.num -= 1;
// 终止遍历
return true; //some()方法的规则
}
})
}else if(val.type == 'add') {
//3.加号变更
this.list.some(item=>{
if(item.id == val.id) {
item.num += 1;
// 终止遍历
return true; //some()方法的规则
}
})
}

},
// 拿到子组件传递过来的id,去删除list中对应的数据
delCart: function(id) {
// 1.找到id所对应的数据的索引:其中 findIndex() 是找索引的方法
var index = this.list.findIndex(item => {
return item.id == id; //拿着商品列表的id,比对接收到的id,返回处理结果
});
// 2.根据索引,删除对应数据:其中 splice() 是删除数组元素的方法,两参数分别表示要删除的索引值和删除个数
this.list.splice(index, 1);
}
}
});



// Vue根组件
var vm = new Vue({
el: '#app',
data: {

}
});

</script>
</body>
</html>

》案例中知识点总结

  • 组件的模板内容 template:后面,要么是一行单引号包起来的单个根元素,要不然是反引号包起来的多行模板字符串

  • 父组件的数据属性 data: function(){ } 必须是函数

  • 子组件接收父组件的数据值 props: [], 须是数组,传过来后,在该子组件的模板template中就能使用这个属性了

  • v-bind:可以缩写为冒号,绑定的是属性;

  • v-on:可以缩写为@,绑定的是事件;

  • 分清单引号和反引号,注意中英文逗号

  • 遍历数组时别忘了加:key='item.id'或者:key='item.index'

  • 对于最后这个商品案例,删除操作要在父组件中进行处理

  • 原则上,通过 props 传来的父组件的数据我们不做修改,所以商品数量动态显示的实现方式,不采用 v-model动态绑定,而是直接选择了属性绑定 :value='' 直接填充过来显示就可以

  • 在鼠标失去焦点时(@blur)变更商品数量,也不推荐在子组件中修改list数组的值,所以还是选择传递给父组件。传参时,$event作为参数,可以把事件对象传递给函数。

  • event.target.value() 可获取当前文本框的值(由事件触发时)。可以去了解一下event.target是什么意思

  • 整理一下子组件传值给父组件,让父组件操作业务的逻辑(以变更商品数量的功能为例):

    1. 在子组件的template模板中设置监听事件 @blur='changeNum(item.id, $event)',其中 @blur 是当元素失去焦点时触发事件,触发的事件名称是 changeNum()
    2. 然后在该子组件的methods函数方法中定义 changeNum() 函数为changeNum: function(id, event){ this.$emit('change-num',{ 执行语句 } },其中$emit是为了让父组件监听到自定义事件,自定义事件的名称是 change-num
    3. 之后父组件在父组件自己的template模板字符串中为子组件标签添加’change-num’自定义监听事件:@change-num='changeNum($event)',其中 changeNum() 是指该子组件标签触发事件时执行的函数名称,另外$event是为了接收传过来的值。
    4. 最后在父组件methods函数方法中定义所执行的函数 changeNum: function(val){ 执行语句 },其中val表示第二步传过来那俩参数和值(id, event)。
  • 我发现很多操作都是让子组件传值给父组件,让父组件执行,而不是子组件自己修改数据。

    1. vue2.0 子组件props接受父组件传递的值,能不能修改的问题整理
    2. vue 子组件为何不可以修改父组件传递的值?
    3. 总之:vue设计是单向数据流,数据的流动方向只能是自上往下的方向;父子组件传值时,父组件传递的参数,数组和对象,子组件接受之后可以直接进行修改,并且会传递给父组件相应的值也会修改。如果传递的值是字符串,直接修改会报错。综上,不推荐子组件直接修改父组件中的参数,避免这个参数多个子组件引用,无法找到造成数据不正常的原因。
  • 可能是第一次用组件的思维写代码,父子组件数据交互还是听着有点糊涂,尤其是子组件传给父组件的时候,光函数就好几个:

    1. 子组件的事件监听函数、执行函数,
    2. 父组件的事件监听函数、执行函数…

特别感谢: