使用 Vue 框架 实现经典例子:TodoMVC


前言

TodoMVC是一个示例项目,它使用目前流行的不同JavaScript框架的来实现同一个Demo,来帮助你熟悉和选择最合适的前端框架。学习框架最直接有效的方式就是上手练习,接下来我们将用Vue.js来完成TodoMVC的示例。
官网地址:https://todomvc.com/

一、准备

在 Vscode 软件在打开集成终端

  • 输入 vue cerate 项目名 创建项目
  • 输入cd 项目名 进入创建好的项目内
  • 输入 npm run serve启动项目
  • 创建 TodoDemo.vue
  • TodoDemo.vue引入 App.vue

二、创建

1.编写TodoMvc基本结构

代码如下(示例):

<template>
  <div class="todo-demo">
    <h1>TODOS</h1>
    <div class="todo-form">
      <input type="text">
      <button>添加</button>
    </div>
    <div class="todo-content">
      <ul>
        <li> </li>
      </ul>
    </div>
    <div class="todo-foot">
      <span>1 item left</span>
      <div class="type-btns">
        <button>all</button>
        <button>active</button>
        <button>completde</button>
      </div>
      <button>clear completde</button>
    </div>
  </div>
</template>

<script>
export default {
data(){
    return {
      todos: [
        {
          id:1,
          text:"律师函警告",
          done: false
        },
         {
          id:2,
          text:"喜欢唱,跳,rep,篮球",
          done: true
        },
         {
          id:3,
          text:"你干嘛,哎呀",
          done: false
        }
      ]
    }
  }
}
</script>

<style>
.todo-demo {
  width: 400px;
  margin: 0 auto;
}
.todo-foot{
  display: flex;
  justify-content: space-between;
}
</style>

2.功能实现

  • 1) 使用 v-for 实现展示功能

代码如下:

 <div class="todo-content">
      <ul>
        <li> <li v-for="todo in todos" :key="todo.id">{{todo.text}}</li></li>
      </ul>
    </div>

在这里插入图片描述


  • 2) 使用 @click 实现删除功能

代码如下:

<div class="todo-content">
      <ul>
        <li v-for="todo in todos" :key="todo.id">
        <input type="checkbox">{{todo.text}}
        <span @click="del(todo.id)">×</span></li>  <!--点击 x 实现删除功能-->
      </ul>
</div>

export default {
  data(){...}
  },
  methods: {
  //删除功能
    del(id){
      this.todos = this.todos.filter(todo => todo.id !== id)
    }
  }
}

在这里插入图片描述


  • 3) 使用 v-model 实现复选框修改事件

代码如下:

<div class="todo-content">
      <ul>
        <li v-for="todo in todos" :key="todo.id">
        <input type="checkbox"  v-model="todo.done">{{todo.text}}
        <span @click="del(todo.id)">×</span></li>
      </ul>
</div>

export default {
  data(){
    return {
      todos: [
        {
          id:1,
          text:"律师函警告",
          done: false
        },
         {
          id:2,
          text:"喜欢唱,跳,rep,篮球",
          done: true
        },
         {
          id:3,
          text:"你干嘛,哎呀",
          done: false
        }
      ]
    }
  },
  methods: {
    del(id){
      this.todos = this.todos.filter(todo => todo.id !== id)
    }
  }
}

在这里插入图片描述


  • 3.1) 使用 :checked 和@change 实现复选框修改事件

代码如下:

<div class="todo-content">
      <ul>
        <li v-for="todo in todos" :key="todo.id">
          <!-- <input type="checkbox"  v-model="todo.done"> -->
          <input type="checkbox"  :checked="todo.done" @change="change">
          {{todo.text}} 
          <span @click="del(todo.id)">×</span>
        </li>
      </ul>
</div>

export default {
  data(){
    return {...},
    
  methods: {
    // 删除功能
    del(id){...},
    // 复选框的修改事件
    change(id){
      const currentTodo = this.todos.find(todo => todo.id === id)
      currentTodo.done = !currentTodo.done
    }
  }
}


  • 3.2)使用:class实现复选框选择状态下画横线

