持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
前言
- 在github上偶然间看到一个比较有意思的项目todomvc。
- 作为开发人员,通过一个比较小的项目,来快速学习和了解一个新的框架,帮助我们做技术选型,是比较友好的。
- todomvc做的就是这样的工作。这个项目是做一个简单的todolist列表,完成增删改查的需求。官网上有各种框架做这个需求的代码示例。
- 我也是突发奇想,将todomvc的静态页面搞下来,准备使用这个小项目来学习各种框架。在这里学习vue3刚好用上。
- 我的GitHub地址:github.com/ynzy/todo-m…
todomvc项目需求
既然作为一个小项目来做,我们先来分析下需求。 根据上面的动图,我们可以看到有这样几个需求。
- 头部
- 点击输入框可以添加todo事项(按enter键触发添加)
- 点击左侧下拉菜单可以完成所有todo(有todos时显示)
- todos列表
- 显示所有todos事项列表
- 点击待办事项按钮,完成代办事项(completed)
- todo和completed事项可以删除
- 双击事项可以修改内容(按enter,blur键修改内容,escape键取消修改)
- 注意:li上的状态标识:
''
进行中completed
已完成editing
编辑
- 底部
- 显示todo事项条数(多条显示items,一条显示item)
- 切换显示状态(所有、进行中、已完成)
- 清空completed事项
- 本地存储:todos数据使用storage进行存储
需求分析做完了,根据这些需求,我们来完成我们的项目。
静态页面准备
- 静态页面我们可以直接用todomvc项目的,我把英文改成了中文,html如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>todo静态页</title>
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css/index.css" />
</head>
<body>
<section id="app" class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="你需要做什么" autocomplete="off" autofocus />
</header>
<section class="main">
<input id="toggle-all" class="toggle-all" type="checkbox" />
<label for="toggle-all">点击完成所有事项</label>
<ul class="todo-list">
<li class="completed">
<div class="view" style="display: block">
<input class="toggle" type="checkbox" />
<label>内容</label>
<button class="destroy"></button>
</div>
<!-- <input class="edit" type="text" /> -->
</li>
<li class="">
<div class="view" style="display: block">
<input class="toggle" type="checkbox" />
<label>内容</label>
<button class="destroy"></button>
</div>
<!-- <input class="edit" type="text" /> -->
</li>
<li class="editing">
<div class="view">
<input class="toggle" type="checkbox" />
<label>内容</label>
<button class="destroy"></button>
</div>
<input class="edit" type="text" />
</li>
</ul>
</section>
<footer class="footer">
<span class="todo-count">进行中<strong>0</strong>items left </span>
<ul class="filters">
<li><a href="#/all" class="selected">显示所有</a></li>
<li><a href="#/active">进行中</a></li>
<li><a href="#/completed">已完成</a></li>
</ul>
<button class="clear-completed">清除已完成</button>
</footer>
</section>
<footer class="info">
<p>双击编辑todo事项</p>
<!-- Change this out with your name and url ↓ -->
<p>创建者<a href="https://zce.me">zce</a></p>
<p>编辑者<a href="http://evanyou.me">Evan You</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</body>
</html>
复制代码
创建vue实例
接下来我们引入vue3,并创建vue实例
- index.html
<html lang="en">
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<section id="app" class="todoapp">
...此处省略
</section>
</body>
<script src="https://unpkg.com/vue@next"></script>
<script type="module" src="./index.js"></script>
</html>
复制代码
- index.js
const { createApp, reactive, toRefs, computed, watchEffect, onMounted, onUnmounted } = Vue
createApp({
setup() {
}
}).mount('#app')
复制代码
样式中我添加了v-cloak
,这是vue中在不使用脚手架情况下,用于隐藏未编译的模板,直到完成,然后再渲染页面。 接下来我们来完成对应的逻辑。 代码要写到setup
中并返回哦,我这里只列举对应的操作,完整代码可以看我github源码
todos数据操作
我们先准备好对todos数据的操作逻辑。
- 简单封装了本地设置和获取todo数据的方法
const storage = {
get: () => JSON.parse(localStorage.getItem('latest_todos') || '[]'),
set: value => localStorage.setItem('latest_todos', JSON.stringify(value))
}
复制代码
- 根据状态类型过滤显示数据:显示所有all、进行中active、已完成completed
const filters = {
all: todos => todos,
active: todos => todos.filter(todo => !todo.completed),
completed: todos => todos.filter(todo => todo.completed)
}
复制代码
- 初始化进入页面时初始化todos数据从本地获取,每次更新todos数据时,同步修改本地数据
const todos = ref(storage.get())
// 自动收集依赖,调用一次回调,通过 Proxy 监视哪些被使用
watchEffect(() => {
storage.set(todos.value)
})
复制代码
头部逻辑
- 输入内容后,按enter键,讲数据保存到todos中
- 点击左侧按钮,可以将所有todo改成完成状态
<header class="header">
<h1>todos</h1>
<input v-model.trim="input" @keyup.enter="addTodo" class="new-todo" placeholder="你需要做什么" autocomplete="off" autofocus />
</header>
<section class="main">
<input v-model="allDone" id="toggle-all" class="toggle-all" type="checkbox" />
<label for="toggle-all">点击完成所有事项</label>
</section>
<script>
const input = ref('')
const addTodo = () => {
const text = input.value
if (!text) return
todos.value.push({ id: todos.value.length + 1, text, completed: false })
input.value = ''
}
const remaining = computed(() => filters.active(todos.value).length)
const allDone = computed({
get: () => !remaining.value,
set: value => {
todos.value.forEach(todo => {
todo.completed = value
})
}
})
</script>
复制代码
todos列表逻辑
- 显示的数据需要根据当前的状态过滤之后进行显示,也就是
filteredTodos
- 当前的状态
visibility
是通过监听浏览器的hash
值,当发生改变时,修改对应的状态。 - 编辑操作是当双击todo时,改变为编辑状态
<section class="main">
<ul class="todo-list">
<li v-for="todo in filteredTodos" :key="todo.id" :class="{completed: todo.completed, editing: editingTodo == todo}">
<div class="view">
<input v-model="todo.completed" class="toggle" type="checkbox" />
<label @dblclick="editTodo(todo)">{{todo.text}}</label>
<button @click="removeTodo" class="destroy"></button>
</div>
<input v-model.trim="todo.text" v-edit-focus="editingTodo == todo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.escape="cancelEdit(todo)" class="edit" type="text" />
</li>
</ul>
</section>
<script>
const visibility = ref('all')
const filteredTodos = computed(() => filters[visibility.value](todos.value))
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
onHashChange()
})
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
const onHashChange = () => {
const hash = window.location.hash.replace(/#\/?/, '')
if (filters[hash]) {
visibility.value = hash
} else {
visibility.value = 'all'
window.location.hash = ''
}
}
const editTodo = todo => {
editingTodo.value = todo
beforeEditText.value = todo.text
}
const removeTodo = todo => {
todos.value.splice(todos.value.indexOf(todo), 1)
}
const doneEdit = todo => {
if (!editingTodo.value) return
todo.text || removeTodo(todo)
editingTodo.value = null
}
const cancelEdit = todo => {
editingTodo.value = null
state.text = beforeEditText.value
}
</script>
复制代码
- vue3中,想要实现指令,目前为止的话,使用
setup
选项时,需要有directives
选项来实现指令。
createApp({
directives: {
editFocus: (el, { value }) => value && el.focus()
}
})
复制代码
底部逻辑
- 显示当前进行中的数量:通过过滤的方式来判断标识,vue3中去掉了过滤属性,可以使用方法来实现
<footer class="footer">
<span class="todo-count">进行中<strong>{{remaining}}</strong>{{pluralize(remaining)}} left </span>
<ul class="filters">
<li><a href="#/all" :class="{ selected: visibility === 'all' }">显示所有</a></li>
<li><a href="#/active" :class="{ selected: visibility === 'active' }">进行中</a></li>
<li><a href="#/completed" :class="{ selected: visibility === 'completed' }">已完成</a></li>
</ul>
<button v-show="todos.length > remaining" @click="removeCompleted" class="clear-completed">清除已完成</button>
</footer>
<script>
const pluralize = count => {
return count <= 1 ? 'item' : 'items'
}
const removeCompleted = () => {
todos.value = filters.active(todos.value)
}
</script>
复制代码
总结
- 我们在不依赖脚手架的情况下,完成整个todo-mvc的功能,也达到了我们的学习的目的。
- 如果我们想在非常老的项目(比如jsp)上使用vue3框架库就可以采用这种不使用脚手架的方式。