教程来自freecodeCamp:【英字】使用 React 和 TypeScript 构建应用程序
跟做,仅记录用
其他资料:https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/
以下是视频(0:40-0:60) 的内容
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; /* 元素可以换行 */
}
Todo列表中的每个Todo卡片,包括tood描述、编辑按钮、删除按钮和完成按钮
icons图标我们可以使用react的UI组件react-icons
安装
npm install react-icons
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%;
}
}
分别对三个按钮实现功能,较简单的是点击完成(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按钮的事件响应逻辑比较复杂
edit
,跟踪当前是否处于“编辑状态”;以及一个状态变量editTodo
,跟踪当前输入到todo的文本。const [edit, setEdit] = useState<boolean>(false);
const [editTodo, setEditTodo] = useState<string>(todo.todo);
<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>
handleEdit
函数const handleEdit = (e:React.FormEvent, id: number) => {
e.preventDefault(); // 禁止默认的页面刷新行为
setTodos(todos.map((todo => (
todo.id === id? {...todo, todo: editTodo}:todo
)
)))
setEdit(false);
}
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进行状态管理了,可以先复习一下
使用到的: