Bootstrap

Vue | 03Vue之组件化开发

一、组件化开发思想

1.1 现实中的组件化思想体现

什么是组件化开发呢?简单来说就是将目标分解为多个组件,举个栗子就是:台式电脑的机箱内由CPU、显卡、内存条、硬盘、主板来组成,这些就可以看成是组件。可以理解为机箱由多个组件组合而成。

思想: 标准、分治、重用、组合

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

现实生活中我们存在各种各样的关系,如父子关系,兄弟关系,等、所以在软件开发中就引入了这样的思想,存在父组件、子组件、兄弟组件这样的关系。

1.3 组件化规范: Web Components

  • 希望尽可能多的重用代码
  • 自定义组件的方式不太容易(html、css和js)
  • 多次使用组件可能导致冲突
    Web Components 通过创建封装好功能的 定制元素解决上述问题

官方网站

二、组件注册

2.1 全局组件注册语法

Vue.component(组件名称, {
	  data: 组件数据,
	  template: 组件的模板内容,
});
<script type="text/javascript">
      // 注册一个名为 button-counter 的新组件
      Vue.component("button-counter", {
        data: function () {
          return {
            count: 0,
          };
        },
        template: '<button v-on:click="count++">点击了{{ count }}次.</button>',
      });
</script>

2.2 组件使用

<div id="app">
	<button-counter></button-counter>
</div>

<div id="app">
	<button-counter></button-counter>
	<button-counter></button-counter>
	<button-counter></button-counter>
</div>

2.3 组件注册注意事项

  1. data必须是一个函数
<script type="text/javascript">
    Vue.component('button-counter',{
        data:{
            count: 0,
        },
        template:'<button @click="handle">点击次数为{{count}}</button>',
        methods:{
            handle:function(){
                this.count++;
            }
        }
        
    });
      var vm = new Vue({
        el: "#app",
        data: {},
      });
</script>

控制台输出内容:
data必须是一个函数
正确写法:

<script type="text/javascript">
      Vue.component('button-counter',{
          data:function(){
              return{
                  count: 0,
              }
          },
          template:'<button @click="handle">点击次数为{{count}}</button>',
          methods:{
              handle:function(){
                  this.count++;
              }
          }

      });
        var vm = new Vue({
          el: "#app",
          data: {},
        });
</script>
  1. 组件模板内容必须是单个跟元素

错误写法:

template:'<button @click="handle">点击次数为{{count}}</button><button></button>',

控制台输出内容:
组件模板内容必须是单个跟元素

  1. 组件模板内容可以是模板字符串

书写格式:

 template:
          `
            <div>
                <button @click="handle">点击了{{count}}次</button>
            </div>
          `,
  1. 组件命名方式

短横线方式:

Vue.component('my-component', { /* ... */ })

驼峰方式:

