본문 바로가기
개발자 도전기/[STUDY] JS || TS

TypeScript | 강의 메모 | CLI에서 동작하는 todo앱 프로젝트 - Todo 추가, 삭제하기(1)

by 답수 2022. 4. 14.
728x90
반응형

 

 

※ 인프런 - 타입스크립트 시작하기(by 이재승) 강의를 기반으로 정리한 내용입니다.

 

 

이전까지는 todo리스트가 담겨져 있는 배열에 있는 Todo들을 터미널에 출력하는 것까지 구현했다. 이번에는 Todo 리스트의 타이틀과 우선순위를 직접 입력하여 추가하는 기능을 구현해볼 것이다.

 

 

new todo 생성 클래스

 command.ts 파일에 새로운 todo를 추가하는 클래스를 작성할 것이다.

// src/command.ts

// 새로운 todo 추가하는 클래스
export class CommandNewTodo extends Command {
    constructor() {
        super("n", "할 일 추가하기");
    }

    async run(): Promise<void | ActionNewTodo> {    // ActionNewTodo 타입도 설정
        const title = await waitForInput("title: ");
        // priority 높음(0) ~ 낮음(2) 이런식으로 사용자에게 더 친절한 설명 위해 map 생성(type.ts에서 변수 생성)
        const priorityStr = await waitForInput(`priority ${PRIORITY_NAME_MAP[Priority.High]}(${Priority.High}) ~ ${PRIORITY_NAME_MAP[Priority.Low]}(${Priority.Low}): `);
        const priority = Number(priorityStr);

        // 제목(빈 문자열 X)과 우선순위(enum에 속하는지) validation 체크        
        if (title && CommandNewTodo.getIsPriority(priority)) {
            // todo 추가
            // 이 작업은 앱의 상태를 변경하는 것이기 때문에 index.ts에서 다루는 것이 더 적합. 어떻게 바꿔야 하는지만 설명 --> Action
            return {
                type: "newTodo",
                title,
                priority
            }
        }
    }

    // 우선순위 enum에 맞는지 여부 파악하는 함수
    static getIsPriority(priority: number): priority is Priority {
        return getIsValidEnumValue(Priority, priority);
    }
}

 

키 n을 눌렀을 때 todo 추가하기로 설정.

타이틀은 내가 입력하는 값으로 하기 위해 title이라는 변수에 waitForInput() 함수를 사용하고, 우선순위 역시 내가 입력하는 값으로 하기 위해 같은 함수를 사용한다. 이때, 단순히 priority라고만 출력되면 사용자 입장에서 헷갈릴수도 있으니, 우선순위 숫자와 한글을 매핑하여 보기 더 편하게 작성한다.

 

// src/type.ts

// priority 높음(0) ~ 낮음(2) 이런식으로 사용자에게 더 친절한 설명 위해 map 생성
export const PRIORITY_NAME_MAP: { [key in Priority]: string } = {   // 맵드타입 이용하여 enum의 속성 누락 없도록
    [Priority.High]: "높음",
    [Priority.Medium]: "중간",
    [Priority.Low]: "낮음",
}

 

 

입력한 타이틀이 빈 문자열이 아니고, 우선순위가 enum에 해당되는 값만 사용할 수 있도록 valid체크를 하는 함수를 만든다. 여기서true인 경우에만 새로운 todo를 생성하도록 한다. 이때 리턴되는 작업은 앱의 상태를 변경하는 것이기 때문에 index파일에서 다루도록 하고, command파일 내에서는 앱의 상태가 어떻게 바뀌는지만 설명하도록 한다. 액션에 필요한 타입은 type.ts에 정의한다.

// src/type.ts

// 액션
export interface ActionNewTodo {
    type: "newTodo";    // 식별 가능한 유니온 타입(나중에 어떤 액션인지 구분하기 위한 용도)
    title: string;
    priority: Priority;
}

export type Action = ActionNewTodo;

 

 

index.ts 수정

이제 commands 배열에 위에서 만들었던 클래스를 추가한다.

 

그후 입력한 값이 commands 내에 있는지 확인할 때 action이 있을 때 run을 실행하도록 하고, run() 실행 이후 상태를 변경하기 위한 함수 getNextState()함수를 작성한다.

// src/index.ts

import { Command, CommandNewTodo, CommandPrintTodos } from "./Command";
import { waitForInput } from "./Input";
import Todo from "./Todo";
import { Action, Appstate, Priority } from "./type";

// 커맨드 클래스 통해 todo리스트 가져올 배열 생성, + todo추가 클래스 생성
const commands: Command[] = [new CommandPrintTodos(), new CommandNewTodo()];

async function main() {
    // 앱의 상태 정의
    let state: Appstate = {
        todos: [
            new Todo("test1", Priority.High),
            new Todo("test2", Priority.Medium),
            new Todo("test3", Priority.Low),
        ],
    }

    while (true) {
        // 처음 시작시 화면 클리어
        console.clear();

        // todo에 있는 목록 가져오기
        for (const command of commands) {
            console.log(command.toString());
        }
        console.log();

        const key = await waitForInput("input command: ");  // waitForInput이 프로미스 객체를 반환하기 때문에 async-await 사용
        /**
         * await는 프로미스 객체가 resolve나 reject가 될때까지 기다려줌
         * resolve가 됐을 때 값을 가져옴
         */
        console.clear();

        // 입력한 값이 커맨드에 있다면
        const command = commands.find(v => v.key === key);
        if (command) {
            // 액션
            const action = await command.run(state);
            if (action) {
                 state = getNextState(state, action);
            }
        }
    }
}

main();


// 현재 상태와 Action을 입력으로 받아서 다음 상태를 반환해주는 함수
const getNextState = (state: Appstate, action: Action): Appstate => {
    switch(action.type) {
        case "newTodo":
            return {
                ...state,
                todos: [...state.todos, new Todo(action.title, action.priority)]    // 기존 todos에 새로운 todo 추가
            };
    }
}

 

여기까지 구현했을 때 결과:

 

 

여기서 우선순위의 숫자를 한글로 매핑했던 것을 한 번 더 이용해서 우선순위를 한글로 수정

// src/Todo.ts

import { Priority, PRIORITY_NAME_MAP } from "./type";

// Todo 클래스
export default class Todo {
    static nextId: number = 1;  // 객체별로 이 변수를 관리할 필요가 없기 때문에 static으로 정의
    constructor(
        private title: string, 
        private priority: Priority,
        public id: number = Todo.nextId,
    ) {
        Todo.nextId++;
    }
    toString() {
        return `${this.id}) 제목: ${this.title} (우선순위: ${PRIORITY_NAME_MAP[this.priority]})`;   // 우선순위 한글로 나오게 수정
    }
}

 

 

수정 후 결과

여기서 터미널을 재시작했기 때문에 이전에 작성했던 4번 날라갔다. 지금 입력하는 데이터들은 단순히 메모리에만 쌓이기 때문에 재시작되면 날아간다(메모리는 휘발성이니까!). 만약 DB나 다른 곳에 저장하려면 따로 구현을 해야 할 듯 하지만 일단 마지막 강의를 마저 보고 마무리가 어떻게 되는지 보자구

 

 

 

728x90
반응형
LIST

댓글