Vue3.0实现todoList案例

Vue3.0实现todolist案例

文章内容输出来源:大前端高薪训练营

代码地址:
https://gitee.com/jiailing/lagou-fed/tree/master/fed-e-task-03-05/code/04-todolist

在这里插入图片描述

1. ToDoList功能列表

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

2. 项目结构

使用vue脚手架创建Vue项目,先升级vue-cli,4.5.6版本的vue-cli创建项目时可以选择vue版本。

Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -gyarn global remove vue-cli 卸载它

可以使用下列任一命令安装这个新的包:

npm install -g @vue/cli
# OR
yarn global add @vue/cli

先使用vue create创建项目,创建的时候选择3.0。

vue create 04-todolist

然后选择Default (Vue 3 Preview) ([Vue 3] babel, eslint)

就会自动创建项目了。

3. 添加待办事项

在输入框输入文本按下enter键提交待办事项

// 1. 添加待办事项
const useAdd = todos => {
  const input = ref('')
  const addTodo = () => {
    const text = input.value?.trim()
    if (text.length === 0) return
    todos.value.unshift({
      text,
      completed: false
    })
    input.value = ''
  }
  return {
    input,
    addTodo
  }
}

4. 删除待办事项

点击待办事项右侧的叉号可以删除待办事项

// 2. 删除待办事项
const useRemove = todos => {
  const remove = todo => {
    const index = todos.value.indexOf(todo)
    todos.value.splice(index, 1)
  }
  return {
    remove
  }
}

5. 编辑待办事项

双击进入编辑状态,按esc退出编辑,按enter提交编辑,如果删光了文本,则为删除这一项。

  • 双击待办事项,展示编辑文本框
  • 按回车或者编辑文本框失去焦点,修改数据
  • 按esc取消编辑
  • 把编辑文本框清空按回车,删除这一项
  • 显示编辑文本框的时候获取焦点
// 3. 编辑待办事项
const useEdit = (remove) => {
  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()
    if (todo.text === '') remove(todo)
    editingTodo.value = null
  }
  const cancelEdit = todo => {
    editingTodo.value = null
    todo.text = beforeEditingText
  }
  return {
    editingTodo,
    editTodo,
    doneEdit,
    cancelEdit
  }
}

模板中:

<ul class="todo-list">
  <li v-for="todo in todos" :key="todo" :class="{editing: todo === editingTodo}">
    <div class="view">
      <input type="checkbox" class="toggle">
      <label @dblclick="editTodo(todo)">{{ todo.text }}</label>
      <button class="destroy" @click="remove(todo)"></button>
    </div>
    <input type="text" class="edit" v-model="todo.text" @keyup.enter="doneEdit(todo)" @blur="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
  </li>
</ul>

6. 编辑文本框获取焦点 - vue3.0自定义指令

传对象形式:

  • Vue 2.x

    Vue.directive('editingFocus', {
      bind(el, binding, vnode, prevVnode) {},
      inserted() {},
      update() {} // remove,
      componentUpdated() {},
      unbind() {}
    })
    
  • Vue 3.0

    app.directive('editingFocus', {
      beforeMount(el, binding, vnode, prevVnode) {},
      mounted() {},
      breforeUpdate() {}, // new
      updated() {},
      beforeUnmount() {}, // new
      unmounted() {}
    })
    

传函数形式:

  • Vue 2.x

    Vue.directive('editingFocus', (el, binding) => {
      binding.value && el.focus()
    })
    
  • Vue 3.0

    app.directive('editingFocus', (el, binding) => {
      binding.value && el.focus()
    })
    

代码实现自定义事件获取正在编辑的文本框焦点:

export default {
  name: 'App',
  
  // 省略了setup() {},
  
  directives: {
    editingFocus: (el, binding) => { // binding可以获取到一些参数
      binding.value && el.focus()
    }
  }
}

使用:

<input v-editing-focus="todo === editingTodo" type="text" class="edit" v-model="todo.text" @keyup.enter="doneEdit(todo)" @blur="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">

7. 切换待办事项

  • 点击CheckBox可以改变所有代办项状态
  • All/Active/Completed
  • 其他
    • 显示未完成代办项个数
    • 移除所有完成的项目
    • 如果没有待办项,隐藏main和footer
// 4. 切换待办项完成状态
const useFilter = todos => {
  const allDone = computed({
    get: () => {
      return !todos.value.filter(item => !item.completed).length
    },
    set: (value) => {
      todos.value.forEach(todo => {
        todo.completed = value
      })
    }
  })

  const filter = {
    all: list => list,
    active: list => list.filter(todo => !todo.completed),
    completed: list => list.filter(todo => todo.completed)
  }

  const type = ref('all')
  const filteredTodos = computed(() => filter[type.value](todos.value))

  const remainingCount = computed(() => filter.active(todos.value).length)

  const count = computed(() => todos.value.length)

  const onHashChange = () => {
    const hash = window.location.hash.replace('#/', '')
    if (filter[hash]) {
      type.value = hash
    } else {
      type.value = 'all'
      window.location.hash = ''
    }
  }

  onMounted(() => {
    window.addEventListener('hashchange', onHashChange)
    onHashChange()
  })

  onUnmounted(() => {
    window.removeEventListener('hashchange', onHashChange)
  })

  return {
    allDone,
    filteredTodos,
    remainingCount,
    count
  }
}

8. 本地存储

import useLocalStorage from './utils/useLocalStorage'
const storage = useLocalStorage()

