VUE实现一个列表清单【props 父子组件通信、slot插槽的使用、全局自定义指令的封装、$nextTick解决异步DOM更新、巧用v-model简化父子组件之间的通信、触发事件的事件源event】

简介: VUE实现一个列表清单【props 父子组件通信、slot插槽的使用、全局自定义指令的封装、$nextTick解决异步DOM更新、巧用v-model简化父子组件之间的通信、触发事件的事件源event】

引子

现在决定就走前端的这条道路了,当然更希望 2026 年考公上岸。这周一直在巩固 VUE,在仓库里看见了这个去年暑假学习VUE的时候练习的一个Demo,发现挺不错的,打算写一篇博客。

这个Demo,或许看起来平平无奇,但它深深凹印着VUE的基础篇章:

  • props emit 绘制了一条神秘的密码,实现了父子组件间的暗号交流
  • 开启了slot插槽的大门,使得组件灵活性,复用性更高 ⭐⭐⭐⭐⭐
  • 全局自定义指令的封装
  • 使用$nextTick演示了如何优雅的应对异步DOM更新,感觉就像是有了掌控时间的超能力
  • 巧用v-model,简洁地优化了父子组件之间的通信 ⭐⭐⭐⭐⭐
  • 触发事件的事件源event
  • ref 、$refs 的绑定和使用
  • 原生HTML5 Drag and Drop API 的使用

预览

项目文件结构

-db 数据库的存放位置
 |- index.json 组织和管理数据库中的数据
-node_modules 包含了通过 npm 或 yarn 安装的所有依赖包
-public 这是公共资源目录,其中的文件和内容会被直接复制到构建输出的根目录
 |- favicon.ico 网页的图标,显示在浏览器的标签页上
 |- index.html 这是项目的入口HTML文件,用于加载Vue应用
-src 源代码目录,包含了项目的所有源代码文件
 |- assets 存放所有静态资源文件,如图片、样式文件等
    |- logo.png 项目的Logo图片
-components 存放所有的Vue组件
 |- MyTable.vue 一个自定义的Vue表格组件
 |- MyTag.vue 一个自定义的Vue标签组件
-directives 存放所有的全局Vue指令
 |- globalDirectives.js 全局Vue指令的定义和注册
-store Vuex存储管理,用于管理应用的状态
 |- index.js Vuex存储的入口文件,定义和配置了整个存储系统
-utils 工具函数和实用程序的集合
-App.vue 应用的根组件
-main.js 应用的入口文件,通常在这里初始化Vue应用并挂载到DOM中
-.browserslistrc 定义了Babel和Browserify的浏览器兼容性目标
-.editorconfig 定义了不同编辑器的代码风格和格式
-.eslintrc.js ESLint的配置文件,用于代码质量检查和静态代码分析
-.gitignore Git版本控制系统忽略的文件和目录列表
-babel.config.js Babel的配置文件,用于转译ES6+代码到ES5
-package.json 包含了项目的元信息和依赖包列表
-README.md 项目说明文档
-vue.config.js Vue CLI项目的配置文件,可以进行各种自定义配置
-yarn.lock Yarn依赖包的锁定文件,确保依赖包的版本一致性

数据准备

{
  "goods": [
    {
      "id": 1,
      "picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5/a43f1f52-6850-4cab-837f-b93ff752f16d/ja-1-ep-%E5%B0%8F%E9%87%91%E9%BE%99%E9%BE%99%E5%B9%B4%E6%AC%BE%E5%AE%9E%E6%88%98%E8%B4%BE%E8%8E%AB%E5%85%B0%E7%89%B9%E7%94%B7%E5%AD%90%E7%AF%AE%E7%90%83%E9%9E%8B-ZLQQx9.png",
      "name": "“小金龙”龙年款实战贾莫兰特男子篮球鞋",
      "tag": "篮球鞋"
    },
    {
      "id": 2,
      "picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5,u_126ab356-44d8-4a06-89b4-fcdcc8df0245,c_scale,fl_relative,w_1.0,h_1.0,fl_layer_apply/dcb6b305-9d83-43b8-8b93-d7761ab4d6a8/air-jordan-legacy-312-%E9%9D%92%E9%BE%99%E7%94%B7%E5%AD%90%E8%BF%90%E5%8A%A8%E9%9E%8B-bssr37.png",
      "name": "Air Jordan Legacy 312 “青龙”男子运动鞋",
      "tag": "运动鞋"
    },
    {
      "id": 3,
      "picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5/406bd965-8f3e-4af8-bfe1-c0375cd4fd19/custom-sabrina-1-by-you.png",
      "name": "Sabrina 1 By You 专属定制篮球鞋",
      "tag": "定制"
    },
    {
      "id": 4,
      "picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5/ba3a7f48-77d9-49aa-ad2b-24d0df830bac/lebron-21-ep-%E7%94%B7%E5%AD%90%E7%AF%AE%E7%90%83%E9%9E%8B-wK6QND.png",
      "name": "LeBron XXI EP 男子篮球鞋",
      "tag": "人物系列"
    }
  ]
}

