ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

使用 TypeScript + Vite 编写原生 TODO

2021-06-28 01:04:16  阅读:262  来源: 互联网

标签:TypeScript const target ITodoData TODO HTMLElement todo id Vite


介绍

如何编写一个 TODO ?

  1. 外观模式
  2. 数据分离

设计方案

传统写法

绑定事件处理函数 - 数据
    1. 增加项 - 列表数据 -> 增加一项
        { id: timestamp, content: string, completed: false }
        每一项的视图 -> 列表
    2. 删除项 - 列表数据 -> id -> removeItem
        将对应项的视图 -> 列表 -> 删除
    3. 改变完成状态 - 列表数据 -> id -> change completed
        将对应项的完成状态 -> 是否完成 toggle

面向对象、类的继承、横向切割程序

程序进行分类  
    外层:浏览器的事件 -> 调用方法 -> 事件处理函数的绑定  
    操作数据:addTodo、removeTodo、toggleComplete  
    操作DOM:addItem、removeItem、changeCompleted  
    管理模版:todoView -> 接受参数

项目初始化

初始化

$ mkdir todo
$ cd todo
$ npm init -y
$ npm i vite -D

然后修改 package.json 中的运行命令为 vite

  // package.json
  "scripts": {
    "dev": "vite"
  },

运行

现在可以直接运行项目了

$ npm run dev

当然,还需创建以下目录

todo
 ├── index.html
 └── src
     ├── app.ts
     └── js
         ├── TodoDom.ts  // todo dom 操作
         ├── utils.ts  // 工具类
         ├── TodoEvent.ts  // todo 事件
         ├── TodoTemplate.ts  // 渲染 todo-item 的 DOM 模板
         └── typing.ts  // 接口

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo</title>
</head>
<body>
    <div class="app">
        <div class="todo-input">
            <input type="text" placeholder="请输入待办事项">
            <button>增加</button>
        </div>
        <div class="todo-list"></div>
    </div>
</body>
<script type="module" src="./src/app.ts"></script>
</html>

src/app.ts

import {ITodoData} from "./js/typing";
import {TodoEvent} from "./js/TodoEvent"

((doc) => {
    const dInput: HTMLInputElement = document.querySelector('input')  // input 输入框
    const dAddBtn: HTMLElement = document.querySelector('button')  // button 增加按钮
    const dTodoList: HTMLElement = document.querySelector('.todo-list')  // todo 列表

    // 初始数据
    const todoData: ITodoData[] = [
        {
            id: 1,
            content: '123',
            completed: true,
        },
        {
            id: 2,
            content: '456',
            completed: false,
        },
        {
            id: 3,
            content: '789',
            completed: true,
        }
    ]

    const todoEvent: TodoEvent = new TodoEvent(todoData, dTodoList)

    const init = (): void => {
        bindEvent()
    }

    function bindEvent(): void {
        dAddBtn.addEventListener('click', handleAddBtnClick, false)
        dTodoList.addEventListener('click', handleListClick, false)
    }

    // 添加操作
    function handleAddBtnClick(): void {
        const value: string = dInput.value.trim()

        if (value.length) {
            const result: undefined | number = todoEvent.addTodo(<ITodoData> {
                id: new Date().getTime(),
                content: value,
                completed: false,
            })

            if (result && result === 1001) {
            alert('列表项已存在')
                dInput.select()
                return
            }

            dInput.value = ''
            dInput.focus()
        }
    }

    // 每个 todo 子项的状态改变、删除事件
    function handleListClick(e: MouseEvent): void {
        const tar = e.target as HTMLElement
        const tagName = tar.tagName.toLocaleLowerCase()

        if (tagName === 'input' || tagName === 'button') {
            const id = parseInt(tar.dataset.id)
            switch (tagName) {
                case "input":
                    todoEvent.toggleComplete(tar, id)
                    break
                case "button":
                    todoEvent.removeTodo(tar, id)
                    break
                default:
                    break
            }
        }
    }

    init()
})(document)

