组件注册

Vue.component(组件名称,{
    data:组件数据,
    template:组件模板内容
})

简单用法

Vue.component('button-counter', {
    data: function () {
        return {
            count: 0
        }
    },
    template: '<button @click="add">点击了{{count}}次</button>',
    methods: {
        add() {
            this.count++
        }
    },
})
<div id="app">
    <button-counter><button-counter>
</div>

dbd73b9c-bbcf-4746-a292-102c72f8a4db

组件可以多次复用,并且每个组件之间互不影响。

组件注意事项

  1. 定义组件时data属性为一个函数,函数内返回一个对象。这个对象中包含数据

    data: function () {
        return {
            count: 0
        }
    }
  2. 组件模板内容必须是单个根元素

    template: '<button @click="add">点击了{{count}}次<p>p标签</p></button>',
  3. 组件模板内容可以是模板字符串

  4. 组件命名方式

    • 横线

      Vue.component('button-counter', {
          data: function () {
              return {
                  count: 0
              }
          },
          template: '',
      })
    • 驼峰式

      Vue.component('HelloWrold', {
          data: function () {
              return {
                  count: 0
              }
          },
          template: '',
      })

      驼峰式命名只能在模板中使用,不能在HTML中使用,如果使用需转换成横线式。

      <hello-wrold></hello-wrold>

局部组件注册方式

var ComponentA = {}
var vm = new Vue({
    el: '#app',
    components: {
        'component-a': ComponentA
    }
});

image-20201101205711037

<body>
    <div id="app">
        <component-a></component-a>
        <component-b></component-b>
        <component-c></component-c>
    </div>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript">
        var ComponentA = {
            data: function () {
                return {
                    msg: 'HelloWorld'
                }
            },
            template: '<div>{{msg}}</div>'
        }
        var ComponentB = {
            data: function () {
                return {
                    msg: 'HelloTOM'
                }
            },
            template: '<div>{{msg}}</div>'
        }
        var ComponentC = {
            data: function () {
                return {
                    msg: 'HelloVue'
                }
            },
            template: '<div>{{msg}}</div>'
        }
        var vm = new Vue({
            el: '#app',
            components: {
                'component-a': ComponentA,
                'component-b': ComponentB,
                'component-c': ComponentC
            }

        });
    </script>
</body>

Vue调试工具

可以直接使用谷歌商城进行安装:https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd/related?hl=zh

也可以参考官方自行构建后安装:https://github.com/vuejs/vue-devtools

如果遇到Vue.js not detected问题可能导致的原因:

  1. 没有开启权限

    image-20201102082049749

    image-20201102082107655

组件间的交互

父组件向子组件传值

在组建中定义props属性,值为一个数组,用于接受来自父组件的值

Vue.component('menu-item', {
    props: ['title'],
    data: function () {
        return {
            msg: '子组件'
        }
    },
    template: '<div>{{msg}}---{{title}}</div>'
})

在使用时可以有两种写法,

  1. 静态传入

    <menu-item title='来自父组件的值'></menu-item>
  2. 通过属性绑定

    <menu-item :title='ptitle'></menu-item>
    var vm = new Vue({
        el: '#app',
        data: {
            pmsg: "父组件",
            ptitle: '来自父组件的值'
        }
    });

image-20201102083255090

<body>
    <div id="app">
        <menu-item title='来自父组件的值'></menu-item>
        <menu-item :title='ptitle'></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: "父组件",
                ptitle: '来自父组件的值'
            }
        });
    </script>
</body>

props属性名

规则

  1. props使用驼峰式传值,那么模板中需要使用短横线的形式

    Vue.component('menu-item', {
        props: ['TitleMsg'],
        template: '<div>{{TitleMsg}}</div>'
    })
    <menu-item title-msg='nihao'></menu-item>

    在HTML中大小写是不敏感的。

  2. 字符串模板中没有这个限制

    Vue.component('menu-item', {
        props: ['TitleMsg'],
        template: '<div><menu-item TitleMsg="nihao"></menu-item></div>'
    })

