Vue3 —— 任务列表案例

刚接触 vue3,基于 vue 3.x + bootstrap 做了个任务列表的小案例,在这里简单梳理一下流程以便加深理解

1. 初始化项目


>> 在终端运行命令,初始化项目

npm init vite-app todos

>> 使用 vscode 打开项目,安装依赖

npm install

>> 安装 less 语法相关依赖

npm i less -D

2. 梳理项目结构


初始化 index.css 文件,创建 TodoList 组件并引入到 App.vue 中。初始项目列表如下

>> 重置 index.css

:root {
  font-size: 12px;
}

body {
  padding: 8px;
}

>> 重置 App.vue 组件

<template>
  <div>
    <h1>App 根组件</h1>
    <hr>
    <todo-list></todo-list>
  </div>
</template>

<script>
import TodoList from './components/todo-list/TodoList.vue'

export default {
  name: 'MyApp',
  data() {
    return {
      // 任务列表数据
      todolist: [
        { id: 1, task: '周一早晨9点开会', done: false },
        { id: 2, task: '周一晚上8点聚餐', done: false },
        { id: 3, task: '准备周三上午的演讲稿', done: true }
      ]
    }
  },
  components: {
    TodoList
  }
}
</script>

<style lang="less" scoped>
</style>

3. 封装 todo-list 组件


>> 基于 bootstrap 渲染列表组件

Bootstrap v4 中文文档

将 bootstrap 文件拷贝至 src/assets 静态资源目录中,在 main.js 入口文件中导入 src/assets/css/bootstrap.css 样式表。

import { createApp } from 'vue'
import App from './App.vue'
// bootstrap 
import './assets/css/bootstrap.css'
import './index.css'

createApp(App).mount('#app')

根据 bootstrap 提供的 列表组复选框 渲染列表组件的基本效果。

  <ul class="list-group">
    <!-- 列表组 -->
    <li class="list-group-item d-flex justify-content-between align-items-center">
      <!-- 复选框 -->
      <div class="custom-control custom-checkbox">
        <input type="checkbox" class="custom-control-input" id="customCheck1">
        <label class="custom-control-label" for="customCheck1">Check this custom checkbox</label>
      </div>
      <!-- 徽标 badge -->
      <span class="badge badge-success badge-pill">完成</span>
      <span class="badge badge-danger badge-pill">未完成</span>
    </li>
  </ul>

>> 为 TodeList 声明 props 属性

为了接受外界传递的列表数据,需要在 TodoList 组件中声明如下 props 属性:

export default {
  name: 'TodoList',
  props: {
    // 列表数据
    list: {
      type: Array,
      required: true,
      default: []
    }
  }
}

之后,在 App 组件中通过 list 属性,将数据传递到 TodoList 组件中:

    <todo-list :list="todolist"></todo-list>

>> 渲染列表的 DOM 结构

通过 v-for 指令,循环渲染列表的 DOM 结构:

<template>
  <ul class="list-group">
    <!-- 列表组 -->
    <li class="list-group-item d-flex justify-content-between align-items-center" v-for="item in list" :key="item.id">
      <!-- 复选框 -->
      <div class="custom-control custom-checkbox">
        <input type="checkbox" class="custom-control-input" :id="item.id">
        <label class="custom-control-label" :for="item.id">{{ item.task }}</label>
      </div>
      <!-- 徽标 badge -->
      <span class="badge badge-success badge-pill">完成</span>
      <span class="badge badge-danger badge-pill">未完成</span>
    </li>
  </ul>
</template>

通过 v-ifv-else 指令,按需渲染 badge 效果(完成/未完成):

    <span class="badge badge-success badge-pill" v-if="item.done">完成</span>
    <span class="badge badge-danger badge-pill" v-else>未完成</span>

通过 v-model 指令,双向绑定任务的完成状态:

    <input type="checkbox" class="custom-control-input" :id="item.id" v-model="item.done">

通过 v-bind 属性绑定,动态切换元素的 class 类名:

    <label class="custom-control-label" :class="item.done ? 'delete':''" :for="item.id">{{ item.task }}</label>

TodoList 组件中样式声明:

<style lang="less" scoped>
.list-group {
  width: 400px;		// 为列表设置固定宽度
}

.delete {
  text-decoration: line-through;
  color: gray;
  font-style: italic;
}
</style>

4. 封装 todo-input 组件


>> 创建并注册 TodoInput 组件

src/components/todo-input/ 目录下新建 TodoInput.vue 组件,并导入至 App.vue 组件中。

>> 基于 bootstrap 渲染组件结构

根据 bootstrap 提供的 inline-forms 渲染 TodoInput 组件的基本结构。

  <form class="form-inline">
    <div class="input-group mb-2 mr-sm-2">
      <div class="input-group-prepend">
        <div class="input-group-text">任务</div>
      </div>
      <input type="text" class="form-control" placeholder="请输入任务信息" style="with: 356px">
    </div>

    <button type="submit" class="btn btn-primary mb-2">添加新任务</button>
  </form>

>> 通过自定义事件向外传递数据

需求:在 App 组件中,监听 TodoInput 组件的自定义事件,获取要添加的任务名称

TodoInput 组件的 data 中声明如下数据:

  data() {
    return {
      // 新任务名称
      taskname: ''
    }
  }

TodoInput 组件中 input 输入框进行 v-model 双向数据绑定:

  <input type="text" class="form-control" placeholder="请输入任务信息" style="with: 356px" v-model.trim="taskname">

监听 form 表单的 submit 事件,阻止默认提交行为并指定事件处理函数:

  <form class="form-inline" @submit.prevent="onFormSubmit">

