Vue todoList小项目记录

最初代码

简单搭一个vue2的小项目

App.vue

html 复制代码
<template>
  <div id="app">
    <!-- 容器 -->
    <div class="todo-container">
      <div class="todo-wrap">

        <!-- 头部 -->
        <MyHeader :addTodo="addTodo"></MyHeader>

        <!-- 主要内容区域 -->
        <List :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></List>

        <!-- 页尾 -->
        <MyFooter :todoList="todoList" :checkAll="checkAll" :deleteAllDone="deleteAllDone"></MyFooter>

      </div>

    </div>

  </div>

</template>

<script>
import MyHeader from './components/MyHeader.vue';
import MyFooter from './components/MyFooter.vue';
import List from './components/List.vue';

export default {
  name: 'App',
  components: {
    MyHeader, MyFooter, List
  },
  data() {
    return {
      todoList: JSON.parse(localStorage.getItem('todoList')) || []
    }
  },
  methods: {
    addTodo(todoObj) {
      // console.log('我是App组件,我收到了数据x',todoObj);
      this.todoList.unshift(todoObj)
    },
    checkTodo(id) {
      // console.log('我是app',id);
      //在这里改变数据,从而依次去改变儿子的数据,因为是父传子
      this.todoList.forEach((todo) => {
        if(todo.id === id) {
            todo.done = !todo.done;
        }
      })
    },
    deleteTodo(id) {
      this.todoList= this.todoList.filter((todo) => {
        return todo.id != id;
      })
    },
    checkAll(flag) {
      this.todoList.forEach((todo) => {
        todo.done = flag;
      })
    },
    deleteAllDone() {
      this.todoList = this.todoList.filter((todo) => {
        return !todo.done;
      })
    }
  },
  watch: {
    todoList: {
      deep:true,
      handler(value) {
        localStorage.setItem('todoList',JSON.stringify(this.todoList));
      }
    }
  }
}
</script>

各个组件

MyHeader

html 复制代码
<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add" />
  </div>

</template>

<script>

import { nanoid } from 'nanoid'

export default {
  name: 'MyHeader',
  props: ['addTodo'],
  data() {
    return {
      title: ''
    }
  },
  methods: {
    add(e) {
      if (this.title.trim() != '') {
        const todoObj = { id: nanoid(), title: this.title, done: false };
        this.addTodo(todoObj);
        this.title = ''
      }
    }
  }
}
</script>

List.vue

bash 复制代码
<template>
    <div><!-- 主要内容区域 -->
        <ul class="todo-main">
            <Item v-for="todoObj in todoList" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
        </ul>

    </div>

</template>

<script>
import Item from './Item.vue';

export default {
    name: 'List',
    components: { Item },
    props: ['todoList','checkTodo','deleteTodo']
}
</script>

Item.vue

bash 复制代码
<template>
    <div>
        <li>
            <label>
                <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span>{{todo.title}}</span>

            </label>

            <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>

        </li>

    </div>

</template>

<script>
export default {
    name: 'Item',
    props: ['todo','checkTodo','deleteTodo'],
    methods: {
      handleCheck(id) {
        //通知App取反是否勾选
        this.checkTodo(id);
      },
      handleDelete(id) {
        if(confirm('确定删除吗?')) {
          this.deleteTodo(id);
        }
      }
    }
}
</script>

MyFooter.vue

bash 复制代码
<template>
  <!-- 页尾 -->
  <div class="todo-footer" v-show="total > 0">
    <label>
      <input type="checkbox" v-model="isAll" />
    </label>

    <span>
      <span>已完成{{ doneNum }}</span> / 全部{{ total }}
    </span>

    <button class="btn btn-danger" @click="deleteDoneTodo">清除已完成任务</button>

  </div>

</template>

<script>

export default {
  name: 'MyFooter',
  props: ['todoList', 'checkAll', 'deleteAllDone'],
  methods: {
    deleteDoneTodo() {
      this.deleteAllDone();
    }
  },
  computed: {
    doneNum() {
      return this.todoList.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
    },
    total() {
      return this.todoList.length;
    },
    isAll: {
      get() {
        return this.doneNum === this.total && this.total > 0;
      },
      set() {
        this.checkAll(!this.isAll);
      }
    }
  }
}
</script>

小总结

