Skip to content

最佳实践

这里提供一个TodoList的示例,展示最佳实践。

index.tsx:

tsx
import { createRoot } from "j20";
import { App } from "./Todo";

// 用creatRoot创建应用根节点
const root = createRoot(() => <App></App>);

// 将根节点添加到DOM中
document.querySelector("#root")!.append(root.element);

Todo.tsx:

tsx
import { For } from "j20";
import { TodoItem } from "./TodoItem";

// 定义Todo项的类型
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

// 初始化一些示例数据
let $todos = [
  { id: 1, text: "学习j20框架", completed: false },
  { id: 2, text: "创建todolist应用", completed: false },
  { id: 3, text: "享受编程乐趣", completed: true },
];

// 添加新todo的函数
const addTodo = (text: string) => {
  if (text.trim() === "") return;

  $todos = [
    ...$todos,
    {
      id: Date.now(), // 使用时间戳作为唯一ID
      text: text.trim(),
      completed: false,
    },
  ];
};

// 删除todo的函数
const deleteTodo = (id: number) => {
  $todos = $todos.filter((todo) => todo.id !== id);
};

// 切换todo完成状态的函数
const toggleTodo = (id: number) => {
  $todos = $todos.map((todo) =>
    todo.id === id ? { ...todo, completed: !todo.completed } : todo
  );
};

// 清除所有已完成的todo
const clearCompleted = () => {
  $todos = $todos.filter((todo) => !todo.completed);
};

// 计算未完成的todo数量
const $activeCount = $todos.filter((todo) => !todo.completed).length;

// App组件
export const App = () => {
  let $newTodoText = ""; // 输入框的值

  const handleAddTodo = (e: Event & { target: HTMLInputElement }) => {
    addTodo(e.target.value);
    e.target.value = ""; // 清空输入框
    $newTodoText = ""; // 清空信号值
  };

  return (
    <div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-lg">
      <h1 class="text-3xl font-bold text-center mb-6 text-gray-800">
        Todo List
      </h1>

      {/* 添加新todo的输入框 */}
      <div class="mb-6">
        <div class="flex">
          <input
            type="text"
            placeholder="添加新的任务..."
            class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
            onInput={(e: Event & { target: HTMLInputElement }) => {
              $newTodoText = e.target.value;
            }}
            onKeyDown={(e: KeyboardEvent) => {
              if (e.key === "Enter" && $newTodoText.trim() !== "") {
                addTodo($newTodoText);
                $newTodoText = "";
              }
            }}
          />
          <button
            class="bg-blue-500 text-white px-4 py-2 rounded-r-lg hover:bg-blue-600 transition-colors"
            onClick={() => {
              if ($newTodoText.trim() !== "") {
                addTodo($newTodoText);
                $newTodoText = "";
              }
            }}
          >
            添加
          </button>
        </div>
      </div>

      {/* 统计信息 */}
      <div class="mb-4 text-gray-600">
        未完成: {$activeCount} | 总计: {$todos.length}
      </div>

      {/* Todo列表 */}
      <div class="mb-6">
        <For of={$todos}>
          {(todo, $index) => (
            <TodoItem
              text={`${todo.text}, 序号: ${$index}`}
              completed={todo.completed}
              onToggle={() => toggleTodo(todo.id)}
              onDelete={() => deleteTodo(todo.id)}
            />
          )}
        </For>
      </div>

      {/* 操作按钮 */}
      <div class="flex justify-between">
        <button
          class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition-colors"
          onClick={clearCompleted}
        >
          清除已完成
        </button>
        <div class="text-gray-600">
          全部: {$todos.length} | 已完成: {$todos.length - $activeCount}
        </div>
      </div>
    </div>
  );
};

TodoItem.tsx:

tsx
// 定义组件属性类型
interface TodoItemProps {
  text: string;
  completed: boolean;
  onToggle?: () => void;
  onDelete?: () => void;
}

// TodoItem组件
export const TodoItem = ($props: TodoItemProps) => {
  // 入参解构要使用 $ 开头别名,这样才是响应式的
  // 或者使用 $props.xx 直接取
  const {
    text: $text,
    completed: $completed,
    onToggle: $onToggle = () => {},
    onDelete: $onDelete = () => {},
  } = $props;

  return (
    <div class="flex items-center justify-between p-3 mb-2 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
      <div class="flex items-center">
        {/* 复选框 */}
        <input
          type="checkbox"
          class="h-5 w-5 mr-3 text-blue-500 rounded focus:ring-blue-400"
          checked={$completed}
          onChange={() => $onToggle()}
        />

        {/* Todo文本 */}
        <span
          class={$completed ? "line-through text-gray-500" : "text-gray-800"}
        >
          {$text}
        </span>
      </div>

      {/* 删除按钮 */}
      <button
        class="text-red-500 hover:text-red-700 transition-colors"
        onClick={() => $onDelete()}
      >
        删除
      </button>
    </div>
  );
};