代码如下:

 <div class="todo-content">
      <ul>
        <li v-for="todo in todos" :key="todo.id">
          <input type="checkbox"  v-model="todo.done">
          <!-- <input type="checkbox"  :checked="todo.done" @change="change"> -->
          <span :class="{done: todo.done}">{{todo.text}} </span>
          <!-- 复选框选择状态下,我们使用 :class 的绑定中的对象的写法来实现 --> 
          <span @click="del(todo.id)">×</span>
        </li>
      </ul>
    </div>
    
<style>
/* 选择状态的样式 */
.todo-content ul li span.done{
  text-decoration: line-through;
  color: #ccc;
}
</style>


  • 4)使用v-model.trim @click实现添加功能,@keyup.enter:使用键盘回车完成添加

代码如下:

<div class="todo-form">
      <!-- 事件修饰符 .enter  @keyup.enter:键盘上回车 完成添加-->
      <input type="text" v-model.trim="todoText" @keyup.enter="submit">
      <button @click="submit">添加</button>
</div>

 data(){
    return {
      todos: [...],
      todoText: "",//默认为空
    }
  },
  
  methods: {
   // 添加功能
    submit(){
      const { todoText } = this
      console.log(todoText)
      if(todoText){
        // Date().getTime() 确保添加完成后的 id 不同
        this.todos.push({ id: new Date().getTime(), text: todoText})
        this.todoText = '';// 添加完成后清空输入框
      }
    },
  }

在这里插入图片描述


  • 5)使用计算属性computed以箭头函数实现动态未完成事件的更新

代码如下:

<div class="todo-foot">
      <!-- 实现动态 item left的更新 -->
      <span>{{activeTodoNums}}item left</span>
</div>


export default {
  data(){
    return {
      todos: [... {... done: false}],
      todoText: "",//默认为空
    }
  },
  // 使用计算属性:computed 实现动态的未完成事件
  computed: {
    activeTodoNums(){
      // 方法一:
      // return this.todos.reduce( function(res, todo){
      //   if(!todo.done){
      //     res ++
      //   }
      //   return res
      // },0)
      
      // 使用箭头函数 判断 done false有多少 !todo.done 如果为false  ++res
      return this.todos.reduce((res, todo) => (!todo.done ? ++res : res),0) 
    }
}

在这里插入图片描述


  • 5.1)使用:class @click computed实现 all active completde 的选择,并且改变颜色

代码如下:

<div class="type-btns">
        <button :class="{'btn-active': type === 'all'}" @click="type = 'all'">all</button>
        <button :class="{'btn-active': type === 'active'}" @click="type = 'active'">active</button>
        <button :class="{'btn-active': type === 'completde'}" @click="type = 'completde'">completde</button>
</div>

export default {
  data(){
    return {[...]
      todoText: "",//默认为空
      type: 'all',// 规定点击的的是 all | active | completde
    }
  },
  // 使用计算属性:computed 实现动态的未完成事件
  computed: {
    activeTodoNums(){...},
    showTodos(){
      return []
    }
  },
}

<style>
...
/* 点击效果 */
.btn-active {
  color: red;
}
</style>

  • 5.2)使用 computed 与 JavaScript中的 const实现 all active completde 的选择的展示

代码如下:

<div class="todo-content">
      <ul>
        <li v-for="todo in showTodos" :key="todo.id"> <!--重新选择遍历对象为:showTodos-->
         ...
        </li>
      </ul>
</div>

export default {
  data(){
    return {[...]
      todoText: "",//默认为空
      type: 'all',// 规定点击的的是 all | active | completde
    }
  },
  // 使用计算属性:computed 实现动态的未完成事件
  computed: {
    activeTodoNums(){...},
    showTodos(){
      const {todos, type} = this
      // 使用箭头函数  type 的值 进行判断,判断它是为:all | active | completde
      return todos.filter(todo => type === 'all' ? true : type === 'active' ? !todo.done : todo.done)
    }
  },
}

在这里插入图片描述

在这里插入图片描述

  • 5.2)使用 @click实现 clear completde 清空已经完成的任务

代码如下:


