vue3入门07 - 不依赖脚手架完成todomvc

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

前言

  • 在github上偶然间看到一个比较有意思的项目todomvc
  • 作为开发人员,通过一个比较小的项目,来快速学习和了解一个新的框架,帮助我们做技术选型,是比较友好的。
  • todomvc做的就是这样的工作。这个项目是做一个简单的todolist列表,完成增删改查的需求。官网上有各种框架做这个需求的代码示例。
  • 我也是突发奇想,将todomvc的静态页面搞下来,准备使用这个小项目来学习各种框架。在这里学习vue3刚好用上。
  • 我的GitHub地址:github.com/ynzy/todo-m…

todomvc项目需求

既然作为一个小项目来做,我们先来分析下需求。 2022-06-01 08-06-55.2022-06-01 08_10_47.gif 根据上面的动图,我们可以看到有这样几个需求。

  • 头部
    • 点击输入框可以添加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框架库就可以采用这种不使用脚手架的方式。

猜你喜欢

转载自juejin.im/post/7105937537868562463