这里写的东西,都是比较原始的,像组件的通信这些,使用的是最原始的props

props按理来说,用于父和子之间的传递,所以如果如果是兄弟组件,只能两个兄弟组件通过父组件,来共享数据,或者说爷孙关系的组件的话,还得通过爷 -> 父 -> 子这样传,之后的完善还是很有必要的

浏览器本地存储

我自己也有写浏览器相关的记录

为什么要和存储有关,就是因为如果我们刷新页面的话,我们这个项目没有通过mysql进行数据的交互,我们就得依赖于本地存储,刷新页面的话,我们这个项目就不会存储我们之前的值

实现的方式,先总结来看,就是我们对todoList进行监视,如果todoList修改了,我们就要去重新赋值本地存储中的数据,这里的修改包括增删改

注意: 我们watch函数对对象的监视,只是浅浅的监视,不会监视对象属性的变化,如果要监视对象上属性的变化,需要开启深度监视,这一点在我博客的Vue基础那一篇中有记录

需要修改的代码,就这两行,我们监视的时候,需要开启deep: true

然后每次修改了重新赋值,需要注意的是,我们得使用JSON.stringify()把对象转换成字符串,这样我们才能看的懂

还有就是展示的时候,如果本来是空值的话,JSON.parse()返回的是null,所以我们得使用 || 后面跟一个空数组,不然我们在其他页面使用 todoList.length会报错,相当于 null.length会报错

组件通信修改

对于App.vue 和 Item.vue这两个组件之间的通信,原先我们的写法是
App -> List -> Item

需要通过List,这里我们通过全局事件总线进行修改

现在main.js中导入$bus

在App.vue上,我们给$bus定义事件

bash 复制代码
  mounted() {
    this.$bus.$on('checkTodo',this.checkTodo); 
    this.$bus.$on('deleteTodo',this.deleteTodo); 
  },
  beforeDestroy() {
    this.$bus.$off('checkTodo');
    this.$bus.$off('deleteTodo');
  }

记得写解绑的代码

这里的回调函数用之前的就行

并且删除在List.vue关于props传递的函数checkTodo,deleteTodo

Item.vue中,我们发送数据,通过$bus发送数据

bash 复制代码
      handleCheck(id) {
        //通知App取反是否勾选
        this.$bus.$emit('checkTodo',id);
      },
      handleDelete(id) {
        if(confirm('确定删除吗?')) {
          this.$bus.$emit('deleteTodo',id);
        }
      },

发送数据便完成

编辑按钮

添加一个编辑按钮

整体代码的逻辑就是

我们点击编辑按钮,然后左边的title会呈现输入框的样式,如下

并且编辑按钮会消失

失去焦点之后,会自动更新todoList的数据,更新数据

vue 复制代码
<div>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span v-show="!todo.isEdit">{{todo.title}}</span>
            <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputEdit">
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
        <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit" >编辑</button>
    </li>
</div>
      
  ...
  
  handleEdit(todo) {
        if(todo.hasOwnProperty('isEdit')) {
          todo.isEdit = true;
        } else {
          //给todo绑定一个响应式的属性isEdit,动态追加属性
          this.$set(todo,'isEdit',true);
        }
      },

我们给todo动态的绑定的一个属性,并且加入了判断是否有isEdit,避免重复添加属性

失去焦点之后,我们就更新数据

vue 复制代码
<!-- 输入框代码 -->
<input type="text" 
       v-show="todo.isEdit" 
       :value="todo.title" 
       @blur="handleBlur(todo,$event)" 
       ref="inputEdit">
  
  ...
  handleBlur(todo,e) {
        todo.isEdit = false;
        if(!e.target.value.trim()) {
          alert('输入不能为空!');
          return;
        }
        this.$bus.$emit('updateTodoEdit', todo.id,e.target.value)
      }

需要注意的是,我们在input上拿取input输入的值的时候,需要给方法传$event,我们在方法内部使用e.target.value拿取输入内容

点击输入框,自动获取焦点

我们有这样一个需求,就是我们点击编辑之后,但是我不想改,点击其他地方,可以继续修改

但是我们写的代码,必须点击输入框然后再失去焦点才行,所以我们这里得自动获取焦点

那么谁去获取焦点呢? input输入框,所以在点击编辑按钮的时候,让input获取焦点