methods 中声明 onFormSubmit 事件处理函数:

  emits: ['add'],
  methods: {
    onFormSubmit() {
      
      // 1. 判断任务名称不能为空
      if (!this.taskname) return alert('任务名称不能为空!')
      
      // 2. 触发自定义 add 事件,并向外界传递数据
      this.$emit('add', this.taskname)
      
      // 3. 清空文本框
      this.taskname = ''
    }
  }

>> 实现添加任务的功能

App.vue 组件中监听 TodoInput 组件自定义的 add 事件:

  <todo-input @add="onAddNewTask"></todo-input>

App.vue 组件的 data 中声明 nextId 来模拟 id 自增 +1 的操作:

  data() {
    return {
      // 任务列表数据
      todolist: [
        { id: 1, task: '周一早晨9点开会', done: false },
        { id: 2, task: '周一晚上8点聚餐', done: false },
        { id: 3, task: '准备周三上午的演讲稿', done: true }
      ],
      // 下一个可用 Id
      nextId: 4
    }
  },

App.vue 组件的 methods 中声明 onAddNewTask 事件处理函数如下:

  methods: {
    onAddNewTask(taskname) {
      // 向任务列表中新增任务信息
      this.todolist.push({
        id: this.nextId,
        task: taskname,
        done: false
      })
      // nextId 自增
      this.nextId++
    }
  },

5. 封装 todo-button 组件


>> 创建并注册 TodoButton 组件

src/components/todo-button/ 目录下新建 TodoButton.vue 组件,并导入至 App.vue 中:

>> 基于 bootstrap 渲染组件结构

根据 bootstrap 提供的 Button group 渲染 TodoButton 组件的基本结构:

  <div class="button-container">
    <div class="btn-group">
      <button type="button" class="btn btn-primary">全部</button>
      <button type="button" class="btn btn-secondary">已完成</button>
      <button type="button" class="btn btn-secondary">未完成</button>
    </div>
  </div>

btn-primarybtn-secondary 为 Bootstrap 内置的预定义的按钮样式,参见 按钮(Buttons)

在这里插入图片描述

通过类名 button-container 美化样式

<style lang="less" scoped>
.button-container {
  width: 400px;
  text-align: center;
}
</style>

>> 通过 props 指定默认激活的按钮

TodoButton 组件中声明如下的 props,用来指定默认激活按钮的索引:

  props: {
    active: {
      type: Number,
      required: true,
      default: 0 		// 默认激活全部按钮
    }
  }

通过动态绑定 class 类名的方式控制按钮的激活状态:

  <div class="button-container">
    <div class="btn-group">
      <button type="button" class="btn" :class="active === 0 ? 'btn-primary' : 'btn-secondary'">全部</button>
      <button type="button" class="btn" :class="active === 1 ? 'btn-primary' : 'btn-secondary'">已完成</button>
      <button type="button" class="btn" :class="active === 2 ? 'btn-primary' : 'btn-secondary'">未完成</button>
    </div>
  </div>

App 组件中声明默认激活项的索引,并通过属性绑定的方式传递给 TodoButton 组件:

  <todo-button class="mt-3" :active="activeBtnIndex"></todo-button>
  data() {
  ...
    return {
      activeBtnIndex: 0
    }
  },

>> 通过 v-model 更新激活项的索引

TodoButton 组件中的三个按钮分别绑定 click 事件处理函数:

  <button type="button" class="btn" :class="active === 0 ? 'btn-primary' : 'btn-secondary'" @click="onBtnClick(0)">全部</button>
  <button type="button" class="btn" :class="active === 1 ? 'btn-primary' : 'btn-secondary'" @click="onBtnClick(1)">已完成</button>
  <button type="button" class="btn" :class="active === 2 ? 'btn-primary' : 'btn-secondary'" @click="onBtnClick(2)">未完成</button>

TodoButton 组件中声明如下的自定义事件,用来更新父组件通过 v-model 指令传递来的 props 数据:

export default {
  name: 'TodoButton',
  emits: ['update:active'],		// 声明和 v-model 相关的自定义事件
  props: {
    // 激活项的索引
    active: {
      type: Number,
      required: true,
      default: 0 // 默认激活全部按钮
    }
  },
}

TodoButton 组件的 methods 节点中声明 onBtnClick 事件处理函数:

  methods: {
    onBtnClick(index) {
      if (index === this.active) return		// 索引值不变,没必要传数据
      this.$emit('update:active', index)	// 触发自定义事件,传递新索引
    }
  }

>> 通过计算属性动态切换列表数据

需求:点击不同按钮,切换显示不同列表数据。可以根据当前激活按钮的索引,动态计算出要显示的列表数据并返回。

App 根组件中声明如下计算属性:

  computed: {
    // 根据激活按钮的索引,动态计算要显示的列表数据
    tasklist() {
      switch (this.activeBtnIndex) {
        case 0:
          return this.todolist
        case 1:
          return this.todolist.filter(x => x.done === true)
        case 2:
          return this.todolist.filter(x => x.done === false)
      }
    }
  },

App 根组件的 DOM 结构中,将 TodoList 组件的 :list="todolist" 修改为该计算属性:

  <!-- 使用 TodoList 组件 -->
  <todo-list :list="tasklist" class="mt-2"></todo-list>

相关文章

前端工程师一般用的是Bootstrap的框架而不是样式,样式一般自...
起步导入:<linkrel="stylesheet"href="b...
(1)modal声明一个模态框(2)modal-dialog定义模态框尺寸(...
图片在Bootstrap版本3中,通过为图片添加 .img-responsive ...
<inputtype="text"class="form-controlda...
目录bootstrap-treeview使用小记零、写在前面的话一、功能说...