React, TypeScript Todo List 만들기

BRANDY·2023년 4월 3일
0

처음 계획은 이미 제작한 투두 리스트를 타입스크립트로 리팩토링 할 예정이었지만 아직 익숙하지 않아 클론 코딩을 진행하였다.

프로젝트 소개
타입스크립트 사용이 목적이기 때문에 최대한 간단한 레이아웃과 기능을 구현한다. 할일을 작성하여 엔터 시 리스트에 할일이 생성되고 체크박스를 체크 했을때, 완료의 목적으로 내용에 줄이 그어지며 삭제할 수 있는 X버튼을 구현하여 목록을 삭제할 수 있게 한다.
리액트로 투두리스트를 구현하는것과 다른 점은 파라미터를 받을 때 타입을 설정해주어야 하는 것이다.

타입스크립트 설치

npx create-react-app tstodolist --template=typescript

App.tsx

//App.tsx
import React from 'react';
import '../styles/inputText.css';

interface InputTextProps { //인터페이스 사용
    onChange(e: React.ChangeEvent<HTMLInputElement>): void; //아무것도 반환하지 않는 void타입
    onKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void; //엔터 시 텍스트 입력
    inputText: string; //텍스트가 들어갈 것이므로 string 타입 지정
}

const InputText = ({
    onChange,
    onKeyDown,
    inputText
}:InputTextProps) => {
    return (
        // <div className="inputTextContainer">
            <input type="text"
                className="inputText"  
                placeholder='Enter'
                onChange={e=> onChange(e)}
                onKeyDown={e => onKeyDown(e)}
                value={inputText}
            />
        // </div>
    );
}

export default InputText;

initialState의 타입을 설정하기 위해 interface를 설정해준다.

CheckBox 컴포넌트

// CheckBox.tsx
import React from 'react';
import '../styles/checkBox.css';

interface CheckBoxProps { //인터페이스 사용
    checked?: boolean; //불리언 타입 지정
    onClick?(): void;
}

const CheckBox = ({
    checked,
    onClick
}: CheckBoxProps) => { //체크 되었을때 표시 "O"
    return (
        <>
            <div className="container" onClick={onClick}>
                <div className="checkIcon">
                    {checked && "O"}
                </div>
            </div>
        </>
    );
}

export default CheckBox;

체크박스를 눌렀을때, O표시가 되고 색이 변한다.

DeleteButton 컴포넌트

//DeleteButton.tsx
import React from 'react';
import '../styles/deleteButton.css'

interface DeleteButtonProps { //인터페이스 사용
    onClick? (): void; 
}

const DeleteButton = ({
    onClick
}:DeleteButtonProps) => {
    return (
        <>
            <div className="deleteButtonContainer" onClick={onClick}>
               <div className="checkIcon">
                    {'X'} // X표시
                </div>
            </div>
        </>
    );
}

export default DeleteButton;

InputText 컴포넌트

//InputText.tsx
import React from 'react';
import '../styles/inputText.css';

interface InputTextProps { //인터페이스 사용
    onChange(e: React.ChangeEvent<HTMLInputElement>): void;
    onKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void;
    inputText: string;
}

const InputText = ({
    onChange,
    onKeyDown,
    inputText
}:InputTextProps) => {
    return (
            <input type="text"
                className="inputText"  
                placeholder='Enter'
                onChange={e=> onChange(e)}
                onKeyDown={e => onKeyDown(e)}
                value={inputText}
            />
    );
}

export default InputText;

Item 컴포넌트

import React from 'react';
import '../styles/item.css';
import CheckBox from './CheckBox';
import DeleteButton from './DeleteButton';
import Text from './Text';

interface ItemProps { //인터페이스 사용
    onClickCheckBox(id: number): void; //id값을 추가하여 체크박스를 구분한다
    onClickDeleteButton(id: number): void;
    completed?: boolean;
    text: string;
    id: number;
}

const Item = ({
    onClickCheckBox,
    onClickDeleteButton,
    completed,
    text,
    id,

}:ItemProps) => {
    
    return (
        <>
            <div className="itemContainer">
                <CheckBox 
                    checked={completed} 
                    onClick={() => onClickCheckBox(id)}
                />
                <Text completed={completed}>
                    {text}
                </Text>
                <DeleteButton
                    onClick={() => onClickDeleteButton(id)}
                />
            </div>
        </>
    );
}

export default Item;

Text 컴포넌트

//Text.tsx
import React from 'react';
import '../styles/text.css';

interface TextProps { //인터페이스 사용
    completed?: boolean; //불리언 타입 지정
    children: React.ReactNode;
}

const Text = ({
    completed,
    children
}: TextProps) => {
    return (
        <>
            <div className={`text ${completed ? 'completedText' : ''}`}>
                {children}
            </div>
        </>
    );
}

export default Text;

children은 주로 재사용을 위해서 사용되며 children 자리에 원하는 구조의 JSX를 넣을 수 있다는 것도 편한것 같다. 하지만 유지보수나 구조가 복잡해질때를 대비하여 사용을 지양해야 할 것 같다.

text-decoration: line-through;

체크된 목록의 텍스트에 줄 긋기는 CSS단에 코드를 작성한다.

TodoList

//TodoList.tsx
import React, {useRef, useState} from 'react';
import InputText from './InputText';
import Item from './Item';
import '../styles/todoList.css';

interface TList {
    id: number; //구분을 위해 id값을 작성하고 타입 지정
    text: string;
    completed: boolean;
}

const TodoList = () => {
    const [inputText, setInputText] = useState('');
    const [tasks, setTasks] = useState<TList[]>([

    ]);
    const nextId = useRef(4);

    // 체크박스 핸들러
    const handleClickCheckBox = (id: number) => { //id를 가진 컴포넌트의 상태변경
        setTasks(tasks.map(task => 
            task.id === id ? {...task, completed: !task.completed} : task
        ));
    }
    // 삭제버튼 핸들러
    const handleClickDeleteButton = (id: number) => {
        setTasks(tasks.filter( task => task.id !== id)); //해당 id를 가진 컴포넌트 삭제
    }
    // 입력값 변경 핸들러
    const handleInputTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setInputText(e.target.value);
    }
    // 입력값 엔터 핸들러
    const handleInputTextKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
        if (e.key === 'Enter' && e.nativeEvent.isComposing === false){ //ENTER 입력이 되면 이벤트 실행
            
            const newList: TList = {
                id: nextId.current,
                text: inputText,
                completed: false,
            }
            setTasks(tasks.concat(newList));
            setInputText('');
            nextId.current += 1;
        }
    }
    console.log(tasks);

    return (
        <div className='appContainer'>
            <div className="todoListContainer">
                <div className="todoList">
                    {tasks.map( task => 
                        <Item 
                            key={`${task.id}task`}
                            id = {task.id}
                            text = {task.text}
                            completed={task.completed}
                            onClickCheckBox={handleClickCheckBox}
                            onClickDeleteButton={handleClickDeleteButton}
                        />
                    )}
                </div>
            <InputText
                    onChange={handleInputTextChange}
                    onKeyDown={handleInputTextKeyDown}
                    inputText={inputText}
                />
            </div>


        </div>
    );
}

export default TodoList;

완성

profile
프런트엔드 개발자

0개의 댓글