Vue3.0实现todoList案例

2,342 阅读3分钟

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

1. ToDoList功能列表

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

2.项目构建

使用vue脚手架创建项目,先升级vue-cli到4.5以上的版本,新版本的vue-cli创建项目时可以选择vue版本

npm update -g @vue/cli
# OR
yarn global upgrade --latest @vue/cli

如果已经全局安装了旧版本的vue-cli(1.x或者2.x), 你需要先通过npm uninstall vue-cli -gyarn global remove vue-cli卸载它,然后再使用npm install -g @vue/cliyarn global add @vue/cli安装新的包 然后就可以使用vue create创建项目了:

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 && 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()
    todo.text || remove(todo)
    editingTodo.value = null
  }
  const cancelEdit = todo => {
    editingTodo.value = null
    todo.text = beforeEditingText
  }
  return {
    editingTodo,
    editTodo,
    doneEdit,
    cancelEdit
  }
}

模板中:

<li
  v-for="todo in todos"
  :key="todo"
  :class="{ editing: todo === editingTodo }"
>
  <div class="view">
    <input class="toggle" type="checkbox">
    <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>

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

传对象形式:

  • Vue2.x
Vue.directive('editingFocus', {
  bind(el,binding, vnode, prenode) {},
  inserted() {},
  updatew() {}, // remove
  componentUpdated() {},
  unbind() {}
})
  • Vue3.0
app.directive('editingFocus', {
  beforeMount(el, binding, vnode, prevnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // new
  unmounted() {}
})

传函数形式:

  • Vue2.x
Vue.directive('editingFocus', (el, binding) => {
  binding.value && el.focus()
})
  • Vue3.0(函数在bind和updated之间执行,el是指令所绑定的元素,binding.value是指令对应的值)
app.directive('editingFocus',(el, binding) => {
  binding.value && el.focus()
})

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

export default {
  name: 'App',
  setup() {
    const todos = ref([])
    const { remove } = useRemove(todos) // 编辑待办事项需要使用到remove函数,所以单独解构出来
    return {
      todos,
      remove,
      ...useAdd(todos),
      ...useEdit(remove)
    }
  },
  directives: {
    editingFocus: (el, binding) => {
      binding.value && el.focus()
    }
  }
}

使用:

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

7. 切换待办事项

  • 点击CheckBox可以改变所有待办项状态
  • All/Active/Completed
  • 其他
    • 显示未完成待办项个数
    • 移除所有完成的项目
    • 如果没有待办项,隐藏main和footer
// 2.删除待办
const useRemove = todos => {
  const remove = todo => {
    const index = todos.value.indexOf(todo)
    todos.value.splice(index, 1)
  }
  const removeCompleted = () => { // 移除所有完成的项目,需要再setup中解构并导出
    todos.value = todos.value.filter(todo => !todo.completed)
  }
  return {
    remove,
    removeCompleted
  }
}

 // 4.切换待办事项的完成状态
const useFilter = todos => {
  const allDone = computed({ // 全选切换
    get() {
      return todos.value.every(todo => todo.completed)
    },
    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 toggleFilterType = filterType => { // 切换过滤类型
    type.value = filterType
  }
  return {
    allDone,
    toggleFilterType,
    filteredTodos,
    remainingCount,
    count
  }
}   

使用:

<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> -->
    <li @click="toggleFilterType('all')"><a>All</a></li>
    <li @click="toggleFilterType('active')"><a>Active</a></li>
    <li @click="toggleFilterType('completed')"><a>Completed</a></li>
  </ul>
  <button class="clear-completed" @click="removeCompleted" v-show="count > remainingCount">
    Clear completed
  </button>
</footer>

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
  }
}