vue 复制代码
handleEdit(todo) {
        if(todo.hasOwnProperty('isEdit')) {
          todo.isEdit = true;
        } else {
          //给todo绑定一个响应式的属性isEdit,动态追加属性
          this.$set(todo,'isEdit',true);
        }
        this.$nextTick(() => {
          this.$refs.inputEdit.focus();
        })
      }

值得注意的是,我们的input输入框显示与否,是根据todo.isEdit的值,进行显示与否的,所以handleEdit前面的代码是去显示input,如果我们直接让input输入框获取焦点,此时页面上没有input输入框,所以我们的需求是在dom刷新之后,在去获取焦点,我们就使用到nextTick(),他会在数据更新之后,在执行nextTick里边的回调函数

完整代码

vue 复制代码
<template>
    <div>
        <li>
            <label>
                <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span v-show="!todo.isEdit">{{todo.title}}</span>
                <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputEdit">
            </label>
            <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
            <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit" >编辑</button>
        </li>
    </div>
</template>

<script>
export default {
    name: 'Item',
    props: ['todo'],
    methods: {
      handleCheck(id) {
        //通知App取反是否勾选
        this.$bus.$emit('checkTodo',id);
      },
      handleDelete(id) {
        if(confirm('确定删除吗?')) {
          this.$bus.$emit('deleteTodo',id);
        }
      },
      handleEdit(todo) {
        if(todo.hasOwnProperty('isEdit')) {
          todo.isEdit = true;
        } else {
          //给todo绑定一个响应式的属性isEdit,动态追加属性
          this.$set(todo,'isEdit',true);
        }
        this.$nextTick(() => {
          this.$refs.inputEdit.focus();
        })
      },
      handleBlur(todo,e) {
        todo.isEdit = false;
        if(!e.target.value.trim()) {
          alert('输入不能为空!');
          return;
        }
        this.$bus.$emit('updateTodoEdit', todo.id,e.target.value)
      }
    }
}
</script>

App.vue

vue 复制代码
<template>
  <div id="app">
    <!-- 容器 -->
    <div class="todo-container">
      <div class="todo-wrap">

        <!-- 头部 -->
        <MyHeader @addTodo="addTodo"></MyHeader>

        <!-- 主要内容区域 -->
        <List :todoList="todoList"></List>

        <!-- 页尾 -->
        <MyFooter :todoList="todoList" @checkAll="checkAll" @deleteAllDone="deleteAllDone"></MyFooter>
      </div>
    </div>
  </div>

</template>

<script>
import MyHeader from './components/MyHeader.vue';
import MyFooter from './components/MyFooter.vue';
import List from './components/List.vue';

export default {
  name: 'App',
  components: {
    MyHeader, MyFooter, List
  },
  data() {
    return {
      todoList: JSON.parse(localStorage.getItem('todoList')) || []
    }
  },
  methods: {
    ...
    //更新todo的value
    updateTodoEdit(id,title) {
      this.todoList.forEach((todo) => {
        if(todo.id === id) {
          todo.title = title;
        }
      })
    }
  },
  watch: {
    todoList: {
      deep:true,
      handler(value) {
        localStorage.setItem('todoList',JSON.stringify(this.todoList));
      }
    }
  },
  mounted() {
    this.$bus.$on('checkTodo',this.checkTodo); 
    this.$bus.$on('deleteTodo',this.deleteTodo); 
    this.$bus.$on('updateTodoEdit',this.updateTodoEdit); 
  },
  beforeDestroy() {
    this.$bus.$off('checkTodo');
    this.$bus.$off('deleteTodo');
    this.$bus.$off('updateTodoEdit');
  }
}
</script>
相关推荐
diemeng11191 小时前
AI前端开发技能变革时代:效率与创新的新范式
前端·人工智能
bin91533 小时前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
晴空万里藏片云4 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一5 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球5 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7235 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense
无责任此方_修行中6 小时前
每周见闻分享:杂谈AI取代程序员
javascript·资讯
Σίσυφος19007 小时前
halcon 条形码、二维码识别、opencv识别
前端·数据库
学代码的小前端7 小时前
0基础学前端-----CSS DAY13
前端·css
dorabighead8 小时前
JavaScript 高级程序设计 读书笔记(第三章)
开发语言·javascript·ecmascript