Vue2 基础三组件化开发

代码下载

组件 (Component) 是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码。

组件注册

全局注册:

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

        // 全局组件
        Vue.component('button-counter', {
            data: function() {
                return { count: 0 };
            },
            template: `<button @click="handle" v-text="'点击了(' + count + ')次'"></button>`,
            methods: {
                handle: function() {
                    console.log(this.count);
                    this.count++;
                }
            }
        });

局部注册:只能在当前注册它的vue实例中使用

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

            },
            // 局部组件
            components: {
                'myComponent': {
                    data: function() {
                        return { msg: 'hello' };
                    },
                    template: `<div v-text='msg'></div>`
                }
            }
        });

组件使用:

        <h3>全局组件</h3>
        <button-counter></button-counter>

        <h3>局部组件</h3>
        <my-component></my-component>

注意事项:

  • data必须是一个函数。
  • 组件模板内容必须是单个跟元素,组件模板内容可以是模板字符串,模板字符串需要浏览器提供支持(ES6语法)。
  • 组件命名方式有短横线方式(Vue.component('my-component', { /* ... */ })),驼峰方式(Vue.component('myComponent', { /* ... */ }))但是必须使用短横线的方式使用组件。

Vue调试工具vue-devtools

安装

  • 打开终端,进入指定目录,执行mkdir 文件夹名命令创建文件夹。
  • 执行cd 文件夹名进入刚创建的文件夹目录,执行npm install vue-devtools命令开始下载(前提:安装node.js)。
  • 下载完成后,进入该文件下的 node_modules 文件,进入 vue-devtools 此文件夹,将其中vender 文件夹下的 manifest.json文件 进行编辑,修改 persistent 为 true 保存。
  • 打开浏览器找到扩展程序(以谷歌为例),并勾选开发者模式,点击出现的“加载扩展程序”按钮,最后选择vender 文件,重启浏览器就可以使用了。

编写如下代码,谷歌浏览器打开,打开开发者工具,选择“vue”,即可使用了:

<body>
    <div class="main">
        <h2>调试工具(vue-devTools)</h2>
        <div id="app">
            <div v-text="root"></div>
            <first-com></first-com>
        </div>
    </div>

    <script src="../js/vue.js"></script>
    <script>
        Vue.component('first-com', {
            data: function() {
                return { first: '一级组件' }
            },
            template: `
                <div>
                    <div v-text='first'></div>
                    <second-com></second-com>
                </div>
            `
        });
        Vue.component('second-com', {
            data: function() {
                return { second: '二级组件' }
            },
            template: `
                <div v-text='second'></div>
            `
        });
        let vm = new Vue({
            el: '#app',
            data: {
                root: '顶层根组件'
            }
        });
    </script>
</body>

请添加图片描述

组件间数据交互

父组件向子组件传值

  • 父组件发送的形式是以属性的形式绑定值到子组件身上。
  • 然后子组件用属性props接收。
  • 在props中使用驼峰形式,模板中需要使用短横线的形式,字符串形式的模板中没有这个限制。
<body>
    <div class="main" id="app">
        <h2>父组件向子组件传值</h2>
        <son-com :parent-title="title" :parent-content="content"></son-com>
    </div>

    <script src="../js/vue.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                title: '标题',
                content: '内容'
            },
            components: {
                'son-com': {
                    props: ['parentTitle', 'parentContent'],
                    data: function() {
                        return { info: '我的信息' }
                    },
                    template: `<div v-text="'title: ' + parentTitle + 'content: ' + parentContent + 'info: ' + info"></div>`
                }
            }
        });
    </script>
</body>

props属性值类型有字符串 String、数值 Number、布尔值 Boolean、数组 Array、对象 Object;通过v-bind传递数据类型为其自身类型,直接属性赋值则为字符类型。

        <type-com :pstr="123" :pnum="123" :pbool="true" :parr="[1, 2, 3]" :pobj="{ a: '123', b: '456' }"></type-com>
        <type-com pstr="123" pnum="123" pbool="true" parr="[1, 2, 3]" pobj="{ a: '123', b: '456' }"></type-com>
        
            components: {
                'type-com': {
                    props: ['pstr', 'pnum', 'pbool', 'parr', 'pobj'],
                    template: `<div>
                        <div v-text='typeof pstr'></div>
                        <div v-text='typeof pnum'></div>
                        <div v-text='typeof pbool'></div>
                        <div v-text='typeof parr'></div>
                        <div v-text='typeof pobj'></div>
                    </div>`
                }
            }
        // number
        // number
        // boolean
        // object
        // object
        // string
        // string
        // string
        // string
        // string

