【Typescript学习】使用 React 和 TypeScript 构建web应用(三)所有组件

教程来自freecodeCamp:【英字】使用 React 和 TypeScript 构建应用程序
跟做,仅记录用
其他资料:https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/


第三天

以下是视频(0:40-0:60) 的内容

目录

  • 第三天
    • 1 创建TodoList组件
    • 2 SingleTodo组件
    • 3 todo的按钮响应函数

1 创建TodoList组件

就是装载所有todo卡片container
【Typescript学习】使用 React 和 TypeScript 构建web应用(三)所有组件_第1张图片

App中以prop传入todos数组、setTodos方法
App.tsx

return (
    <div className="App">
      <span className='heading'>Taskify</span>
      <InputField todo={todo} setTodo={setTodo} handleAdd={handleAdd}/>
      <TodoList todos={todos} setTodos={setTodos}/>  
    </div>
  );

TodoList 中的每一个卡片是SingleTodo组件(在后文),TodoList组件的结构和UI如下
TodoList.tsx

import React from 'react'
import './styles.css'
import { Todo } from "../model";
import SingleTodo from './SingleTodo';
type Props = {
    todos: Todo[],
    setTodos: React.Dispatch<React.SetStateAction<Todo[]>>,
}

const TodoList:React.FC<Props> = ({todos, setTodos}: Props) => {
  return (
    <div className='todos'>
        {todos.map(todo =>(
            <SingleTodo key={todo.id}
                todo={todo}  
                todos={todos}
                setTodos={setTodos}/>
        ))}
    </div>
  )
}

export default TodoList

styles.css

// 之前的省略...
.todos {
    display: flex;
    justify-content: space-evenly;
    width: 90%;
    flex-wrap: wrap;  /* 元素可以换行 */
}

2 SingleTodo组件

Todo列表中的每个Todo卡片,包括tood描述、编辑按钮、删除按钮和完成按钮
在这里插入图片描述
icons图标我们可以使用react的UI组件react-icons
【Typescript学习】使用 React 和 TypeScript 构建web应用(三)所有组件_第2张图片

安装

npm install react-icons

在网页的搜索栏可以搜到按钮的名字
【Typescript学习】使用 React 和 TypeScript 构建web应用(三)所有组件_第3张图片
【Typescript学习】使用 React 和 TypeScript 构建web应用(三)所有组件_第4张图片
使用

import React from 'react'
import { Todo } from "../model";
import { AiFillEdit, AiFillDelete } from "react-icons/ai";
import { MdDone } from "react-icons/md";
import './styles.css'
type Props = {
    todo: Todo,
    todos: Todo[],
    setTodos: React.Dispatch<React.SetStateAction<Todo[]>>,
}


const SingleTodo = ({todo, todos, setTodos}: Props) => {
  return (
    <form className='todos__single'>
        <span className="todos__single--text">{todo.todo}</span>
        <div>
            <span className="icon"><AiFillEdit/></span>
            <span className="icon"><AiFillDelete/></span>
            <span className="icon"><MdDone/></span>
        </div>
    </form>
  )
}

(SingleTodo的HTML标签是form,因为当修改todo时需要处理submit,后文会实现)
css样式设计
styles.css

// 之前的省略...
.todos__single {
    display: flex;
    width: 29.5%;
    border-radius: 5px;
    padding: 20px;
    margin-top: 15px;
    background-image: url("https://img.freepik.com/free-photo/crumpled-yellow-paper-background-close-up_60487-2390.jpg?size=626&ext=jpg");
}
.todos__single--text {
    flex: 1;
    padding: 5px;
    border: none;
    font-size: 20px;
}
.todos__single--textJ:focus {
    outline: none;
}

.icon {
    margin-left: 10px;
    font-size: 25px;
    cursor: pointer;
}

/* 响应式布局 */
@media (max-width: 1200px) {
    .todos__single {
        width: 40%;
    }
}
@media (max-width: 700px) {
    .input {
        width: 95%;
    }
    .todos {
        width: 95%;
    }
    .todos__single {
        width: 100%;
    }
}

其中响应式布局的效果如下,当宽度小于700px时:
【Typescript学习】使用 React 和 TypeScript 构建web应用(三)所有组件_第5张图片

3 todo的按钮响应函数

分别对三个按钮实现功能,较简单的是点击完成(done)的事件响应和删除(delete)的事件响应
SingleTodo.tsx

// done按钮的响应
  const handleDone = (id: number) => {
    // 改变对应todo的isDone属性
    setTodos(
        todos.map((todo) => 
            todo.id === id ? {...todo, isDone: !todo.isDone}
                           : todo
        )
    );
  };
  // delete按钮的响应
  const handleDelete = (id: number ) => {
    setTodos(
        todos.filter((todo) => todo.id === id)
    )
  };
  return (
    <form className='todos__single'>
        {// 如果是完成状态,则用删除线效果
          todo.isDone ? (
           //  删除线标签
            <s className="todos__single--text">{todo.todo}</s>
        ):(
            <span className="todos__single--text">{todo.todo}</span>
        )}
        <div>
            <span className="icon"><AiFillEdit/></span>
            <span className="icon" onClick={()=>handleDelete(todo.id)}><AiFillDelete/></span>
            <span className="icon" onClick={()=>handleDone(todo.id)}><MdDone/></span>
        </div>
    </form>
  )

