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>
相关推荐
Alan_Wdd16 分钟前
解决谷歌人机验证 (reCAPTCHA) 无法加载问题
前端·chrome·浏览器·插件·人机验证·recaptcha
ndjnddjxn18 分钟前
比赛的复现(2024isctf)
java·linux·前端
诗歌难吟46441 分钟前
Marquee
javascript·数据结构
gqkmiss1 小时前
Chrome 132 版本开发者工具(DevTools)更新内容
前端·chrome·chrome devtools
喵个咪1 小时前
无头内容管理系统 Headless CMS
前端·后端·cms
网络点点滴1 小时前
第一个AJAX调用XMLHttpRequest
前端·javascript·ajax
恋猫de小郭1 小时前
Android 16 Baklava 来了,来看看开发者预览版给我们带来了什么
android·前端·flutter
匹马夕阳1 小时前
前端自动化部署之ssh2和ssh2-sftp-client
前端·javascript·自动化
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_084】第 14 章:CSS 蒙版、形状与剪切概述 + 14.1:CSS 滤镜
前端·css·css3·html5·滤镜·css滤镜·滤镜特效