Vue.component('MyComponent', { /* ... */ })
 <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>
          <HelloWorld></HelloWorld>
        </div>
      `,
        methods: {
          handle: function () {
            this.count += 2;
          },
        },
      });
      var vm = new Vue({
        el: "#app",
        data: {},
      });
</script>


测试中遇到的坑:

<div id="app">
      <hello-World></hello-World>
</div>
<script type="text/javascript">
      Vue.component("hello-World", {
        data: function () {
          return {
            msg: "HelloWorld",
          };
        },
        template: "<div>{{msg}}</div>",
      });
      var vm = new Vue({
        el: "#app",
        data: {},
      });
</script>

报错提示

看了很久就是找不出来错误在哪,浏览器就一直报这个错误,在后来的测试中发现与这个命名有关,在注册组件的时候我虽然使用的短横线来连接,但是还是被识别为驼峰命名,所以导致这个错误,解决错误的方法就是去掉短横线,helloWorld这样写即可。如果要使用驼峰命名法,在使用组件时一定要加短横线。

在这里插入图片描述

组件注册注意事项总结:
1、组件参数的data值必须是函数同时这个函数要求返回一个对象
2、组件模板必须是单个根元素
3、组件模板的内容可以是模板字符串
4、如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,但是在普通的标签模板中,必须使用短横线的方式使用组件

2.4 局部组件注册

<div id="app">
      <Hello-World></Hello-World>
      <Hello-Lucy></Hello-Lucy>
      <Hello-Tom></Hello-Tom>
      <Hello-Kitty></Hello-Kitty>
</div>
<script type="text/javascript">
      var HelloWorld = {
        data: function () {
          return {
            msg: "HelloWorld",
          };
        },
        template: "<div>{{msg}}</div>",
      };
      var HelloLucy = {
        data: function () {
          return {
            msg: "HelloLucy",
          };
        },
        template: "<div>{{msg}}</div>",
      };
      var HelloTom = {
        data: function () {
          return {
            msg: "HelloTom",
          };
        },
        template: "<div>{{msg}}</div>",
      };
      var HelloKitty = {
        data: function () {
          return {
            msg: "HelloKitty",
          };
        },
        template: "<div>{{msg}}</div>",
      };
      var vm = new Vue({
        el: "#app",
        data: {},
        components: {
          "hello-world": HelloWorld,
          "hello-lucy": HelloLucy,
          "hello-tom": HelloTom,
          "hello-kitty": HelloKitty,
        },
      });
</script>

在这里插入图片描述
测试在其他组件中使用局部组件:

<div id="app">
 	<test-com></test-com>
</div>
Vue.component("test-com", {
        template: "<div><Hello-Kitty></Hello-Kitty></div>",
});

在这里插入图片描述

tips:
如何全局注册组件

  • Vue.component(“组件名称“,{});

组件的注意事项

  • 组件参数的data值必须是函数同时这个函数要求返回一个对象
  • 组件模板必须是单个根元素
  • 组件模板的内容可以是模板字符串

组件命名有什么规定?

  • 短横线
  • 驼峰命名

如何定义局部组件?

  • 在Vue中通过 components
  • 注意全局的不带S局部的带s

三、Vue调试工具

3.1 下载安装调试工具

以Chrome举例,从Chrome商店下载即可(需要科学上网)在这里插入图片描述

3.2 使用遇到的问题

如果下载后,图标是灰色的,点击后弹出这个提示:
在这里插入图片描述

3.3 问题解决方案

Chrome->更多工具->拓展程序->Vue.js devtools->详细信息->下拉勾选开启允许访问文件网址->重启浏览器即可。

在这里插入图片描述
在这里插入图片描述
在浏览器的控制台中即可使用Vue的工具
在这里插入图片描述
到这里Vue工具的使用以及问题就介绍完毕了,开始使用工具开发吧!

四、组件间数据交互

父组件可以向子组件传递数据,子组件也可以向父组件传递数据,兄弟组件也可以进行数据的交互。

4.1 父组件向子组件传值

1.组件内部通过props接收传递过来的值
在这里插入图片描述

写死绑定

<body>
    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item title="来自父组件的值"></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
            父组件向子组件传递值
        */
        Vue.component('menu-item', {
            props: ['title'],
            data: function () {
                return {
                    msg: '子组件本身的数据'
                }
            },
            template: '<div>{{msg + "--------->" + title}}</div>'
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中的内容',
            }
        });
    </script>
</body>

父组件通过属性传递给子组件数据,子组件通过props来接受父组件传递过来的值。

在这里插入图片描述

2.父组件通过属性将值传递给子组件
在这里插入图片描述

动态绑定

<body>
    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item title="来自父组件的值"></menu-item>
        <menu-item :title="ptitile"></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
            父组件向子组件传递值
        */ 
        Vue.component('menu-item', {
            props: ['title'],
            data: function () {
                return {
                    msg: '子组件本身的数据'
                }
            },
            template: '<div>{{msg + "--------->" + title}}</div>'
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中的内容',
                ptitile:'动态绑定属性'
            }
        });
    </script>
</body>

在这里插入图片描述
传递多个值

<body>
    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item title="来自父组件的值"></menu-item>
        <menu-item :title="ptitile" 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: '父组件中的内容',
                ptitile:'动态绑定属性'
            }
        });
    </script>
</body>

在这里插入图片描述
3. props属性名规则

  • 在 props中使用驼峰形式
  • 模板中需要使用短横线的形式,字符串形式的模板中没有这个限制

在这里插入图片描述
在 props中使用驼峰形式,模板中需要使用短横线的形式

<body>
    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item :menuTitle="ptitile"></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        Vue.component('menu-item', {
            props: ['menuTitle'],
            template: '<div>{{menuTitle}}</div>'
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中的内容',
                ptitile: '动态绑定属性'
            }
        });
    </script>
</body>

在这里插入图片描述
在这里插入图片描述
修改为短横线形式

 <menu-item :menu-title="ptitile"></menu-item>

在这里插入图片描述
字符串形式的模板中没有这个限制

<body>
    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item :menu-title="ptitile"></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        Vue.component('third-com', {
            props: ['testTitle'],
            template: '<div>{{testTitle}}</div>'
        });
        Vue.component('menu-item', {
            props: ['menuTitle'],
            template: '<div>{{menuTitle}}<third-com testTitle="hello"></third-com></div>'
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中的内容',
                ptitile: '动态绑定属性'
            }
        });
    </script>
</body>

在这里插入图片描述
4.props属性值类型

  • 字符串 String
  • 数值 Number
  • 布尔值Boo|ean
  • 数组Array
  • 对象 Object
<body>
    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item :pstr='pstr' :pnum='12'></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        Vue.component('menu-item', {
            props: ['pstr', 'pnum'],
            template: `
                <div>
                    <div>{{pstr}}</div>    
                    <div>{{typeof pnum}}</div>        
                </div>
            `
        })
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中的内容',
                pstr: 'hello',
            }
        });
    </script>
</body>

在这里插入图片描述

:与不加的区别,加了就是原样展示,如果不加默认就是字符串。

<menu-item :pstr='pstr' pnum='12'></menu-item>

在这里插入图片描述

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

在这里插入图片描述

注:书写vue表达式的时候一定不要多加空格!!!!

小结:

  • 父组件如何向子组件传值?
  • 父组件发送的形式是以属性的形式绑定值到子组件身上。
  • 然后子组件用属性 props接收

4.2 子组件向父组件传值

1.子组件通过自定义事件向父组件传递信息
在这里插入图片描述
2.父组件监听子组件的事件
在这里插入图片描述

<body>
    <div id="app">
        <div :style='{fontSize:fontSize + "px"}'>{{pmsg}}</div>
        <menu-item :parr='parr' @enlarge-text='handle'></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
            子组件向父组件传递值-基本用法:
            props传递数据原则:单向数据流 就是只允许 父组件 像子组件 传递数据 而不允许子组件之间操作props中的数据 不推荐这样做
        */
        Vue.component('menu-item', {
            props: ['parr'],
            template: ` 
            <div>
                <ul>
                    <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
                </ul>
                <button @click='parr.push("lemon")'>点击增加lemon</button>
                <button @click='$emit("enlarge-text")'>扩大父组件中的字体大小</button>
            </div>
           `
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中的内容',
                parr: ['apple', 'orange', 'banana'],
                fontSize: 10,
            },
            methods: {
                handle: function () {
                    //扩大字体大小
                    this.fontSize += 5;
                }
            }
        });
    </script>
</body>

在这里插入图片描述
3.子组件通过自定义事件向父组件传递信息
在这里插入图片描述
4.父组件监听子组件的事件
在这里插入图片描述

<body>
    <div id="app">
        <div :style='{fontSize:fontSize + "px"}'>{{pmsg}}</div>
        <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
            子组件向父组件传递值-基本用法:
            props传递数据原则:单向数据流 就是只允许 父组件 像子组件 传递数据 而不允许子组件之间操作props中的数据 不推荐这样做
        */
        Vue.component('menu-item', {
            props: ['parr'],
            template:
                ` <div>
                <ul>
                    <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
                </ul>
                <button @click='parr.push("lemon")'>点击增加lemon</button>
                <button @click='$emit("enlarge-text",5)'>扩大父组件中的字体大小</button>
                <button @click='$emit("enlarge-text",10)'>扩大父组件中的字体大小</button>
            </div>
           `
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中的内容',
                parr: ['apple', 'orange', 'banana'],
                fontSize: 10,
            },
            methods: {
                handle: function (val) {
                    //扩大字体大小
                    this.fontSize += val;
                }
            }
        });
    </script>
</body>

在这里插入图片描述

4.3 非父子组件间传值

1.单独的事件中心管理组件间的通信
在这里插入图片描述
在这里插入图片描述
2.(重点)监听事件与销毁事件
在这里插入图片描述

3.(重点)触发事件
在这里插入图片描述

<body>
    <div id="app">
        <div>{{pmsg}}</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 () {
                //监听事件 var 兄弟组件传递过来的
                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 () {
                //监听事件 var 兄弟组件传递过来的
                hub.$on('jerry-event', (val) => {
                    this.num += val;
                })
            }
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件',
            },
            methods: {
            }
        });
    </script>
</body>

在这里插入图片描述
添加销毁事件

<body>
    <div id="app">
        <div>{{pmsg}}</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 () {
                //监听事件 var 兄弟组件传递过来的
                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 () {
                //监听事件 var 兄弟组件传递过来的
                hub.$on('jerry-event', (val) => {
                    this.num += val;
                })
            }
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件',
            },
            methods: {
                handle: function () {
                    hub.$off('tom-event');
                    hub.$off('jerry-event');
                }
            }
        });
    </script>
</body>

在这里插入图片描述

tips:
父组件如何向子组件传值?

  • 通过 props属性值类型
  • 可以传递静态值
  • 也可以传递动态值动态值可以是字符串数字布尔值数组对象

子组件如何向父组件传递数据?

  • 子组件用$emit()触发事件
  • $emit()第一个参数为自定义的事件名称第二个参数为需要传递的数据
  • 父组件通过事件监听监听子组件传递过来的数据

如何传递参数?

  • $emit()第一个参数为自定义的事件名称,第二个参数为需要传递的数据

兄弟之间如何通讯?

  • 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
    提供事件中心 var hub= new vue();
  • 传递数据方,通过一个事件触发hub. $emit(方法名,传递的数据)
  • 接收数据方,通过 mounted(){}钩子中触发hub$on()方法名
  • 销毁事件通过hub$off()方法名销毁之后无法进行传递数据

五、组件插槽

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

在这里插入图片描述

5.2 组件插槽基本用法

1.插槽位置
在这里插入图片描述2.插槽内容
在这里插入图片描述
插槽基本用法

<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>

在这里插入图片描述

5.3 具名插槽用法

有名字的插槽,根据名称进行匹配,如果没有匹配到则放到默认的插槽中。

1.插槽定义
在这里插入图片描述

2.插槽内容
在这里插入图片描述

<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>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

希望插槽中填充多个标签的话可以使用template只是用来包裹作用,实际展示的时候没有这个元素。template属于Vue的API用来包裹多个标签。

5.4 作用域插槽

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

1.插槽定义
在这里插入图片描述
2.插槽内容
在这里插入图片描述

<style>
        .current {
            color: orange;
        }
</style>
<body>
    <div id="app">
        <fruit-list :list='list'>
            <template slot-scope='slotProps'>
                <strong v-if='slotProps.info.id==2' 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>

在这里插入图片描述

tips:
插槽的作用是什么?

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

插槽如何使用?

  • 提供一个插槽的位置
  • 插槽的内容

具名插槽和匿名插槽的区别?

  • 具名插槽有名字
  • 匿名插槽没有名字

具名插槽用法具有名字的插槽

  • 使用<slot>中的"name"属性绑定元素
  • 通过slot属性来指定,这个slot的值必须和下面slot组件得name值对应上如果没有匹配到则放到匿名的插槽中

作用域插槽使用场景

  • 父组件对子组件加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致

作用域插槽的使用

  • 子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg=“xxx”
  • 插槽可以提供—个默认内容,如果如果父组件没有为这个插槽提供了內容会显示默认的内容。如果父组件为这个插槽提供了内容,则默认的内容会被替换掉

六、基于组件的案例

在这里插入图片描述

1.按照组件化方式实现业务需求

根据业务功能进行组件化划分
①标题组件(展示文本)
②列表组件(列表展示、商品数量变更、商品删除)
③结算组件(计算商品总额)

1.功能实现步骤

  • 实现整体布局和样式效果
  • 划分独立的功能组件
  • 组合所有的子组件形成整体结构
  • 逐个实现各个组件功能
    标题组件
    列表组件
    结算组件
<!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">
            <my-cart></my-cart>
        </div>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        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: {
                del: function (id, $event) {
                    //把id值传给父组件
                    this.$emit('cart-del', id);
                },
                changeNum: function (id, event) {
                    this.$emit('change-num', {
                        id: id,
                        type: 'change',
                        num: event.target.value
                    })
                },
                sub: function (id) {
                    this.$emit('change-num', {
                        id: id,
                        type: 'sub'
                    })
                },
                add: function (id) {
                    this.$emit('change-num', {
                        id: id,
                        type: 'add'
                    })
                }
            }
        }
        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;
                }
            }
        }
        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  :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: {
                delCart: function (id) {
                    //根据id删除list中对应的数据
                    var index = this.list.findIndex(item => {
                        return item.id == id;
                    });
                    // 2、根据索引删除对应数据
                    this.list.splice(index, 1);
                },
                changeNum: function (val) {
                    //分为三种情况,输入域的变更、加号变更、减号变更、
                    if (val.type == 'change') {
                        //根据子组件传递过来的数据,更新list中对应的数据
                        this.list.some(item => {
                            if (item.id == val.id) {
                                item.num = val.num;
                                //终止遍历
                                return true;
                            }
                        });
                    } else if (val.type == 'sub') {
                        //根据子组件传递过来的数据,更新list中对应的数据
                        this.list.some(item => {
                            if (item.id == val.id && item.num != 0) {
                                item.num -= 1;
                                //终止遍历
                                return true;
                            }
                        });
                    } else if (val.type == 'add') {
                        //根据子组件传递过来的数据,更新list中对应的数据
                        this.list.some(item => {
                            if (item.id == val.id && item.num < 10) {
                                item.num += 1;
                                //终止遍历
                                return true;
                            }
                        });
                    }
                }
            }
        })
        var vm = new Vue({
            el: '#app',
            data: {

            }
        });

    </script>
</body>

</html>

在这里插入图片描述

根据什么功能化分组件?

  • 根据每个业务功能化分组件

如何把静态页面实现组件化?

  • 定义组件把静态页面转换成组件化模式
  • 把组件渲染到页面上

标题组件实现动态渲染

  • 从父组件把标题数据传递过来即父向子组件传值
  • 把传递过来的数据渲染到页面上

结算功能组件

  • 从父组件把商品列表list数据传递过来即父向子组件传值
  • 把传递过来的数据计算最终价格渲染到页面上

如何实现删除功能?

  • 从父组件把商品列表list数据传递过来即父向子组件传值
  • 把传递过来的数据渲染到页面上
  • 点击删除按钮的时候删除对应的数据

组件更新商品功能

  • 将输入框中的默认数据动态渲染出来
  • 输入框失去焦点的时候更改商品的数量
  • 子组件中不推荐操作数据把这些数据传递给父组件让父组件处理这些数据
  • 父组件中接收子组件传递过来的数据并处理

更新功能思路

  • 子组件通过一个标识符来标记对用的用户点击 + − +- +或者输入框输入的内容
  • 父组件拿到标识符更新对应的组件
;