MyTable.vue

  • 可自定义表头和表体,通过插槽的方式进行传入。
  • 支持拖拽排序功能,通过dragstart,drop事件实现元素的拖拽排序功能。
<template>
  <div class="table-case">
    <table class="my-table">
      <thead>
        <tr>
          <slot name="head"></slot>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item, index) in data" :key="index" @dragstart="dragStart(index)" @drop="drop(index)" @dragover.prevent>
          <slot name="body" :item="item" :index="index"></slot>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  name: 'MyTable',
  props: {
    data: {
      type: Array,
      require: true
    }
  },
  data () {
    return {
      draggedIndex: 0,
      endIndex: 0
    }
  },
  methods: {
    dragStart (index) {
      this.draggedIndex = index
    },
    drop (index) {
      this.endIndex = index
      const obj = {
        start: this.draggedIndex,
        end: this.endIndex
      }
      this.$emit('swapThem', obj)
    }
  }
}
</script>

<style lang="less" scoped>
.table-case {
  width: 1000px;
  margin: 50px auto;
  img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
  }

  .my-table {
    width: 100%;
    border-spacing: 0;
    tr {
      transition:  background-color .3s;
      &:hover {
        background-color: rgba(0,0,0,.4);
        color: #fff;
        cursor: pointer;
      }
    }
    img {
      width: 100px;
      height: 100px;
      object-fit: contain;
      vertical-align: middle;
    }
    th {
      background: #000;
      color:#fff;
      border-bottom: 2px solid #ccc;
    }
    td {
      border-bottom: 1px dashed #ccc;
    }
    td,
    th {
      text-align: center;
      padding: 10px;
      transition: all 0.5s;
      &.red {
        color: red;
      }
    }
    .none {
      height: 100px;
      line-height: 100px;
      color: #999;
    }
  }
}
</style>

MyTag.vue

  • 双击标签即可编辑,编辑时显示输入框,失焦或按下 Enter 键即可提交修改。
  • 使用了自定义指令v-focus来实现输入框聚焦功能。
<template>
  <div class="my-tag">
    <input
      v-if="isEdit"
      v-focus
      @blur="isEdit = false"
      ref="inp"
      class="input"
      type="text"
      placeholder="输入标签"
      :value="value"
      @keyup.enter="handleEnter"
    />
    <div
      v-else
      class="text"
      @dblclick="handleClick"
    >
      {{ value }}
    </div>
  </div>
</template>

<script>
export default {
  name: 'MyTag',
  props: {
    value: {
      type: String
    }
  },
  data () {
    return {
      isEdit: false
    }
  },
  methods: {
    handleClick () {
      // 切换显示状态 (Vue是异步Dom更新)
      this.isEdit = true
      // 立刻获取焦点
      // this.$nextTick(() => {
      //   console.log(this.$refs)
      //   this.$refs.inp.focus()
      // })
    },
    handleEnter (e) {
      this.$emit('input', e.target.value)
      this.isEdit = false
    }
  }
}
</script>

<style lang="less">
.my-tag {
  cursor: pointer;
  .input {
    appearance: none;
    outline: none;
    border: 1px solid #ccc;
    width: 100px;
    height: 40px;
    box-sizing: border-box;
    padding: 10px;
    color: #666;
    &::placeholder {
      color: #666;
    }
  }
}
</style>

App.vue

<template>
  <div class="table-case">
    <MyTable :data="goodList" v-on:swapThem="swapThem">
      <template #head>
          <th>编号</th>
          <th>商品名</th>
          <th>商品展示</th>
          <th width="100px"></th>
      </template>
      <!-- 解构也是可以的 #body="{ item, index }" -->
      <template #body="slotProps">
        <td>{{ slotProps.index + 1 }}</td>
          <td>{{ slotProps.item.name }}</td>
          <td>
            <img :src="slotProps.item.picture" />
          </td>
          <td>
          <MyTag v-model="slotProps.item.tag"></MyTag>
      </td>
      </template>
    </MyTable>
  </div>