src/js/TodoEvent.ts

import {ITodoData} from "./typing";
import {TodoDom} from "./TodoDom";

class TodoEvent extends TodoDom{
    private todoData: ITodoData[]

    constructor(todoData: ITodoData[], todoWrapper: HTMLElement) {
        super(todoWrapper)
        this.todoData = todoData
        this.init()
    }

    private init() {
        this.initList(this.todoData)
    }

    public addTodo(todo: ITodoData): undefined | number {
        const _todo: null | ITodoData = this.todoData.find((item: ITodoData) => item.content === todo.content)  // 去重

        if (!_todo) {
            this.todoData.push(todo)
            this.addItem(todo)
            return
        }

        return 1001
    }

    public removeTodo(target: HTMLElement, id: number): void {
        this.todoData = this.todoData.filter((todo: ITodoData) => todo.id !== id)  // 删除
        this.removeItem(target)
    }

    public toggleComplete(target: HTMLElement, id: number): void {
        this.todoData = this.todoData.map((todo: ITodoData) => {
            if (todo.id === id) {
                todo.completed = !todo.completed
                this.changeCompleted(target, todo.completed)
            }
            return todo
        })
    }
}

export {
    TodoEvent
}

src/js/TodoDom.ts

import {ITodoData} from "./typing";
import {TodoTemplate} from "./TodoTemplate";
import {createItem, findParentNode} from "./utils";

class TodoDom extends TodoTemplate{
    private todoWrapper: HTMLElement

    constructor(todoWrapper: HTMLElement) {
        super()
        this.todoWrapper = todoWrapper
    }

    protected initList(todoData: ITodoData[]) {
        todoData.map(todo => this.addItem(todo))
    }

    protected addItem(todo: ITodoData) {
        const dItem: HTMLElement = createItem('div', 'todo-item', this.todoView(todo))
        this.todoWrapper.appendChild(dItem)
    }

    protected removeItem(target: HTMLElement) {
        const dParentNode: HTMLElement = findParentNode(target, 'todo-item')
        dParentNode.remove()
    }

    protected changeCompleted(target: HTMLElement, completed: boolean) {
        const dParentNode: HTMLElement = findParentNode(target, 'todo-item')
        const dContent: HTMLElement = dParentNode.querySelector('span')

        dContent.style.textDecoration = completed ? 'line-through' : 'none'
    }
}

export {
    TodoDom
}

src/js/TodoTemplate.ts

import {ITodoData} from "./typing";

class TodoTemplate {
    protected todoView({id, content, completed}: ITodoData): string {
        return `
            <input type="checkbox" ${completed ? 'checked': ''} data-id="${id}"/>
            <span style="text-decoration: ${completed ? 'line-through': 'none'}">${content}</span>
            <button data-id="${id}">删除</button>
        `
    }
}

export {
    TodoTemplate
}

src/js/typing.ts

interface ITodoData {
    id: number
    content: string
    completed: boolean
}

export {
    ITodoData
}

src/js/utils.ts

/**
 * 寻找当前父节点
 * @param target
 * @param className
 */
function findParentNode(target: HTMLElement, className: string): HTMLElement {
    while (target = target.parentNode as HTMLElement) {
        if (target.className === className) {
            return target
        }
    }
}

/**
 * 创建节点
 * @param tagName
 * @param className
 * @param todoItem
 */
function createItem(tagName: string, className: string, todoItem: string): HTMLElement {
    const dItem: HTMLElement = document.createElement(tagName)
    dItem.className = className
    dItem.innerHTML = todoItem

    return dItem
}


export {
    findParentNode,
    createItem,
}

地址

最后对应的 GitHub 地址:https://github.com/Pooc-J/todo

内容来源 B 站小野:https://www.bilibili.com/video/BV1Jt4y1k7dS

标签:TypeScript,const,target,ITodoData,TODO,HTMLElement,todo,id,Vite
来源: https://www.cnblogs.com/pooc/p/14942798.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有