// 5. 存储待办事项
const useStorage = () => {
  const KEY = 'TODOKEYS'
  const todos = ref(storage.getItem(KEY) || [])
  watchEffect(() => {
    storage.setItem(KEY, todos.value)
  })
  return todos
}

utils/useLocalStorage.js

function parse(str) {
  let value
  try {
    value = JSON.parse(str)
  } catch {
    value = null
  }
  return value
}

function stringify(obj) {
  let value
  try {
    value = JSON.stringify(obj)
  } catch {
    value = null
  }
  return value
}

export default function useLocalStorage() {
  function setItem(key, value) {
    value = stringify(value)
    window.localStorage.setItem(key, value)
  }
  function getItem(key) {
    let value = window.localStorage.getItem(key)
    if(value) {
      value = parse(value)
    }
    return value
  }
  return {
    setItem,
    getItem
  }
}

9. 完整代码

<template>
<section id="app" class="todoapp">
  <header class="header">
    <h1>todos</h1>
    <input type="text" class="new-todo" placeholder="What needs to be done?" autocomplete="off" autofocus v-model="input" @keyup.enter="addTodo">
  </header>
  <section class="main" v-show="count">
    <input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone">
    <label for="toggle-all">Mark all as complete</label>
    <ul class="todo-list">
      <li v-for="todo in filteredTodos" :key="todo" :class="{editing: todo === editingTodo, completed: todo.completed}">
        <div class="view">
          <input type="checkbox" class="toggle" v-model="todo.completed">
          <label @dblclick="editTodo(todo)">{{ todo.text }}</label>
          <button class="destroy" @click="remove(todo)"></button>
        </div>
        <input v-editing-focus="todo === editingTodo" type="text" class="edit" v-model="todo.text" @keyup.enter="doneEdit(todo)" @blur="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
      </li>
    </ul>
  </section>
  <footer class="footer" v-show="count">
    <span class="todo-count">
      <strong>{{ remainingCount }}</strong> {{remainingCount > 1 ? 'items' : 'item'}} left
    </span>
    <ul class="filters">
      <li><a href="#/all">All</a></li>
      <li><a href="#/active">Active</a></li>
      <li><a href="#/completed">Completed</a></li>
    </ul>
    <button class="clear-completed" @click="removeCompleted" v-show="count > remainingCount">Clear completed</button>
  </footer>
</section>
</template>

<script>
import './assets/index.css'
import useLocalStorage from './utils/useLocalStorage'
import {
  ref,
  computed,
  onMounted,
  onUnmounted,
  watchEffect
} from 'vue'

const storage = useLocalStorage()

// 1. 添加待办事项
const useAdd = todos => {
  const input = ref('')
  const addTodo = () => {
    const text = input.value?.trim()
    if (text.length === 0) return
    todos.value.unshift({
      text,
      completed: false
    })
    input.value = ''
  }
  return {
    input,
    addTodo
  }
}

// 2. 删除待办事项
const useRemove = todos => {
  const remove = todo => {
    const index = todos.value.indexOf(todo)
    todos.value.splice(index, 1)
  }

  const removeCompleted = () => {
    todos.value = todos.value.filter(todo => !todo.completed)
  }
  return {
    remove,
    removeCompleted
  }
}

// 3. 编辑待办事项
const useEdit = (remove) => {
  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()
    if (todo.text === '') remove(todo)
    editingTodo.value = null
  }
  const cancelEdit = todo => {
    editingTodo.value = null
    todo.text = beforeEditingText
  }
  return {
    editingTodo,
    editTodo,
    doneEdit,
    cancelEdit
  }
}

// 4. 切换待办项完成状态
const useFilter = todos => {
  const allDone = computed({
    get: () => {
      return !todos.value.filter(item => !item.completed).length
    },
    set: (value) => {
      todos.value.forEach(todo => {
        todo.completed = value
      })
    }
  })

  const filter = {
    all: list => list,
    active: list => list.filter(todo => !todo.completed),
    completed: list => list.filter(todo => todo.completed)
  }

  const type = ref('all')
  const filteredTodos = computed(() => filter[type.value](todos.value))

  const remainingCount = computed(() => filter.active(todos.value).length)

  const count = computed(() => todos.value.length)

  const onHashChange = () => {
    const hash = window.location.hash.replace('#/', '')
    if (filter[hash]) {
      type.value = hash
    } else {
      type.value = 'all'
      window.location.hash = ''
    }
  }

  onMounted(() => {
    window.addEventListener('hashchange', onHashChange)
    onHashChange()
  })

  onUnmounted(() => {
    window.removeEventListener('hashchange', onHashChange)
  })

  return {
    allDone,
    filteredTodos,
    remainingCount,
    count
  }
}

// 5. 存储待办事项
const useStorage = () => {
  const KEY = 'TODOKEYS'
  const todos = ref(storage.getItem(KEY) || [])
  watchEffect(() => {
    storage.setItem(KEY, todos.value)
  })
  return todos
}

export default {
  name: 'App',
  setup() {
    const todos = useStorage()
    const {
      remove,
      removeCompleted
    } = useRemove(todos)
    return {
      todos,
      remove,
      removeCompleted,
      ...useAdd(todos),
      ...useEdit(remove),
      ...useFilter(todos)
    }
  },
  directives: {
    editingFocus: (el, binding) => { // binding可以获取到一些参数
      binding.value && el.focus()
    }
  },
}
</script>

<style>
</style>

©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页