子组件向父组件传值

  • 子组件用 $emit() 自定义事件,第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
  • 父组件用v-on监听子组件的事件
<body>
    <div class="main" id="app">
        <h2>子组件向父组件传值</h2>
        <div :style="{fontSize: fontSize + 'px'}">父组件内容</div>
        <son-com :data="info" :size="fontSize" @large-text="handle"></son-com>
    </div>

    <script src="../js/vue.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                info: ['萝卜', '白菜', '土豆'],
                fontSize: 18
            },
            components: {
                'son-com': {
                    props: ['data'],
                    data: function() {
                        return { size: '' }
                    },
                    template: `<div>
                        <ul>
                            <li :key='index' v-for='(value, index) in data' v-text='value'></li>
                        </ul>
                        <input type='text' v-model.number='size'>
                        <button @click='$emit("large-text", size)'>增加字号</button>
                    </div>`
                }
            },
            methods: {
                handle: function(v) {
                    console.log('value: ', v);
                    this.fontSize += v;
                    console.log(this.fontSize);
                }
            }
        })
    </script>
</body>

兄弟组件的传值

  • 兄弟组件传递数据需要借助于事件中心,通过事件中心传递数据,提供事件中心 var hub = new Vue()
  • 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
  • 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
  • 销毁事件通过hub.$off()方法名,销毁之后无法进行传递数据
<body>
    <div class="main" id="app">
        <h2>兄弟组件传值</h2>
        <div>
            <one-com></one-com>
            <two-com></two-com>
            <button @click="destroy">销毁</button>
        </div>
    </div>

    <script src="../js/vue.js"></script>
    <script>
        // 事件中心
        let hub = new Vue();

        // 组件one
        Vue.component('one-com', {
            data: function() {
                return { count: 0 };
            },
            template: `<div>
                <div v-text='"one: " + count'></div>
                <button @click='handle'>点击</button>
            </div>`,
            methods: {
                handle: function() {
                    // 触发事件
                    hub.$emit('one-event', 1);
                }
            },
            mounted: function() {
                // 监听事件
                hub.$on('two-event', (v) => {
                    this.count += v;
                });
            }
        });

        // 组件two
        Vue.component('two-com', {
            data: function() {
                return { count: 0 };
            },
            template: `<div>
                <div v-text='"two: " + count'></div>
                <button @click='handle'>点击</button>
            </div>`,
            methods: {
                handle: function() {
                    hub.$emit('two-event', 2);
                }
            },
            mounted: function() {
                hub.$on('one-event', (v) => {
                    this.count += v;
                });
            }
        });
        let vm = new Vue({
            el: '#app',
            data: {

            },
            methods: {
                destroy: function() {
                    hub.$off('one-event');
                    hub.$off('two-event');
                }
            }
        });
    </script>
</body>

组件插槽

插槽可以为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。举例来说,这里有一个 组件,可以像这样使用:

<FancyButton>
  Click me! <!-- 插槽内容 -->
</FancyButton>

<FancyButton> 的模板是这样的:

<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
image

最终渲染出的 DOM 是这样:

<button class="fancy-btn">Click me!</button>

默认内容

在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如模板是这样的一个组件:

<button type="submit">
  <slot>
    Submit <!-- 默认内容 -->
  </slot>
</button>

具名插槽

组件中包含多个插槽出口是很有用,对于这种场景,<slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID。举例来说,在一个 <BaseLayout> 组件中以确定每一处要渲染的内容:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name<slot> 出口会隐式地命名为“default”。

在父组件中需要一种方式将多个插槽内容传入到各自目标插槽的出口,此时就需要用到具名插槽了。要为具名插槽传入内容,需要使用一个含 v-slot 指令的 <template> 元素,并将目标插槽的名字传给该指令,v-slot 有对应的简写 #,因此 <template v-slot:header> 可以简写为 <template #header>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”:
image

        <base-layout>
            <template v-slot:header>
                <h4>页眉</h4>
            </template>
            <template v-slot:default>
                <p>主体1</p>
                <p>主体2</p>
            </template>
            <template #footer>
                <p>页脚</p>
            </template>
        </base-layout>

当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。所以上面也可以写成:

        <base-layout>
            <template v-slot:header>
                <h4>页眉</h4>
            </template>
            <p>主体1</p>
            <p>主体2</p>
            <template #footer>
                <p>页脚</p>
            </template>
        </base-layout>

也可以通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上,如果没有匹配到则放到匿名的插槽中:

        <base-layout>
            <h4 slot="header">页眉</h4>
            <p>主体1</p>
            <p>主体2</p>
            <p slot="footer">页脚</p>
        </base-layout>

作用域插槽

在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,需要一种方法来让子组件在渲染时将一部分数据提供给插槽。确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes

