从命令式到声明式:用 Vue 3 构建任务清单的开发哲学

从命令式到声明式:用 Vue 3 构建任务清单的开发哲学

在前端开发演进的历程中,开发者逐渐从"操作 DOM"转向"管理状态"。一个看似简单的任务清单(Todos)应用,恰恰是理解这一范式转变的绝佳入口。本文将结合 Vue 3 的 Composition API、响应式系统与模板指令,深入剖析现代前端开发的核心思想------数据驱动 UI


一、传统开发 vs Vue 开发:两种思维模式

在没有框架的时代,我们这样写任务输入功能:

ini 复制代码
const app = document.getElementById('app');
const input = document.getElementById('todo-input');

input.addEventListener('change', (e) => {
  const value = e.target.value.trim();
  if (value) app.innerHTML = value;
});

这种方式称为 命令式编程

  • 先获取 DOM 元素
  • 再监听事件
  • 最后手动修改 DOM

问题显而易见:

  • 代码与 DOM 强耦合
  • 难以维护复杂交互
  • 性能差(频繁操作慢速的渲染引擎)

而 Vue 的做法完全不同:

不再思考"页面元素怎么操作",而是思考"数据如何变化"

你只需定义:

  • 当前标题是什么?
  • 任务列表有哪些?
  • 哪些任务已完成?

Vue 自动将这些数据映射到界面,并在数据变化时高效更新 DOM。这就是 声明式编程 的魅力。


二、Vue 3 Composition API:逻辑更清晰

App.vue 中,我们使用 <script setup> 语法,这是 Vue 3.2+ 推荐的组合式 API 写法:

php 复制代码
import { ref, computed } from 'vue';

const title = ref(""); // 响应式字符串
const todos = ref([
  { id: 1, title: '打王者', done: false },
  { id: 2, title: '吃饭', done: true }
]);
  • ref() 将普通值包装为响应式对象。
  • 所有逻辑集中在 setup 中,避免 Options API 的碎片化。

相比 Vue 2 的 data()methodscomputed 分散在不同选项,Composition API 让相关功能内聚,便于复用与测试。


三、核心指令:用声明式语法描述 UI

Vue 的模板通过指令(v- 开头)实现声明式渲染:

1. v-model:双向绑定

ini 复制代码
<input v-model="title" @keydown.enter="addTodo">
<input type="checkbox" v-model="todo.done">
  • 输入框内容 ↔ title
  • 复选框状态 ↔ todo.done

无需手动监听 inputchange 事件。

2. v-for:列表渲染

ini 复制代码
<li v-for="todo in todos" :key="todo.id">
  • 自动遍历 todos 数组
  • :key 提供唯一标识,提升 Diff 算法效率

3. v-if / v-else:条件渲染

xml 复制代码
<ul v-if="todos.length">...</ul>
<div v-else>暂无计划</div>
  • 根据数据是否存在决定显示内容

4. 动态 class 绑定

css 复制代码
<span :class="{ done: todo.done }">{{ todo.title }}</span>
  • done 为真,应用 .done 样式(灰色 + 删除线)

四、计算属性:智能派生状态

任务清单需要实时显示"未完成任务数"和"全选"功能。Vue 用 computed 完美解决:

1. 只读计算属性:未完成数量

ini 复制代码
const active = computed(() => {
  return todos.value.filter(todo => !todo.done).length;
});
  • 自动依赖 todos
  • 结果缓存,仅在 todos 变化时重新计算
  • 模板中直接使用 {{ active }}

2. 可读写计算属性:全选控制

ini 复制代码
const allDone = computed({
  get() {
    return todos.value.every(todo => todo.done);
  },
  set(val) {
    todos.value.forEach(todo => todo.done = val);
  }
});
  • get:判断是否全部完成
  • set:当用户点击"全选"复选框时,批量更新所有任务状态

这体现了 Vue 对"属性"的扩展------不仅是值,还可以包含行为。


五、事件处理:简洁而高效

添加任务只需一行逻辑:

