小案例理解何为 Vue 组件、插槽

1,420 阅读4分钟

前言

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战。恰逢掘金八月更文挑战,想必很多初学 Vue 的小伙伴经常看到一个英文单词 component,不知道如何写组件以及理解 slot 插槽概念的时候犯头痛,在跟着敲完代码之后,心里可能想过组件和插槽居然可以这么配合使用,极大降低了代码的耦合度,轻轻松松实现代码的复用,太啊眉紧了吧!废话少叙,今天希望能够通过这个小案例帮助到大家。加油,共勉!

页面效果展示

today.gif

需求分析

首先,进入到用户管理列表,就获取用户注册的所有信息数据,并通过数据表格动态渲染出来,每一个用户独占一行,并且根据用户 id 号给定固定的序号

其次,表格的最后一列作为操作列,通过按下删除按钮,根据用户 id 后台删除该用户信息以及其他的绑定的数据,删除完毕后,dom 结构随之发生变化,实现自动渲染表格,序号 -- ,指定的行消失,到此彻底完成删除操作。

最后,为了更好的用户操作体验,及时反馈点击删除按钮之后删除操作结果,倘若用户误操作,可点击取消按钮,取消这次删除操作。此外,删除操作完成之后,询问一次用户是否返回主菜单页面。

代码实现

[v-cloak] {
    display: none;
}

这里为什么使用v-cloak 指令设置样式呢?

由于打开页面时就开始动态加载数据表格,当网络较慢或者加载的数据较多时,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码 ,比如经常使用到的 {{}} 。我们可以使用v-cloak 指令来解决屏幕闪动这一问题。

另外,这些样式会在 Vue 实例编译结束时,从绑定的 HTML 元素上被移除。

在简单项目中,使用 v-cloak 指令是解决屏幕闪动的好方法。但是在大型、工程化的项目中(webpackvue-router)只有一个空的 div 元素,元素中的内容是通过路由挂载来实现的,而不使用 v-cloak 指令。

<body>
    <div id="usersTable" v-cloak>
        <my-table id="my-table">
            <my-thead slot="my-thead"></my-thead>
            <my-tbody slot="my-tbody" v-for="(user, index) in users" v-bind:user="user" v-bind:index="index" v-bind:key="index" v-on:remove="delUser(index)"></my-tbody>
        </my-table>
    </div>
</body>

注意: 要为每个组件提供一个 key ,尽管不影响,但是会出现警告信息(要有显式的键):warning:component lists rendered with v-for should have explicit keys

Q:那为什么要为组件提供一个 key ?

  1. 为了给 Vue 一个提示,以便它能跟踪每个节点的身份
  2. 从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性
// myTable 中设置插槽
Vue.component('myTable', {
    template:
        '<table>\
            <slot name="my-thead"></slot >\
            <slot name="my-tbody"></slot>\
        </table>'
});

// 表格头 插槽
Vue.component('myThead', {
    props: ['user'],
    template:
        '<tr>\
            <th>序号</th>\
            <th>uid</th>\
            <th>用户名</th>\
            <th>姓名</th>\
            <th>性别</th>\
            <th>生日</th>\
            <th>年龄</th>\
            <th>手机号码</th>\
            <th>密码</th>\
            <th>是否删除</th>\
        </tr>\
        '
});

// 表格体 插槽
Vue.component('myTbody', {
    props: ['index','user'],
    template:
        '<tr>\
            <td>{{index}}</td>\
            <td>{{user.uid}}</td>\
            <td>{{user.username}}</td>\
            <td>{{user.name}}</td>\
            <td v-if="user.gender">男</td>\
            <td v-else>女</td>\
            <td>{{user.birthday}}</td>\
            <td>{{user.age}}</td>\
            <td>{{user.mobile}}</td>\
            <td>{{user.password}}</td>\
            <td><button id="tableBut" v-on:click="remove">删除</button></td>\
        </tr>\
        ',
    methods: {
        remove: function (object) {
            // 组件内部绑定事件
            this.$emit('remove', object);
        }
    }
});

