【Typescript学习】使用 React 和 TypeScript 构建web应用(四)useReducer、扑街了的分区功能【完结了】

教程来自freecodeCamp:【英字】使用 React 和 TypeScript 构建应用程序
跟做,仅记录用
其他资料:https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/
作者提供的源码https://github.com/piyush-eon/react-typescript-taskify/tree/react-typescript-tutorial


第四天

以下是视频(1:00-1:20) 的内容

1 useReducer

这part的代码是我自己写的,因为原视频中说作为homework,就没给代码

对todo内容的增删改查进行逻辑整合,写一个reducer
state 即为todo数组
action有四种: add、remove、done、edit
改造成reducer

  1. 先定义针对todos的reducer函数TodoReducer,以及Actions类型
    model.ts
// ......
export type Actions = 
    { type: 'add', payload: string } 
  | { type: 'remove', payload: number } 
  | { type: 'done', payload: number }
  | { type: 'edit', payloadId: number, payloadContent: string}

export const TodoReducer = (state: Todo[], action: Actions) => {
 switch (action.type) {
    case 'add':
        return [
          ...state,
          { id: Date.now(), todo: action.payload, isDone: false }
        ];
    case 'remove':
        return state.filter((todo) => todo.id !== action.payload);
    case 'done':
        return state.map((todo) => todo.id === action.payload 
            ? {...todo, isDone: !todo.isDone}
            : todo
        )
    case 'edit':
        return state.map((todo) => (
            todo.id === action.payloadId
            ? {...todo, todo: action.payloadContent }
            : todo
        ))
    default:
        return state;
 }
}
  1. 其次修改 App.tsx,用useReducer替换useState,修改handleAdd以及向子组件的传值
import React, { useState, useReducer } from 'react';
import './App.css';
import InputField from './components/InputField';
import TodoList from './components/TodoList';
import { TodoReducer } from "./model";

const App: React.FC = () => {
  const [todo, setTodo] = useState<string>(""); // 尖括号加上变量类型
  const [todosState, dispatch] = useReducer(TodoReducer, []);
  
  
  return (
    <div className="App">
      <span className='heading'>Taskify</span>
      <InputField todo={todo} setTodo={setTodo} todosDispatch={dispatch}/>
      <TodoList todosState={todosState} todosDispatch={dispatch}/>
    </div>
  );
}

export default App;
}
  1. 在子组件中使用dispatch函数,先看‘add’这个action的调用,即InputField.tsx
// ...
type Props = {
    todo: string,
    setTodo: React.Dispatch<React.SetStateAction<string>>,
    todosDispatch: React.Dispatch<Actions>,
}

const InputField: React.FC<Props> = ({todo, setTodo, todosDispatch}: Props) => {
  const inputRef = useRef<HTMLInputElement>(null);
  // 点击GO后
  const handleAdd = (e: React.FormEvent):void => {
    e.preventDefault(); // 取消默认的页面刷新行为
    if(todo) {
      todosDispatch({type: 'add', payload: todo})
      setTodo("");
    }
  };
  return (
    <form className='input' onSubmit={(e) => {
            handleAdd(e);
            console.log(e);
            inputRef.current?.blur();  // 移除focus状态
        }}
    >
        <input type="input"
          ref={inputRef}
          value={todo} 
          onChange={
            (e)=>setTodo(e.target.value)
          }
          placeholder="Enter a task" 
          className='input__box'
        />
        <button className='input_submit' type='submit'>
            GO
        </button>
    </form>
  )
}
  1. 再修改使用done、remove、edid的SingleTodo.tsx
type Props = {
    todo: Todo,
    todosDispatch: React.Dispatch<Actions>
}

const SingleTodo = ({todo, todosDispatch}: Props) => {
  const [edit, setEdit] = useState<boolean>(false);
  const [editTodo, setEditTodo] = useState<string>(todo.todo);
  // done按钮的响应
  const handleDone = (id: number) => {
    todosDispatch({
      type: 'done',
      payload: id
    })
  };
  // edit按钮的响应
  const handleEdit = (e:React.FormEvent, id: number) => {
    e.preventDefault();  // 禁止默认的页面刷新行为
    todosDispatch({
      type:'edit',
      payloadContent: editTodo,
      payloadId: id
    })
    setEdit(false);
  }
  // delete按钮的响应
  const handleDelete = (id: number ) => {
    todosDispatch({
      type: 'remove', 
      payload: id
    })
  };
  
  const inputRef = useRef<HTMLInputElement>(null);  // 编辑框
  // edit状态改变时自动获取编辑框的焦点
  useEffect(() => {  
    inputRef.current?.focus();
  }, [edit]);
  
  // ...
  
}