ini 复制代码
const addTodo = () => {
  if (!title.value) return;
  todos.value.push({
    id: Math.random(), 
    title: title.value,
    done: false
  });
  title.value = ''; // 清空输入框
};

配合 @keydown.enter,用户按回车即可提交,体验流畅。


六、开发哲学:聚焦数据,而非 DOM

正如项目注释所言:

"VUE 做法不再需要思考页面的元素怎么操作,而是要思考数据是怎么变化的。"

这种转变带来三大优势:

  1. 开发效率高:无需手动操作 DOM,减少样板代码
  2. 可维护性强:逻辑围绕数据组织,结构清晰
  3. 性能更优:Vue 内部使用虚拟 DOM 和响应式系统,自动优化更新

对新手友好,对老手高效------这正是 Vue "渐进式框架"理念的体现。


七、结语

一个简单的任务清单,浓缩了 Vue 3 的精华:

  • 响应式系统:数据变,视图自动更新
  • Composition API:逻辑聚合,易于复用
  • 声明式模板:直观表达 UI 与状态关系
  • 计算属性:智能、缓存、响应式的派生数据

它不仅是一个功能 demo,更是一种思维方式的启蒙:前端开发的本质,是管理状态,而非操作元素

未来,你可以在此基础上扩展:

  • 本地存储(localStorage)
  • 过滤视图(全部/未完成/已完成)
  • 动画过渡
  • TypeScript 类型支持

而这一切,都始于对"数据如何流动"的思考。


附录:完整的App.vue代码

vue 复制代码
<template>
  <div>
    <h1>任务清单</h1>
    <h2>{{ title }}</h2>
    <input type="text" v-model="title" @keydown.enter="addTodo">
    <ul v-if="todos.length">
      <li v-for="todo in todos" :key="todo.id">
        <input type="checkbox" v-model="todo.done">
        <span :class="{done: todo.done}">{{ todo.title }}</span>
      </li>
    </ul>
    <div v-else>
      暂无计划
    </div>
    <div>
      全选<input type="checkbox" v-model="allDone">
      {{ active }}
      /
      {{ todos.length }}
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const title = ref("")
const todos = ref([
  {
    id: 1,
    title: '打王者',
    done: false
  },
  {
    id: 2,
    title: '吃饭',
    done: true
  }
])

const active = computed(() => {
  return todos.value.filter(todo => !todo.done).length
})

const addTodo = () => {
  if (!title.value) return;
  todos.value.push({
    id: Math.random,
    title: title.value,
    done: false
  })
  title.value = ''
}

const allDone = computed({
  get() {
    return todos.value.every(todo => todo.done)
  },
  set(val) {
    todos.value.forEach(todo => todo.done = val)
  }
})
</script>

<style>
  .done {
    color: gray;
    text-decoration: line-through;
  }
</style>
相关推荐
研☆香2 小时前
深入解析JavaScript的arguments对象
开发语言·前端·javascript
parksben2 小时前
告别 iframe 通信的 “飞鸽传书”:Webpage Tunnel 上手指南
前端·javascript·前端框架
全栈前端老曹2 小时前
【前端权限】 权限变更热更新
前端·javascript·vue·react·ui框架·权限系统·前端权限
写代码的皮筏艇2 小时前
react中的useCallback
前端·javascript
水冗水孚2 小时前
通俗易懂地谈谈,前端工程化之自定义脚手架的理解,并附上一个实践案例发布到npm上
javascript·npm·node.js
页面魔术3 小时前
⭐看完vite纪录片才知道尤大有多屌(上)
前端·javascript·vue.js
UpgradeLink3 小时前
Electron 项目使用官方组件 electron-builder 进行跨架构打包
前端·javascript·electron
Moment3 小时前
别再让 JavaScript 抢 CSS 的活儿了,css原生虚拟化来了
前端·javascript·css
晓得迷路了3 小时前
栗子前端技术周刊第 110 期 - shadcn/create、Github 更新 npm 令牌政策、Deno 2.6...
前端·javascript·css