用 Lyt.js 构建 Todo 应用:完整教程

用 Lyt.js 构建 Todo 应用:完整教程

通过一个经典的 Todo 应用,学习 Lyt.js v6.6.0 的核心用法。

最终效果

  • 添加 Todo
  • 标记完成/未完成
  • 删除 Todo
  • 筛选(全部/已完成/未完成)
  • 统计待办数量

步骤 1:创建项目

bash 复制代码
# 使用 Lyt.js CLI 创建项目
npm create lyt@latest my-todo-app

cd my-todo-app
npm install
npm run dev

或者手动创建:

bash 复制代码
mkdir my-todo-app
cd my-todo-app
npm init -y
npm install @lytjs/core @lytjs/plugin-vite vite

# 创建目录结构
mkdir -p src/components

步骤 2:配置 Vite

typescript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import lyt from '@lytjs/plugin-vite'

export default defineConfig({
  plugins: [lyt()],
})
json 复制代码
// package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

步骤 3:创建入口文件

html 复制代码
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Todo App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>
typescript 复制代码
// src/main.ts
import { createApp } from '@lytjs/core'
import App from './App.lyt'

createApp(App).mount('#app')

步骤 4:定义 Todo 类型

typescript 复制代码
// src/types.ts
export interface Todo {
  id: number
  text: string
  completed: boolean
}

export type FilterType = 'all' | 'active' | 'completed'

步骤 5:创建 TodoItem 组件

html 复制代码
<!-- src/components/TodoItem.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'

const props = defineProps<{
  todo: Todo
}>()

const emit = defineEmits<{
  toggle: [id: number]
  delete: [id: number]
}>()
</script>

<template>
  <li class="todo-item" :class="{ completed: todo.completed }">
    <label>
      <input
        type="checkbox"
        :checked="todo.completed"
        on:change="emit('toggle', todo.id)"
      />
      <span>{{ todo.text }}</span>
    </label>
    <button class="delete-btn" on:click="emit('delete', todo.id)">×</button>
  </li>
</template>

<style>
.todo-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px;
  border-bottom: 1px solid #eee;
}

.todo-item.completed span {
  text-decoration: line-through;
  color: #999;
}

.delete-btn {
  background: none;
  border: none;
  color: #ff4d4f;
  font-size: 20px;
  cursor: pointer;
}
</style>

步骤 6:创建 TodoList 组件

html 复制代码
<!-- src/components/TodoList.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'
import TodoItem from './TodoItem.lyt'

const props = defineProps<{
  todos: Todo[]
}>()

const emit = defineEmits<{
  toggle: [id: number]
  delete: [id: number]
}>()
</script>

<template>
  <ul class="todo-list">
    <TodoItem
      each="todo in todos"
      :todo="todo"
      on:toggle="emit('toggle', $event)"
      on:delete="emit('delete', $event)"
    />
  </ul>
</template>

<style>
.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
</style>

步骤 7:创建 TodoInput 组件

html 复制代码
<!-- src/components/TodoInput.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'

const emit = defineEmits<{
  add: [text: string]
}>()

let inputText = ''

function handleSubmit() {
  const text = inputText.trim()
  if (text) {
    emit('add', text)
    inputText = ''
  }
}
</script>

<template>
  <form class="todo-input" on:submit.prevent="handleSubmit">
    <input
      v-model="inputText"
      type="text"
      placeholder="添加新待办..."
    />
    <button type="submit">添加</button>
  </form>
</template>

<style>
.todo-input {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.todo-input input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 16px;
}