注意: 定义组件名的时候要使用单引号或者双引号括起来,否则怀疑人生!

prop 属性

prop 属性:类似于一个自定义 attribute 属性且和 HTML 一样大小写不敏感。

  1. props 中的变量名不能以短横线分割命名法命名,否则无法进行数据绑定。
  2. props 中的变量名要么全小写要么以驼峰命名法进行命名。
  3. 相应地,驼峰命名时,v-bind 绑定的属性名以短横线命名法进行命名(推荐都小写);全小写时,就对应写就完事了。

组件名命名【避坑】

组件名以及注册组件(注册组件尽量全使用小写字母):

  1. 当组件名以短横线分割命名时,不允许出现大写,限制都为小写
    • 注册组件时也要使用同名标签不能更改大小写
  2. 当组件名以单驼峰命名法命名时,例如:myCom
    • camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名进行注册组件
      • 例如:my-commy-Com 等不限制大小写,但是标签尽量全使用小写
  3. 当组件名出现双驼峰时,例如:MyCom
    • 也是可以使用其等价的 kebab-case (短横线分隔命名) 命名进行注册组件(例如:使用 my-com
      • 注意:
        • 在 2、3 两种情况下都不能使用同名或者不区分大小写同名注册组件
        • 必须使用等价的命名,否则报错:did you register the component correctly? For recursive components, make sure to provide the "name" option.
  4. 当组件名出现n驼峰时,例如:MyCoMMyCoMpOnEnT...
    • 都要遵循短横线分割命名法进行注册组件(例如:my-co-m, my-co-mp-on-en-t...
  5. 当组件命名不遵循短横线命名法和驼峰命名法时(出现两个以上字母连续大写或者全小写),例如:MYcomMyCOmMYCOMmycom...
    • 只有全小写的情况下,组件才能注册成功,不会报错

Vue 核心代码

var vm = new Vue({
    el: '#usersTable',
    data: {
        users: []
    },
    created: function () {
        $.ajax({
            type: 'POST',
            url: '/user/getAllUsersInfo',
            success: function (data) {
                initUsersInfo(data);
            },
            error: function (error) {
                alert(error)
            }
        });
    },
    methods: {
        delUser: function (index) {
            // 当前序号
            var num = index.valueOf();
            var uid = this.users[num].uid;
            var ans = confirm(`确认要删除 uid => ${uid} 的用户吗?`);
            if(ans === true) {
                this.users.splice(index, 1);
                $.ajax({
                    type: 'POST',
                    url: '/user/delUserByUid',
                    data: {
                        uid: uid
                    },
                    success: function (data) {
                        alert(data.msg);
                        var ifExit = confirm('是否返回主菜单页面?');
                        if(ifExit) {
                            window.location.href = '../index.html';
                        }
                    },
                    error: function (error) {
                        alert(error)
                    }
                });
            }else {
                alert('你取消了删除操作!');
            }
        }
    }
});

function initUsersInfo(data) {
    if(jQuery.isEmptyObject(data)) {
        alert("用户表中空空如也~")
    }else {
        vm.users = data;
    }
}

首先,Vue 数据定义部分,定义了一个存放用户信息的数组,使用vue.jscreated 方法,它是一个生命周期钩子函数,当一个vue实例被生成后会调用这个函数,这个函数的具体功能就是调用后端接口,获取 tb_user 表下所有用户数据,然后实现数据的双向绑定。

其次,就是根据我们之前说过的需求一步一步实现了,要删除用户的 id 是通过组件内部 this.$emit 绑定事件(事件名自定义),但是仅此并不足够,这并不能帮我们完成想要的功能,仅是传递参数的工具方法罢了,真正删除方法是是定义在 Vue 实例的 methods 中的,通过传递 index 序号,this.users[num].uid 通过序号下标拿到用户 id

到此,小案例就此实现了,如果对 component 组件、slot 插槽还抱有疑问的,请移步 Vue.js官网,自行查阅。

结尾

撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。