属性值

以下类型均可以传递

  1. 字符串

  2. 数值类型

    如果通过v-bind绑定,那么数值为数字类型

  3. 布尔类型

    如果通过v-bind绑定,那么数值为布尔类型

  4. 数组类型

  5. 对象

子组件向父组件传值

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

    <menu-item :parr='parr' @enlarge-text='handle'></menu-item>

    自定义事件名为enlarge-text,触发的函数名为handle

    Vue.component('menu-item', {
        props: ['parr'],
        template: `
    <div>
    <ul>
    <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
    </ul>
    <button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button>
    </div>
    `
    });

    对按钮进行单击事件绑定,绑定的事件为enlarge-text$emit为固定用法。

  2. 父组件通过监听子组件的事件

    var vm = new Vue({
        el: '#app',
        data: {
            pmsg: '父组件中内容',
            parr: ['apple', 'orange', 'banana'],
            fontSize: 10
        },
        methods: {
            handle: function () {
                // 扩大字体大小
                this.fontSize += 5;
            }
        }
    });

子组件向父组件传值-携带参数

定义组件时可以通过$emit的第二个参数进行传值。组件调用时通过$event来接受。

使用组件

<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>

定义组件

Vue.component('menu-item', {
    props: ['parr'],
    template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button>
</div>
`
});

父组件接受值

methods: {
    handle: function (val) {
        // 扩大字体大小
        this.fontSize += val;
    }
}

非父子组件间传值

  1. 单独的事件中心管理组件间的通信

    var hub = new Vue()
  2. 监听事件与销毁事件

    hub.$on('tom-event', (val) => {
        this.num += val
    })
    hub.$off('tom-event')
  3. 触发事件

    hub.$emit('jerry-event', 1)
<body>
    <div id="app">
        <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', 1)
                }
            },
            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', 2)
                }
            },
            mounted: function () {
                hub.$on('jerry-event', (val) => {
                    this.num += val
                })
            },
        });
        var vm = new Vue({
            el: '#app',
        });
    </script>
</body>

组件插槽

父组件向子组件传递内容(模板内容)

image-20201102091617610

  1. 定义插槽

    Vue.component('alert-box', {
        template: `
    <div>
    <strong>Error:<strong>
    <slot>默认内容</slot>    
    </div>
    `
    })

    定义插槽使用slot标签,如果没有传值,那么显示定义时的默认内容。

  2. 使用插槽

    <alert-box>有bug发生</alert-box>
    <alert-box></alert-box>

    image-20201102092132259

    当传递值时显示值的内容,否则显示默认内容(如果有)

具名插槽

即对slot标签添加name属性,表示当前插槽的名称。

Vue.component('base-layout', {
    template: `
<div>
<header>
<slot name='header'></slot>    
</header>  
<main>
<slot></slot>    
</main>  
<footer>
<slot name='footer'></slot>    
</footer>  
</div>
`
})

调用时,通过slot属性指定填充插槽名。没有指定的则添加到默认插槽

<base-layout>
    <p slot="header">标题</p>
    <p>主要内容</p>
    <p slot="footer">尾部</p>
</base-layout>

image-20201102092621027

关于调用也可以使用如下方法

<base-layout>
    <template slot="header">
        <p slot="header">标题</p>
    </template>
    <template>
        <p>主要内容</p>
    </template>
    <template slot="footer">
        <p slot="footer">尾部</p>
    </template>
</base-layout>

作用域插槽

父组件对子组件的内容进行加工处理。

  1. 插槽定义

    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'
            }]
        }
    });
  2. 插槽内容

    <fruit-list :list='list'>
        <template v-slot='slotProps'>
            <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
            <span v-else>{{slotProps.info.name}}</span>
        </template>
    </fruit-list>

值的传递:

  1. 插槽中定义了属性infoitem(每次遍历的结果)

  2. 使用v-slot指令接受,其值可以为任意喜欢的名字。

    <template v-slot='slotProps'></template>
  3. slotProps实际上就是info的值。

    <fruit-list :list='list'>
        <template v-slot='slotProps'>
    
            <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
            <span v-else>{{slotProps.info.name}}</span>
            <span>{{slotProps}}</span>
        </template>
    </fruit-list>

    image-20201102100458309

购物车案例

组件化重构

<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 = {
            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>
`
        }
        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
            }
        });
        var vm = new Vue({
            el: '#app',
            data: {

            }
        });

    </script>