.todo-input button {
  padding: 10px 20px;
  background: #1890ff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

步骤 8:创建 TodoFilter 组件

html 复制代码
<!-- src/components/TodoFilter.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'
import type { FilterType } from '../types'

const props = defineProps<{
  filter: FilterType
  counts: { all: number; active: number; completed: number }
}>()

const emit = defineEmits<{
  'update:filter': [filter: FilterType]
}>()

const filters: FilterType[] = ['all', 'active', 'completed']
const labels = { all: '全部', active: '待完成', completed: '已完成' }
</script>

<template>
  <div class="todo-filter">
    <span class="count">{{ counts.active }} 项待完成</span>
    <div class="filter-buttons">
      <button
        each="f in filters"
        :class="{ active: filter === f }"
        on:click="emit('update:filter', f)"
      >
        {{ labels[f] }}
      </button>
    </div>
  </div>
</template>

<style>
.todo-filter {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  color: #666;
}

.filter-buttons {
  display: flex;
  gap: 8px;
}

.filter-buttons button {
  padding: 6px 12px;
  border: 1px solid #ddd;
  background: white;
  border-radius: 4px;
  cursor: pointer;
}

.filter-buttons button.active {
  background: #1890ff;
  color: white;
  border-color: #1890ff;
}
</style>

步骤 9:创建主 App 组件

html 复制代码
<!-- src/App.lyt -->
<script setup>
import { ref, computed } from '@lytjs/core'
import type { Todo, FilterType } from './types'
import TodoInput from './components/TodoInput.lyt'
import TodoList from './components/TodoList.lyt'
import TodoFilter from './components/TodoFilter.lyt'

let todos = ref<Todo[]>([])
let nextId = 1
let filter = ref<FilterType>('all')

const filteredTodos = computed(() => {
  switch (filter.value) {
    case 'active':
      return todos.value.filter(t => !t.completed)
    case 'completed':
      return todos.value.filter(t => t.completed)
    default:
      return todos.value
  }
})

const counts = computed(() => ({
  all: todos.value.length,
  active: todos.value.filter(t => !t.completed).length,
  completed: todos.value.filter(t => t.completed).length,
}))

function addTodo(text: string) {
  todos.value.push({
    id: nextId++,
    text,
    completed: false,
  })
}

function toggleTodo(id: number) {
  const todo = todos.value.find(t => t.id === id)
  if (todo) {
    todo.completed = !todo.completed
  }
}

function deleteTodo(id: number) {
  const index = todos.value.findIndex(t => t.id === id)
  if (index > -1) {
    todos.value.splice(index, 1)
  }
}
</script>

<template>
  <div class="app">
    <h1>我的待办</h1>
    <TodoInput on:add="addTodo" />
    <TodoList
      if="filteredTodos.length > 0"
      :todos="filteredTodos"
      on:toggle="toggleTodo"
      on:delete="deleteTodo"
    />
    <p else class="empty">暂无待办事项</p>
    <TodoFilter
      if="counts.all > 0"
      :filter="filter"
      :counts="counts"
      on:update:filter="filter = $event"
    />
  </div>
</template>

<style>
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: #f5f5f5;
}

.app {
  max-width: 500px;
  margin: 50px auto;
  padding: 20px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

h1 {
  text-align: center;
  color: #333;
  margin-bottom: 20px;
}

.empty {
  text-align: center;
  color: #999;
  padding: 20px;
}
</style>

步骤 10:运行项目

bash 复制代码
npm run dev

访问 http://localhost:5173,你应该能看到完整的 Todo 应用。


完整目录结构

css 复制代码
my-todo-app/
├── index.html
├── package.json
├── vite.config.ts
└── src/
    ├── main.ts
    ├── App.lyt
    ├── types.ts
    └── components/
        ├── TodoItem.lyt
        ├── TodoList.lyt
        ├── TodoInput.lyt
        └── TodoFilter.lyt

扩展练习

  1. 添加编辑功能:双击编辑 Todo 文字
  2. 持久化存储:使用 localStorage 保存数据
  3. 添加动画 :使用 <Transition> 添加过渡效果
  4. 统计面板:显示完成百分比

关键语法回顾

功能 Lyt.js 语法
条件渲染 if="condition"
列表渲染 each="item in list"
双向绑定 v-model="value" (模板中) / model="value" (Lyt.js)
事件绑定 on:click="handler"
Props defineProps<T>()
Emits defineEmits<T>()
响应式数据 ref() / reactive()
计算属性 computed()
相关推荐
阿黎梨梨4 小时前
跟 Git 打交道的正确姿势
前端
idcu4 小时前
深入 Lyt.js 路由系统:L6 生态系统层的核心
前端·typescript
七夜zippoe4 小时前
DolphinDB时间序列引擎:实时聚合计算
服务器·前端·时间序列·dolphindb·实时聚合
佳木逢钺4 小时前
pnpm 命令功能清单
前端
m0_738120724 小时前
渗透测试基础知识——从零认识JWT(JSON Web Token)身份令牌
服务器·前端·安全·web安全·网络安全·json
放下华子我只抽RuiKe54 小时前
React 从入门到生产(六):路由与导航
前端·人工智能·深度学习·react.js·前端框架·html·claude code
Sylus_sui4 小时前
实现:每行固定 5 个、自动换行、最后一行左对齐、数量不固定
前端·javascript·css
文滨4 小时前
10分钟搞定!Mac 配置 GitHub SSH 完全指南(小白也能看懂)
前端·macos·ssh·github
2601_958492554 小时前
7 WordPress Tools I Trust for Building a High-Traffic Magazine Site
前端·word