<div>
    <slot :text='"你好"' v-bind:count='1'></slot>
</div>

当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。先展示默认插槽如何接受 props,通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:

        <my-component v-slot="data">
            {{data.text}}
            {{data.count}}
        </my-component>

子组件传入插槽的 props 作为了 v-slot 指令的值,可以在插槽内的表达式中访问。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name=“data”。当使用缩写时是这样:

        <one-component>
            <template #header="data">
                {{data}}
            </template>
            <template #default="data">
                {{data}}
            </template>
            <template #footer="data">
                {{data}}
            </template>
        </one-component>

向具名插槽中传入 props:

                    template: `<div>
                        <header>
                            <slot name='header' :msg='"早上好"' v-bind:uname='"张三"''></slot>
                        </header>
                        <main>
                            <slot :msg='"中午好"' v-bind:uname='"李四"'></slot>
                        </main>
                        <footer>
                            <slot name='footer' v-bind:msg='"晚上好"' :uname='"王五"'></slot>
                        </footer>
                    </div>`

同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 <template> 标签。尝试直接为组件添加 v-slot 指令将导致编译错误。这是为了避免因默认插槽的 props 的作用域而困惑:

        <!-- 该模板无法编译 -->
        <one-component>
            <template #header="data">
                {{ data }}
            </template>
            <p v-slot="data">{{ data }}</p>
            <template #footer="data">
                {{ data }}
            </template>
        </one-component>

组件化案例-购物车

略,具体见代码实现:

<body>
    <div class="main" id="app">
        <h2>组件化案例-购物车</h2>
        <div class="container">
            <my-cart></my-cart>
        </div>
    </div>

    <script src="../js/vue.js"></script>
    <script>
        Vue.component('MyCart', {
            data: function() {
                return {
                    title: '我的商品',
                    list: [{
                        id: 1,
                        name: 'TCL彩电',
                        price: 100,
                        num: 1,
                        img: 'img/a.jpg'
                    },{
                        id: 2,
                        name: '机顶盒',
                        price: 200,
                        num: 1,
                        img: 'img/b.jpg'
                    },{
                        id: 3,
                        name: '海尔冰箱',
                        price: 300,
                        num: 1,
                        img: 'img/c.jpg'
                    },{
                        id: 4,
                        name: '小米手机',
                        price: 400,
                        num: 1,
                        img: 'img/d.jpg'
                    },{
                        id: 5,
                        name: 'PPTV电视',
                        price: 500,
                        num: 2,
                        img: 'img/e.jpg'
                    }]
                }
            },
            template: `
                <div class="cart">
                    <cart-title :title='title'></cart-title>
                    <cart-list :list='list' @cart-del='deleteCart' @cart-change='changCartNum'></cart-list>
                    <cart-total :list='list'></cart-total>
                </div>
            `,
            methods: {
                deleteCart: function(i) {
                    this.list.splice(i, 1);
                },
                changCartNum: function(i, t, v) {
                    let item = this.list[i];
                    if (t === 'sub') {
                        if (item.num <= 0) return;
                        item.num--;
                    } else if (t === 'add') {
                        item.num++;
                    } else {
                        item.num = v;
                    }
                }
            },
            components: {
                'CartTitle': {
                    props: ['title'],
                    template: `
                        <div class="title" v-text='title'></div>
                    `
                },
                'CartList': {
                    props: ['list'],
                    template: `
                        <div>
                            <div class="item" :key="v.id" v-for="(v, i) in list">
                                <img :src="'../' + v.img" alt="">
                                <div class="name" v-text="v.name"></div>
                                <div class="change">
                                    <a href="javascritp:;" @click.prevent='$emit("cart-change", i, "sub")'>-</a>
                                    <input type="text" class="num" :value='v.num' @blur='$emit("cart-change", i, "change", $event.target.value)'>
                                    <a href="javascritp:;" @click.prevent='$emit("cart-change", i, "add")'>+</a>
                                </div>
                                <div class="del" @click='$emit("cart-del", i)'>×</div>
                            </div>
                        </div>
                    `
                },
                'CartTotal': {
                    props: ['list'],
                    template: `
                        <div class="total">
                            <span v-text='"总价:" + totalAmount'></span>
                            <button>结算</button>
                        </div>
                    `,
                    computed: {
                        totalAmount: function() {
                            let amount = 0;
                            for (let index = 0; index < this.list.length; index++) {
                                const element = this.list[index];
                                amount += element.price * element.num;
                            }
                            return amount;
                        }
                    }
                },
            }
        });
        let vm = new Vue({
            el: '#app',
            data: {

            }
        });
    </script>
</body>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值