</body>

标题和总价

标题的实现很简单,为了演示功能,定义一个属性记录用户名,并传递给header

Vue.component('my-cart', {
    data: function () {
        return {
            uname: '张三',
        }
    },
    template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
          <cart-list></cart-list>
          <cart-total></cart-total>
        </div>
      `,
}

将值显示出来

var CartTitle = {
    props: ['uname'],
    template: `
<div class="title">{{uname}}的商品</div>
`
}

对于总价,提供一个数据用于记录购物车的列表。并将值传递给total

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>
`,
}

在CartTotal组件中根据列表中的内容计算总价。

var CartTotal = {
    props: ['list'],
    template: `
<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>
`,
    computed: {
        total: function () {
            // 计算总价
            var sum = 0
            console.log(this.list);
            this.list.forEach(item => {
                sum += item.price * item.num
            })
            return sum
        }
    }
}

列表展示和删除

列表展示只需要在父组件中将数组列表传递给子组件,子组件在进行循环遍历即可。

Vue.component('my-cart', {
    template: `
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list :list='list'></cart-list>
<cart-total :list='list'></cart-total>
</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"'>×</div>
</div>
</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) {
          // 通知父组件进行删除
          this.$emit('cart-del', id)
        }
      },
    }

父级中定义删除的实际操作,并监听事件。

Vue.component('my-cart', {
    methods: {
        delCart: function (id) {
            // 根据ID删除list中的数据
            // 1. 找到索引
            var index = this.list.findIndex(item => {
                return item.id == id
            })
            // 2. 根据索引删除
            this.list.splice(index, 1)
        }
    },
    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>
`,
});

商品数量的变更

商品数量变更同样涉及到修改列表内容,同样的交给父组件去修改。

在子组件中定义自定义事件并对input标签绑定失去焦点时触发这个自定义事件。

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: {
        del: function (id) {
            // 通知父组件进行删除
            this.$emit('cart-del', id)
        },
        changeNum: function (id, event) {
            // 修改商品数量
            // 通知父组件进行删除
            let value = event.target.value
            this.$emit('change-num', { id: id, num: value })
        }
    },
}

自定义事件中,将商品id和修改的值传递给父组件。

Vue.component('my-cart', {
      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>
      `,
      methods: {
        changeNum: function (val) {
          console.log(val);
          // 根据子组件传递的数据更新list中对应的数据
          this.list.some(item => {
            if (item.id == val.id) {
              item.num = val.num
              // 终止
              return true
            }
          })
        }
      },
});

父组件监听子组件定义的自定义方法,并触发父组件定义的自定义方法。

按钮操作

按钮操作同样是需要利用父组件来修改数据。为了复用changeNum事件,将其在添加一个值type用于记录当前的操作。子组件修改。

changeNum: function (id, event) {
    // 修改商品数量
    // 通知父组件进行删除
    let value = event.target.value
    this.$emit('change-num', {
        id: id,
        num: value,
        type: 'change'
    })
},
    sub: function (id) {
        this.$emit('change-num', {
            id: id,
            type: 'sub'
        })
    },
        add: function (id) {
            this.$emit('change-num', {
                id: id,
                type: 'add'
            })
        },

父组件的修改

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 -= 1
                // 终止
                return true
            }
        })
    } else {
        // 根据子组件传递的数据更新list中对应的数据
        this.list.some(item => {
            if (item.id == val.id) {
                item.num += 1
                // 终止
                return true
            }
        })
    }