VUE3学习第二天 学以致用 ----todolist 需求开发(一)

2,369 阅读4分钟

功能介绍

  1. 可以输入待办事项
  2. 输入回车键,添加一条待办事项,展现在输入框下,形成待办事项列表
  3. 某条待办事项不想要了,可以删除
  4. 某项待办事项完成了,可以勾选掉
  5. 还需要显示未完成事项
  6. 还可以全选
  7. 可以对待办事项筛选(已完成,未完成),切换待办事项的状态
  8. 能够本地存储

ToDoList 功能清单

  • 添加待办事项
  • 删除待办事项
  • 编辑待办事项
  • 切换待办事项
  • 存储待办事项

项目结构

使用vue-cli 脚手架,完成vue3的项目搭建。

  1. main.js 项目入口,根实例的挂载
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
  1. App.vue 引入todoList样式,并且完成基本HTMl结构的搭建,为了消除vue3的学习噪音,样式和html架子代码可以自取
  • html 结构
    • header
      • 标题(todos)
      • 待办事项输入框
    • section
      • 待办事项列表
    • footer
      • 待办事项条目数
      • 待办事项筛选项(全部,待办, 完成)

接下来用我们前端学过的vue3的api完成todolist功能清单

功能1: 添加待办事项

分析: 添加待办事项文本框,需要绑定一个数据,当用户输入时记录下待办事项,回车后,将该条数据存入数组中,方便后面展示

<input
    class="new-todo"
    placeholder="What needs to be done?"
    autocomplete="off"
    autofocus
    v-model="input" // 用变量input使用v-model绑定用户输入数据
    @keyup.enter="addTodo" // 回车时调用添加todo方法addTodo
    >

我们可以在setup里面完成todo添加功能,但是学过compositionAPI优点,就是让同一交互功能使用函数包装起来,等需要的时候再用,基于这个思想,我们把添加todo功能代码封装在一个函数里面

// 1. 添加待办事项
const useAdd = todos => {
  // 使用ref 声明一个响应式基本数据,用于输入框的绑定,最后return出去
  const input = ref('')
  // 声明一个添加todo方法
  const addTodo = () => {
    const text = input.value && input.value.trim()

    if(text.length === 0) return;
    // 将todo存储到数组中,包括内容,和完成状态(添加时,未完成)
   // 注意: todos也是响应式数据,所以是todos.value
    todos.value.unshift({
      text,
      completed: false
    })
  }
  return {
    input,
    addTodo
  }
}

再在setup里面用上useAdd,声明一个响应式变量(todos),值为数组,收集添加的todo

...
setup () {
    // 用于todo的存储,这个数据变化了todolist列表也要刷新所以也是响应式数据,使用ref
    const todos = ref([])
    return {
      todos,
      ...useAdd(todos)
    }
  },
  ...

将收集到todos的todo渲染出来

    <ul class="todo-list">
       <li
         v-for="todo in todos" // 使用v-for指令遍历todos
         :key="todo"
         :class="{completed: todo.completed }" // 用todo完成状态控制类
       >
         <div class="view">
           <label>{{ todo.text }}</label> // 展示todo内容
         </div>
       </li>
     </ul>

好!!! 先完成添加待办事项 image.png

删除todo

找到todo列表,在todolist里面添加一个删除按钮,绑定一个click事件,调用删除todo方法 remove

<ul class="todo-list">
        <li
          v-for="todo in todos"
          :key="todo"
          :class="{completed: todo.completed }"
        >
          <div class="view">
            ...
            <label >{{ todo.text }}</label>
            <button class="destroy" @click="remove(todo)"></button> // 删除按钮
          </div>
          ...
        </li>
      </ul>

和添加一样,把删除功能进行封装成一个函数

const useRemove = todos => {
    const remove = todo => {
        const index = todos.value.indexOf(todo)
        todos.value.splice(index, 1)
    }
    
    return {
        remove // 返回remove方法给实例使用
    }
}

注意 todos 是响应式数据,需要.value食用。有点难受

编辑待办事项