点击edit按钮的事件响应逻辑比较复杂

  1. 我们需要一个状态变量edit,跟踪当前是否处于“编辑状态”;以及一个状态变量editTodo,跟踪当前输入到todo的文本。
const [edit, setEdit] = useState<boolean>(false);
const [editTodo, setEditTodo] = useState<string>(todo.todo);
  1. 在结构里添加显示的逻辑和编辑完成后提交时的事件
<form className='todos__single' onSubmit={(e)=>handleEdit(e, todo.id)}>
    {   // 如果是edit状态,则显示input框,否则是todo的描述
        edit ? (
            <input value={editTodo} 
               onChange={(e)=> setEditTodo(e.target.value)}
               className='todos__single--text'
            />
        ) : (
            // 如果是完成状态,则用删除线效果
            todo.isDone ? (
                // s 下划线标签
                <s className="todos__single--text">{todo.todo}</s>
            ):(
                <span className="todos__single--text">{todo.todo}</span>
            )
        )
    }
    ...
</form>
  1. 处理handleEdit函数
const handleEdit = (e:React.FormEvent, id: number) => {
  e.preventDefault();  // 禁止默认的页面刷新行为
  setTodos(todos.map((todo => (
      todo.id === id? {...todo, todo: editTodo}:todo
      )
  )))
  setEdit(false);
}
  1. 我们需要点击edit按钮时,input框自动获得焦点,因此用useRef挂载到input框上,同时使用对edit变量的useEffect
  const inputRef = useRef<HTMLInputElement>(null);
  useEffect(() => {
    inputRef.current?.focus();
  }, [edit]);

第三天done!
今天新建了TodoList组件和SingleTodo组件,完成了相应的UI和大部分的逻辑。
UI中需要注意适应窗口的响应式布局、如何使用react-icons框架;组件实现中需要注意具体的逻辑、状态变量如何切换

TodoList.tsx

import React from 'react'
import './styles.css'
import { Todo } from "../model";
import SingleTodo from './SingleTodo';
type Props = {
    todos: Todo[],
    setTodos: React.Dispatch<React.SetStateAction<Todo[]>>,
}

const TodoList:React.FC<Props> = ({todos, setTodos}: Props) => {
  return (
    <div className='todos'>
        {todos.map(todo =>(
            <SingleTodo key={todo.id}
                todo={todo}  
                todos={todos}
                setTodos={setTodos}/>
        ))}
    </div>
  )
}

export default TodoList

SingleTodo.tsx

import React, { useState, useRef, useEffect } from 'react'
import { Todo } from "../model";
import { AiFillEdit, AiFillDelete } from "react-icons/ai";
import { MdDone } from "react-icons/md";
import './styles.css'
type Props = {
    todo: Todo,
    todos: Todo[],
    setTodos: React.Dispatch<React.SetStateAction<Todo[]>>,
}


const SingleTodo = ({todo, todos, setTodos}: Props) => {
  const [edit, setEdit] = useState<boolean>(false);
  const [editTodo, setEditTodo] = useState<string>(todo.todo);

  // done按钮的响应
  const handleDone = (id: number) => {
    // 改变对应todo的isDone属性
    setTodos(
        todos.map((todo) => 
            todo.id === id ? {...todo, isDone: !todo.isDone}
                           : todo
        )
    );
  };
  // edit按钮的响应
  const handleEdit = (e:React.FormEvent, id: number) => {
    e.preventDefault();  // 禁止默认的页面刷新行为
    setTodos(todos.map((todo => (
        todo.id === id? {...todo, todo: editTodo}:todo
        )
    )))
    setEdit(false);
  }
  // delete按钮的响应
  const handleDelete = (id: number ) => {
    setTodos(
        todos.filter((todo) => todo.id === id)
    )
  };
  
  const inputRef = useRef<HTMLInputElement>(null);  // 编辑框
  // edit状态改变时自动获取编辑框的焦点
  useEffect(() => {  
    inputRef.current?.focus();
  }, [edit]);
  
  return (
    <form className='todos__single' onSubmit={(e)=>handleEdit(e, todo.id)}>
        {   // 如果是edit状态,则显示input框,否则是todo的描述
            edit ? (
                <input
                  ref={inputRef} 
                  value={editTodo} 
                  onChange={(e)=> setEditTodo(e.target.value)}
                  className='todos__single--text'
                />
            ) : (
                // 如果是完成状态,则用删除线效果
                todo.isDone ? (
                    // s 下划线标签
                    <s className="todos__single--text">{todo.todo}</s>
                ):(
                    <span className="todos__single--text">{todo.todo}</span>
                )
            )
        }
        
        <div>
            <span className="icon" onClick={()=>{
                if(!edit && !todo.isDone){
                    setEdit(!edit);  // edit变为true
                }
            }}><AiFillEdit/></span>
            <span className="icon" onClick={()=>handleDelete(todo.id)}><AiFillDelete/></span>
            <span className="icon" onClick={()=>handleDone(todo.id)}><MdDone/></span>
        </div>
    </form>
  )
}

export default SingleTodo

明天要使用react reducer hook进行状态管理了,可以先复习一下

使用到的:

  • react的按钮UI组件react-icons
  • 响应式布局

你可能感兴趣的:(前端,前端,react.js,typescript)