至此,完成了基础版的TASKIFY
在这里插入图片描述

2 增加分区功能

我们进一步完善,期望将进行中的任务和已完成的任务分区显示
在这里插入图片描述
更改TodoList.tsx中的HTML结构

const TodoList:React.FC<Props> = ({todosState, todosDispatch}: Props) => {
  return (
    <div className="container">
      <div className='todos'>
        <span className="todos__heading">
          Active Tasks
        </span>
        {
          todosState.map(todo =>(
            <SingleTodo key={todo.id}
              todo={todo}  
              todosDispatch={todosDispatch}
            />
        ))}
      </div>
      <div className='todos remove'>
        <span className="todos__heading">
          Completed Tasks
        </span>
        {todosState.map(todo =>(
            <SingleTodo key={todo.id}
                todo={todo}  
                todosDispatch={todosDispatch}
            />
        ))}
      </div>
    </div>
  )
}

修改样式 styles.css

.input {
    display: flex;
    width: 95%;
    position: relative;
    align-items: center;
}
.input__box {
    width: 100%;
    border-radius: 50px;
    padding: 20px 30px;
    font-size: 25px;
    border: none;
    transition: 0.2s;
    box-shadow: inset 0 0 5px black;
}

.input__box:focus {
    box-shadow: 0 0 10px 1000px rgba(0, 0, 0, 0.5);
    outline: none;
}

.input_submit {
    position: absolute;
    border-radius: 50%;
    font-size: 15px;
    margin: 12px;
    right: 0px;
    background-color: #2f74c0;
    color: white;
    width: 50px;
    height: 50px;
    border: none;
    box-shadow: 0 0 10px black;
    transition: 0.2s all;
}
.input_submit:hover {
    background-color: #388ae2;
}
/* 按钮点击时 */
.input_submit:active {  
    transform: scale(0.8);  /* 缩放 */
    box-shadow: 0 0 5px black;
}

.container {
    display: flex;
    width: 95%;
    margin-top: 10px;
    align-items: flex-start;
    justify-content: space-between;
}

.todos {
    display: flex;
    
    width: 47.5%;
    flex-direction: column;
    padding: 15px;
    border-radius: 5px;
    background-color: rgb(50, 195, 205);
}

.todos__heading {
    font-size: 20px;
    color: white;

}
.todos__single {
    display: flex;
    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");
    transition: 0.2s;
}
.todos__single:hover {
    box-shadow: 0 0 5px black;
    transform: scale(1.03);
}
.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: 1100px){
    .todos {
        width: 45%;
    }
}
@media (max-width: 700px) {
    .input {
        width: 95%;
    }
    .todos {
        width: 95%;
        margin-bottom: 10px;
    }
    .container {
        width: 95%;
        flex-direction: column;
    }
}
.remove {
    background-color: rgb(235, 103, 80);
}

App.tsx中为TodoList组件传入completedTodos属性

     <TodoList 
        todosState={todosState} 
        todosDispatch={dispatch}
        completedTodos={completedTodos}
        setCompletedTodos={setCompletedTodos}
      />

3 添加两个列表间的拖拽功能

借助第三方包 react-beautiful-dnd

ps: 在TypeScript中安装一些依赖时,需要安装types版本,注意不要安装成js包了

npm install --save @types/react-beautiful-dnd

… 得,这个依赖我整不好了,应该是各依赖版本间兼容的问题,总是报错。
具体这个依赖的使用,官方也有详细教程,之后再看吧…


总结

今天主要是useReducer的使用。react-beautiful-dnd那个包可气死我了,以后再说吧。
这个小项目就结束了,主要是为了熟悉一下TypeScript,顺便复习React和CSS,还留了个小坑
视频作者提供了项目源码,供学习https://github.com/piyush-eon/react-typescript-taskify/tree/react-typescript-tutorial

本博仅做记录,如有问题,都是我的错,我改正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值