分析: 这个功能稍微负责一些,如果我们觉得已经添加的待办事项输入错了,想要编辑,可以双击该待办事项,进入编辑输入框状态,并让输入框获取焦点(这个有些难,需要一个自定义指令),把原来待办事项回显到输入框中,然后可以编辑并保存。如果清空了,按回车则是删除该todo 总结:

  • 双击待办事项,展示编辑文本框
  • 按回车或者编辑文本框失去焦点,修改数据
  • 按esc取消编辑
  • 把编辑文本框清空按回车,删除这一项
  • 显示编辑文本框的时候获取焦点
// 3. 编辑待办项
const useEdit= remove => {
  // 用于缓存编辑之前的内容,如果取消编辑则使用改值,这个值不影响dom更新,不需要ref
  let beforeEditingText = ''
  // 编辑内容,绑定编辑输入文本框
  const editingTodo = ref(null)
  const editTodo = todo => {
    beforeEditingText = todo.text;
    editingTodo.value = todo
  }
  // 如果编辑时,内容全部清空则为删除
  const doneEdit = todo => {
    if(!editingTodo.value) return
    todo.text = todo.text.trim()
    todo.text || remove(todo) // 删除
    editingTodo.value = null
  }
  // 取消编辑方法
  const cancelEdit = todo => {
    editingTodo.value = null
    todo.text = beforeEditingText
  }

  return {
    editingTodo,
    editTodo,
    doneEdit,
    cancelEdit
  }
}

这里有remove方法需要传进来。找到遍历相关的位置,绑定编辑状态,绑定双击编辑事件对应方法editTodo。输入文本框绑定焦点事件,doneEdit方法,绑定esc事件绑定cancelEdit方法。

<ul class="todo-list">
        <li
          v-for="todo in todos"
          :key="todo" // 这里的key最好绑定todo对象
          :class="{ editing: todo === editingTodo }" // 编辑状态隐藏viewdom
        >
          <div class="view">
            <input class="toggle" type="checkbox" v-model="todo.completed">
            <label @dblclick="editTodo(todo)">{{ todo.text }}</label> // 绑定双击,editTodo
            <button class="destroy" @click="remove(todo)"></button>
          </div>
          <input
            class="edit"
            type="text"
            v-editing-focus="todo === editingTodo"
            v-model="todo.text"
            @keyup.enter="doneEdit(todo)" // 绑定enter事件删除todo
            @blur="doneEdit(todo)"
            @keyup.esc="cancelEdit(todo)"// 取消编辑
            >
        </li>
      </ul>

注意: 当双击文本框怎样让文本框出现光标,接下来处理

双击出现文本框,让文本框获取焦点

自定义指令

  • 第二个参数是对象时:

vue 2.x

Vue.directive('editingFocus', {
    bind(el, binding,vnode, prevVnode) {},
    inserted() {},
    update() {},
    componentUploadtes() {},
    unbind() {}
})

vue 3.0

Vue.directive('editingFocus', {
    beforeMount(el, binding,vnode, prevVnode) {},
    mounted() {},
    beforeUpdate() {},
    updated() {},
    beforeUnmount() {},
    unmounted() {}
})

两者查边是自定指令钩子函数被重命名,vue 3指令钩子和组件钩子函数保持一致,这样更容易理解,降低使用的心智负担。但是自定义指令钩子函数与组件钩子函数执行方式有些不同(什么鬼...)

  • 第二个参数是函数时,vue2.x 和 vue3没有区别
app.directive('editingFocus', (el, binding) => {
    binding.value && el.focus()
})

我们可以使用自定义指令是操作文本框的焦点,我们自定义指定名字就叫 v-editing-focus

    <input
    class="edit"
    type="text"
    v-editing-focus="todo === editingTodo"
    v-model="todo.text"
    @keyup.enter="doneEdit(todo)"
    @blur="doneEdit(todo)"
    @keyup.esc="cancelEdit(todo)"
    >
    directives: {
        editingFocus: (el, binding) => {
            binding.value && el.focus()
        }
        
    }

这样当绑定的值为true则让改元素触发焦点事件

完成三分之二,明天继续....