</template>

<script>
import MyTable from './components/MyTable.vue'
import MyTag from './components/MyTag.vue'
import axios from 'axios'
export default {
  name: 'TableCase',
  components: {
    MyTable,
    MyTag
  },
  data () {
    return {
      goodList: []
    }
  },
  created () {
    this.fetchListItem()
  },
  methods: {
    async fetchListItem () {
      axios.get('http://localhost:3000/goods').then((res) => {
        this.goodList = res.data
      })
    },
    swapThem (value) {
      const { start, end } = value
      // 交换元素位置
      const removedStart = this.goodList.splice(start, 1)[0]
      const removedEnd = this.goodList.splice(end - 1, 1, removedStart)[0]
      this.goodList.splice(start, 0, removedEnd)

      console.log(this.goodList)
    }
  }
}
</script>

<style lang="less" scoped>

</style>

自定义指令

globalDirectives.js

import Vue from 'vue'

// 全局指令 focus
Vue.directive('focus', {
  inserted (el, binding) {
    el.focus()
  }
})

小结

很简单的一个 Demo


目录
相关文章
|
9天前
|
JavaScript 前端开发
Vue的`v-if`和`v-show`用于条件渲染,`v-if`按需编译/销毁DOM,适合不频繁切换且节省初始化资源
【6月更文挑战第25天】Vue的`v-if`和`v-show`用于条件渲染,`v-if`按需编译/销毁DOM,适合不频繁切换且节省初始化资源;`v-show`则始终编译,仅通过CSS切换显示,适合频繁切换,初始渲染成本高但切换性能好。选择取决于元素显示状态的变化频率和初始渲染需求。
16 2
|
2月前
|
JavaScript 前端开发 API
DOM组件如何实现异步更新?
【5月更文挑战第29天】DOM组件如何实现异步更新?
26 2
|
2月前
|
JavaScript
如何使用requestAnimationFrame实现DOM组件的同步更新?
【5月更文挑战第29天】如何使用requestAnimationFrame实现DOM组件的同步更新?
18 1
|
2月前
|
JavaScript 前端开发 API
DOM组件
【5月更文挑战第29天】DOM组件
19 1
|
2月前
|
JavaScript 开发者
Teleport传送:使用Vue的Teleport进行跨DOM结构渲染
【4月更文挑战第24天】Vue.js的`&lt;teleport&gt;`组件用于跨DOM结构渲染,解决组件视觉呈现跨越父组件DOM的问题。它允许子组件内容传送到DOM的任意位置,如示例中将模态框移到`modal-container`元素。通过`target`属性指定目标元素,结合`v-if`控制显示,实现灵活的UI布局和交互。在适当场景下使用`&lt;teleport&gt;`能优化复杂应用的结构。
|
2月前
|
JavaScript 数据可视化 容器
easyv平台_过滤器强制修改dom(暴力自定义组件渲染样式)
easyv平台_过滤器强制修改dom(暴力自定义组件渲染样式)
12 0
|
2月前
|
JavaScript
js_操作dom(委托和冒泡事件)
js_操作dom(委托和冒泡事件)
21 0
|
2月前
|
缓存 JavaScript 前端开发
【JavaScript 技术专栏】DOM 操作全攻略:从基础到进阶
【4月更文挑战第30天】本文深入讲解JavaScript与DOM交互,涵盖DOM基础、获取/修改元素、创建/删除元素、事件处理结合及性能优化。通过学习,开发者能掌握动态改变网页内容、结构和样式的技能,实现更丰富的交互体验。文中还讨论了DOM操作在实际案例、与其他前端技术结合的应用,助你提升前端开发能力。
|
22天前
|
JavaScript 前端开发 UED
JavaScript基础-DOM操作:查找、创建、修改
【6月更文挑战第12天】本文介绍了DOM基础,包括查找元素(getElementById、getElementsByClassName等)、创建新节点(createElement、createTextNode)和修改节点(innerText、innerHTML、setAttribute等)。强调了易错点,如ID唯一性、性能考量和安全问题,并提供了代码示例。熟练掌握DOM操作对前端开发至关重要,但应注意性能优化,适时使用框架或库。
JavaScript基础-DOM操作:查找、创建、修改
|
21天前
|
JavaScript 前端开发 安全
【JavaScript 】DOM操作快速入门
【JavaScript 】DOM操作快速入门
16 2