```handlebars
<button @click="clear">clear completde</button>

  computed: {
export default {
  data(){
    return {[...]
      todoText: "",//默认为空
      type: 'all',// 规定点击的的是 all | active | completde
    }
  },
  // 使用计算属性:computed 实现动态的未完成事件
  computed: {
    activeTodoNums(){...},
    clear(){
      this.todos = this.todos.filter(todo => !todo.done)
   },
}

在这里插入图片描述

  • 6)使用 v-if v-elese @dblclick :value @blur v-model.lazy实现双击文字进行编辑功能

代码如下:

<div class="todo-content">
      <ul>
          ...
          <!-- template 不会被渲染成所有类 -->
          <template v-if="editId !== todo.id">
          <!-- 判断 editId  todo.id 是否相同 不相同展示内容,相同展示输入框 -->
          ...
          </template>

          <!-- 双击出输入框效果,且原文字内容展示到输入框 -->
          <!-- @blur="changeTodoText" 失去交点的时候关闭输入框-->
          <!-- v-model.lazy 失去交点的时候完成修改  -->
          <input v-else type="text"  @blur="changeTodoText"  v-model.lazy="todo.text"/>
        </li>
      </ul>
    </div>

<script>
export default {
  data() {
    return {
      todos: [...],
      todoText: "", //默认为空
      type: "all", // 规定点击的的是 all | active | completde
      editId:' ', // 定义一个可编辑 id,默认页面只能出现一个 id  id相同时则进行编辑
    };
  },
  // 使用计算属性:computed 实现动态的未完成事件
  computed: {
    activeTodoNums() {...},
    showTodos() {...},
  },
  methods: {
    // 删除功能
    del(id) {... },
    // 复选框的修改事件
    // change(id){...},
    // 添加功能
    submit() {...},
    // 实现clear completde 清空已经完成的任务
    clear() {...},

    changeTodoText(){
      //  失去交点关闭输入框
      this.editId = 0
    }
  },
};
</script>

对于上代码:功能虽然已实现但仍然存在着功能缺陷

 <div class="todo-content">
      <ul>
           ...
            <!-- 双击事件 -->
            <span @dblclick="handleDblclick(todo.id)" :class="{ done: todo.done }">
            {{ todo.text }}
            </span>
            ...
        </li>
      </ul>
 </div>


<script>
export default {
     ...
    changeTodoText(){
      //  失去交点关闭输入框
      this.editId = 0
    },
    // 错误展示
      handleDblclick(id){
      this.editId = id
      console.log(this.$refs.editInp[0])
      this.$refs.editInp[0].focus()
    }

  },
};
</script>

在这里插入图片描述
错误分析:

  • 如果修改完了 马上让输入框获得焦点话, 会出报错,提示 focus of undefined
  • 因为 当双击的时候编辑 id 被修改, 输入框的出现, 所以获得焦点的方法和 输入框的出现起了冲突
  • 入框的出现和输入框获取焦点相当于同时执行的。获取焦点方法是无法生效的。
  • 可以使用 settimeout 做个延迟执行
  • vue 官方提供了 $nextTick

错误修改:

 <div class="todo-content">
      <ul>
           ...
            <!-- 双击事件 -->
            <span @dblclick="handleDblclick(todo.id)" :class="{ done: todo.done }">
            {{ todo.text }}
            </span>
            ...
        </li>
      </ul>
 </div>


<script>
export default {
     ...
    changeTodoText(){
      //  失去交点关闭输入框
      this.editId = 0
    },
    // 错误展示
   handleDblclick(id) {
      this.editId = id;
      // console.log(this.$refs.editInp[0])
      // 如果修改完了 马上让输入框获得焦点话, 会出报错,提示 focus of undefined
      // 因为 当双击的时候编辑 id 被修改, 输入框的出现, 所以获得焦点的方法和 输入框的出现起了冲突
      // 输入框的出现和输入框获取焦点相当于同时执行的。获取焦点方法是无法生效的。
      // 可以使用 settimeout 做个延迟执行
      // vue 官方提供了 $nextTick
      // 方法一:
      //   setTimeout( () => {
      //     this.$refs.editInp[0].focus()
      //   },5)
      //   }

      // 方法二:
      // nextTick作用:在data更新后,马上想要对 data 相关的 dom 节点进行操作的话,可能会失败。可以使用:nextTick来解决
      this.$nextTick(() => {
        // 循环里面设置了 ref 的话 需要使用 [0] 获取
        // this.$resf.
        // 当在 v-for 循环里面使用 ref的话需要注意 ref获取结果时会是一个数组,需要添加 [0] 获取
        //  ref定义的名字是相同的话,那么获取的结果会是一个数组  得到的是所有的 dom 节点
        this.$refs.editInp[0].focus();
      });
    },
  },
};
</script>

在这里插入图片描述

  • 7)使用 v-model computed实现点击的左时将所有任务设置成已完成

代码如下:

    <div class="todo-form">
    ...
      <!-- 实现点击的左时将所有任务设置成已完成 -->
      <input type="checkbox" v-model="isAllChecked">
    ...
    </div>

<script>
export default {
  data() {...},
  // 使用计算属性:computed 实现动态的未完成事件
  computed: {
activeTodoNums() {...},
    showTodos() {...},

    // 点击的左时将所有任务设置成已完成
    isAllChecked: {
      get() {
        return this.todos.every( (todo) => todo.done);
      },
      set(val) {
        this.todos.forEach( todo => {
          todo.done = val
        });
      },
    }
  },
  methods: {...},
};
</script>

在这里插入图片描述

总结

  • 事件处理
    • 按键修饰符:键盘事件的修饰符 @keyup.enter
      • .enter
  • class 与 style 的绑定
    • class的绑定
      • 对象语法 : {a:true}
      • 数组语法 : {‘a’,'b'}
      • 数组对象语法 : {‘a’,{b:true}}
    • class的绑定
      • 对象 {width: '200px'}
  • 条件渲染
    • v-if v-else v-eles-if
    • v-show
  • 列表渲染: v-for
    • :key
    • 它可以在 template 上进行循环
  • 表单的输入绑定:v-model
    • 修饰符:
      • .trim 自动清除 v-model 后面的左右值
      • .lazy失去交点的时候才会发生改变
      • .number交 data 的值转换成 数学 使用:Number 转换
    • 它是 value 已经 input 事件的简化版本
  • 计算属性:computed
    • 当你想要的内容可以利用现有的 data 通过一些运算得到的话,那么我们就可以设置一个计算属性
  • ref
    • vue 提供了一种可以获取元素,真实 dom 节点的方法:this.$refs.名
    • 当在 v-for 循环里面使用 ref的话需要注意 ref获取结果时会是一个数组,需要添加 [0] 获取
    • 当 ref定义的名字是相同的话,那么获取的结果会是一个数组 得到的是所有的 dom 节点
  • nextTick作用:
    • 在data更新后,马上想要对 data 相关的 dom 节点进行操作的话,可能会失败。可以使用:nextTick来解决

完整项目代码 TodoDemo.vue

<template>
  <div class="todo-demo">
    <h1>TODOS</h1>
    <div class="todo-form">
      <!-- 事件修饰符 .enter -->

      <!-- 实现点击的左时将所有任务设置成已完成 -->
      <input type="checkbox" v-model="isAllChecked">
      
      <input type="text" v-model.trim="todoText" @keyup.enter="submit" />
      <button @click="submit">添加</button>
    </div>
    <div class="todo-content">
      <ul>
        <li v-for="todo in showTodos" :key="todo.id">
          <!--重新选择遍历对象为:showTodos  -->

          <!-- template 不会被渲染成所有类 -->
          <template v-if="editId !== todo.id"
            ><!-- 判断 editId  todo.id 是否相同 不相同展示内容,相同展示输入框 -->
            <input type="checkbox" v-model="todo.done" />
            <!-- <input type="checkbox"  :checked="todo.done" @change="change"> -->
            <!-- 双击事件 -->
            <span
              @dblclick="handleDblclick(todo.id)"
              :class="{ done: todo.done }"
              >{{ todo.text }}
            </span>

            <!-- 复选框选择状态下,我们使用 :class 的绑定中的对象的写法来实现 -->
            <span @click="del(todo.id)">×</span>
          </template>

          <!-- 双击出输入框效果,且原文字内容展示到输入框 -->
          <!-- @blur="changeTodoText" 失去交点的时候关闭输入框-->
          <!-- v-model.lazy 失去交点的时候完成修改  -->
          <!-- 输入框自动 或得 交点的话 1. autofocus 属性  2. 使用原生 dom.focus() -->
          <input
            v-else
            type="text"
            @blur="changeTodoText"
            v-model.lazy="todo.text"
            ref="editInp"
          />
        </li>
      </ul>
    </div>
    <div class="todo-foot">
      <!-- 实现动态 item left的更新 -->
      <span>{{ activeTodoNums }}item left</span>
      <div class="type-btns">
        <button :class="{ 'btn-active': type === 'all' }" @click="type = 'all'">
          all
        </button>
        <button
          :class="{ 'btn-active': type === 'active' }"
          @click="type = 'active'"
        >
          active
        </button>
        <button
          :class="{ 'btn-active': type === 'completde' }"
          @click="type = 'completde'"
        >
          completde
        </button>
      </div>
      <button @click="clear">clear completde</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      todos: [
        {
          id: 1,
          text: "律师函警告",
          done: false,
        },
        {
          id: 2,
          text: "喜欢唱,跳,rep,篮球",
          done: true,
        },
        {
          id: 3,
          text: "你干嘛,哎呀",
          done: false,
        },
      ],
      todoText: "", //默认为空
      type: "all", // 规定点击的的是 all | active | completde
      editId: 0, // 定义一个可编辑 id,默认页面只能出现一个 id  id相同时则进行编辑
    };
  },
  // 使用计算属性:computed 实现动态的未完成事件
  computed: {
    activeTodoNums() {
      // 老方法
      // return this.todos.reduce( function(res, todo){
      //   if(!todo.done){
      //     res ++
      //   }
      //   return res
      // },0)

      // 使用箭头函数 判断 done false有多少 !todo.done 如果为false  ++res
      return this.todos.reduce((res, todo) => (!todo.done ? ++res : res), 0);
    },
    showTodos() {
      const { todos, type } = this;
      // 使用箭头函数  type 的值 进行判断,判断它是为:all | active | completde
      return todos.filter((todo) =>
        type === "all" ? true : type === "active" ? !todo.done : todo.done
      );
    },

    // 点击的左时将所有任务设置成已完成
    isAllChecked: {
      get() {
        return this.todos.every( (todo) => todo.done);
      },
      set(val) {
        this.todos.forEach( todo => {
          todo.done = val
        });
      },
    }
  },
  methods: {
    // 删除功能
    del(id) {
      this.todos = this.todos.filter((todo) => todo.id !== id);
    },
    // 复选框的修改事件
    // change(id){
    //   const currentTodo = this.todos.find(todo => todo.id === id)
    //   currentTodo.done = !currentTodo.done
    // },
    // 添加功能
    submit() {
      const { todoText } = this;
      console.log(todoText);
      if (todoText) {
        // Date().getTime() 确保添加完成后的 id 不同
        this.todos.push({ id: new Date().getTime(), text: todoText });
        this.todoText = ""; // 添加完成后清空输入框
      }
    },
    // 实现clear completde 清空已经完成的任务
    clear() {
      this.todos = this.todos.filter((todo) => !todo.done);
    },

    changeTodoText() {
      //  失去交点关闭输入框
      this.editId = 0;
    },

    handleDblclick(id) {
      this.editId = id;
      // console.log(this.$refs.editInp[0])
      // 如果修改完了 马上让输入框获得焦点话, 会出报错,提示 focus of undefined
      // 因为 当双击的时候编辑 id 被修改, 输入框的出现, 所以获得焦点的方法和 输入框的出现起了冲突
      // 输入框的出现和输入框获取焦点相当于同时执行的。获取焦点方法是无法生效的。
      // 可以使用 settimeout 做个延迟执行
      // vue 官方提供了 $nextTick
      //   setTimeout( () => {
      //     this.$refs.editInp[0].focus()
      //   },5)
      //   }
      this.$nextTick(() => {
        // 循环里面设置了 ref 的话 需要使用 [0] 获取
        this.$refs.editInp[0].focus();
      });
    },
  },
};
</script>

<style>
.todo-demo {
  width: 400px;
  margin: 0 auto;
}
.todo-foot {
  display: flex;
  justify-content: space-between;
}
.todo-content ul {
  list-style: none;
  padding-left: 0;
}
.todo-content ul li {
  display: flex;
}
.todo-content ul li span {
  user-select: none;
}
/* 复选框选择状态的样式 */
.todo-content ul li span.done {
  text-decoration: line-through;
  color: #ccc;
}
/* 点击效果 */
.btn-active {
  color: red;
}
</style>
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fvcvv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值