Day29 :【TypeScript 学起来】React + TypeScript 实作简单 Todo App Part2

今天继续 todo app part2, 会纪录实作上遇到的问题。

若有错误,欢迎留言指教,感恩的心。


TodoItem Component

新增 src/components/todos/TodoItem.tsx

//@file: src/components/todos/TodoItem.tsx
import styled from "styled-components";

//定义Props介面,todo 需符合Todo的形状
interface Props {
    todo: Todo; //已在types.d.ts 全域定义
    index: number;
    onToggleTodo: ToggleTodo;
    onDeleteTodo: DeleteTodo;
}

//定义completed的style, 定义completed回传值会是boolean型别,如果是true则划线, false的话none
const StyledTodo = styled.span<{ completed: boolean }>`
    text-decoration: ${(props) => (props.completed ? "line-through" : "none")};
`;

//React.FC<Props> 定义这个 FunctionComponent为Props泛型
const TodoItem: React.FC<Props> = ({
    index,
    todo,
    onToggleTodo,
    onDeleteTodo,
}) => {
    return (
        <div className="form-check border border-bottom-secondary rounded py-3 m-0 d-flex justify-content-between align-items-center">
            <div>
                <input
                    className=" ms-1 me-3 form-check-input"
                    type="checkbox"
                    checked={todo.complete}
                    onClick={() => {
                        onToggleTodo && onToggleTodo(index);
                    }}
                />
                <StyledTodo className="todo" completed={todo.complete}>
                    {todo.text}
                </StyledTodo>
            </div>

            <button
                className="btn btn-outline-danger me-3"
                onClick={() => onDeleteTodo && onDeleteTodo(index)}
            >
                X
            </button>
        </div>
    );
};

export default TodoItem;


我遇到的一些事:

  • React.FC<>的在TypeScript使用的一个泛型,FC就是FunctionComponent的缩写,是函式组件。

  • React.FC<Props>React.FC会依照Props定义好的属性型别带入参数,有点像平时我们写function带入参数的概念一样。

  • 觉得TS 真的蛮严谨, 像 StyledTodo , 原本是想直接带入直接使用 props.completed, 但会报错, 他需要去定义styled.span<{ completed: boolean }> 传入值是boolean型别。

  • 然後是说我太久没用bootstrap, v5 原本的 mr (margin-right), 改为 me(margin-end),right 改为e (end),left 则改为 s (start)。


AddTodoInput Component

新增 src/components/todos/AddTodoInput.tsx

//@file: src/components/todos/AddTodoInput.tsx

import React, { FormEvent, useState } from "react";

interface Props {
    onCreate: IAddTodo;
}

const AddTodoInput: React.FC<Props> = ({ onCreate }) => {
    const [text, setText] = useState("");

    const handleSubmit = (e: FormEvent) => {
        e.preventDefault();
        onCreate && onCreate(text);
        setText("");
    };

    return (
        <form className="input-group mb-5" onSubmit={handleSubmit}>
            <input
                className="form-control border-primary"
                type="text"
                value={text}
                onChange={(e) => setText(e.target.value)}
            />
            <button
                className="btn btn-primary"
                type="submit"
                onClick={handleSubmit}
                disabled={!text}
            >
                Add Todo
            </button>
        </form>
    );
};

export default AddTodoInput;

我遇到的一些事:

  • onCreate 也需要标记型别, callback function 也需要定义型别。
  • handleSubmit 需要本来以为直接用e就可以了, 结果不行, 需定义 event 类型,可参考 React’s Event System,我这边使用的 onSubmit属於 FormEvent,所以针对e去定义。这个event分类比我想像中的多, 突然觉得 JS 好幸福写e就好惹。

Home Page

新增 src/pages/Home.tsx

//@file: src/pages/Home.tsx
import { useState } from "react";
import AddTodoInput from "../components/todos/AddTodoInput";
import TodoItem from "../components/todos/TodoItem";
import { v4 as uuid } from "uuid";

const initialTodos: Todo[] = [
    {
        text: "walk the dog",
        complete: true,
    },
    {
        text: "learn TypeScript",
        complete: false,
    },
];

const Home = () => {
    const [todos, setTodos] = useState(initialTodos);

    const addTodo: AddTodo = (text: string) => {
        const newTodo = { text, complete: false };
        setTodos([...todos, newTodo]);
    };

    const toggleTodo: ToggleTodo = (index: number) => {
        const newTodos: Todo[] = [...todos];
        newTodos[index].complete = !newTodos[index].complete;
        setTodos(newTodos);
    };

    const deleteTodo: DeleteTodo = (index: number) => {
        setTodos([...todos.slice(0, index), ...todos.slice(index + 1)]);
    };

    return (
        <main className="pt-5 mx-auto">
            <div className="container">
                <AddTodoInput onAddTodo={addTodo} />
                {todos.map((todo, index) => {
                    return (
                        <TodoItem
                            todo={todo}
                            key={uuid()}
                            index={index}
                            onToggleTodo={toggleTodo}
                            onDeleteTodo={deleteTodo}
                        />
                    );
                })}
            </div>
        </main>
    );
};

export default Home;

我遇到的一些事:

  • initialTodos 也需要定义型别,因为他是个 array ,我们可以给他 Todo[]型别。
  • AddTodo ToggleTodo DeleteTodo 可在 types.d.ts 定义好,比起在 function 里面定义更乾净。

App.tsx

这边也记得改一下:

import Home from "./pages/Home";

const App = () => {
    return <Home />;
};

export default App;


完成了~

这就完成简单的 Todo App 了, 也同步在 github 可参考。


day29 done. 明天最後一天了~~有点感动!!!


参考资料

https://typeofnan.dev/your-first-react-typescript-project-todo-app/


<<:  IT 铁人赛 k8s 入门30天 -- day30 Share Process Namespace between Containers in a Pod

>>:  Day29 Session 的使用-2

AE卷轴制作5-Day6

1.将要遮罩的Shape>Pre compose 2.最後就是最简单的部分,找张图用遮罩就完成...

【Day 25】Google Apps Script - API Blueprint 篇 - 执行专案取得 .apib 档

执行 Google Docs 转换 API Blueprint 格式专案程序,最後来看看转换後的 ...

[Day3] Rust 函数 基本 / 进阶 使用

那麽最一开始学一个程序语言的起手式想必不用我多说吧。 「Hello World!」 fn hello...

[Day 20] - 初探永丰银行线上收款API - 订单查询及其他(2)

昨晚睡一睡突然一个念头闪过, 为什麽用那麽多try catch 昨天写的程序中,我用了大量的try ...

Day09 - 物件要解构成 primitive 再传入 props ,小心 by reference 动到不该动的兄弟

今天念 重新认识 Vue.js | Kuro Hsu 2-2 元件之间的沟通传递 在 HTML 里的...