VanillaJS로 구현하는 간단한 Todo List

young-gue Park·2023년 1월 26일
0

JavaScript

목록 보기
12/20
post-thumbnail

⚡ Todo List 만들기


💡 오늘 만들어 볼 것은?

초심자라면 누구나 한번쯤 만들어보고 간다는 Todo List의 가장 기초적인 버전을 만들어본다.
컴포넌트 방식으로 만들 것이다.

💡 컴포넌트
프로그램의 한 부분을 의미하며 재사용이 가능한 최소 단위를 말한다. 객체지향언어를 사용할 때 자주 사용되며 재사용이 가능하기 때문에 컴포넌트 단위로 분류하거나 이동 가능하다는 특징이 있다. 소프트웨어는 독립적으로 개발되지 않은 경우가 많고, 독립적으로 개발되어도 다른 모듈과의 호환을 생각하지 않고 개발한다. 이는 소프트웨어의 재사용을 어렵게 하고 유지보수 비용이 크게 증가하는 원인이 된다. 이러한 상황에서 소프트웨어의 재사용의 중요성과 필요성을 위해 나온 기술이 컴포넌트 기술이다.

Todo List 페이지의 구성 요소는 다음과 같다.

  • 맨 위 "simple Todo List"는 Header에 해당한다.
  • main.js에는 data가 들어있으며 App.js의 작동을 담당한다.
  • App.js에는 Header와 TodoForm, TodoList 컴포넌트를 한데 모아 $target에 맞춰 출력될 수 있게한다.
  • App.js에 모일 컴포넌트들은 각각 본인의 기능을 담고 있다.
  • index.html에서 모든 컴포넌트를 호출하여 완성한다.

💡 이런 식의 컴포넌트 구성을 통하여 Header와 같은 컴포넌트 별 코드 수정이 용이해지고 독립된 기능으로서 다른 곳에 재활용이 가능해진다.

그러면 이제 가장 기본 구조인 index.html 먼저 살펴본다.

🖥 index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>simple Todo List</title>
</head>
<body>
    <main class="app"></main>
    <script src="./src/Header.js"></script>
    <script src="./src/TodoForm.js"></script>
    <script src="./src/TodoList.js"></script>
    <script src="./src/App.js"></script>
    <script src="./src/main.js"></script>
</body>
</html>

target으로 app을 지정하기 위하여 main 태그 안에 class 속성 값으로 app을 넣었다.

❗ script 호출 순서가 저것과 다를 경우 기능을 먼저 호출하고 화면을 늦게 호출하여 사용자의 입장에서
좋지 않을 수 있다. 그 외의 문제들도 발생 가능하니 호출 순서를 지키는 것이 중요하다.

다음으로 src 파일 내에 있는 컴포넌트들을 살펴본다.

🖥 TodoList.js(첫 번째 버전)

function TodoList(params) {
    const $target = params.$target;
    const $todoList = document.createElement('div'); // 컴포넌트의 DOM
    $target.appendChild($todoList);

    this.state = params.initialState;

    this.render = () => {
        $todoList.innerHTML = `
            <ul>
                ${this.state.map(todo =>`<li>${todo.text}</li>`).join('')}
            </ul>
        `
        // map을 돈 이후에는 아래처럼 만들어진다
        /*
            this.state.map(todo => `<li>${todo.text}</li>`)
            ->    [{text: '자바스크립트 공부하기'}, {text: '....'}]
            ->    ['<li>자바스크립트 공부하기</li>', '<li>....</li>']

            join('')을 하게되면?
            ->    <li>자바스크립트 공부하기</li><li>....</li>
        */
    }

    this.render();
}
  • params.$target - 해당 컴포넌트가 추가될 DOM 엘리먼트
  • params.initialState - 해당 컴포넌트의 초기 상태
  • map을 돈 이후의 구조 생성은 주석으로 설명하였다.

💡 앞에 $가 붙은 변수는 DOM 객체를 담고 있는 변수라는 의미이며, 접미사에 Element를 붙여 표현할 수도 있다.

이 코드는 ES6의 Objcet Distructuring으로 더 간결하게 바꿀 수 있다.
바꾸는 겸 현재의 상태를 변경하는 setState도 추가로 넣는다.

어제 배운 객체 비구조화 할당이다.

function TodoList({$target, initialState}) {
    const $todoList = document.createElement('div'); // 컴포넌트의 DOM
    $target.appendChild($todoList);

    this.state = initialState;

    // 현재의 상태를 변경하고 변경한 상태를 다시 랜더링
    this.setState = nextState => {
        this.state = nextState;
        this.render();
    }

    this.render = () => {
        $todoList.innerHTML = `
            <ul>
                ${this.state.map(({text}) =>`<li>${text}</li>`).join('')}
            </ul>
        `
    }

    this.render();
}

🖥 todoForm.js

function TodoForm({$target, onSubmit}) {
    const $form = document.createElement('form')
    $target.appendChild($form)

    let isInit = false;

    this.render = () => {
        $form.innerHTML = `
            <input type ="text" name = "todo" />
            <button>add</button>
        `

        if(!isInit) {
            $form.addEventListener('submit', e => {
                e.preventDefault();
            
                const $todo = $form.querySelector('input[name=todo]')
                const text = $todo.value
                
                if(text.length > 1) { // 한 글자 이상 들어왔을 때만 (없으면 빈 문자열만 들어갈 수 있음)
                    $todo.value = '' // 입력 후 input 비워주기

                    onSubmit(text) // App.js의 TodoForm 내의 onSubmit이 호출
                }
            })
            isInit = true
        }
    }
    
    this.render()
}
  • TodoForm 생성 파라미터에 이벤트 콜백을 넣고, text를 입력 받으면 해당 콜백을 통해 text를 넘겨주는 방식이다.
  • 그래서 onSubmit은 App.js에 정의되어 있다.
  • e.preventDefault()
    - input을 제출할 때 마다 화면이 새로운 창으로 새로고침 되는 것을 막는다.
    - get 방식을 이용한 웹 페이지 특성상 웹 페이지 주소 끝에 input 값이 붙는데, 이것을 안보이게 한다.

🤷‍♂️ TodoForm 생성 파라미터에 TodoList를 넣고 직접 참조하면 안되나요?
TodoForm에 TodoList 컴포넌트의 의존성이 강하게 생기기 때문에 안된다❗

🖥 Header.js

function Header ({$target, text}) {
   const $header = document.createElement('h1')

   $target.appendChild($header)

   this.render = () => {
       $header.textContent = text
   }
   this.render()
}
  • 한 줄의 문자열만을 넣는 영역이다보니 아무래도 코드가 단순하다.

  • 컴포넌트가 적용될 영역을 잡고, 랜더링을 한다는 감을 완전히 익히는 것이 중요하다.

    🖥 App.js

function App({$target, initialState}) {
    new Header({
        $target, 
        text: 'simple Todo List'
    })
    
    new TodoForm({
        $target,
        onSubmit: (text) => {
            const nextState = [...todoList.state, { text }]
            todoList.setState(nextState)
        }
    })

    const todoList = new TodoList ({
        $target,
        initialState
    })
}
  • 3개의 컴포넌트들을 호출한다.
  • 호출한 TodoForm 안에는 콜백함수를 정의해 두었다.

🖥 main.js

const data = [
    {
        text: '완전 기깔나게 숨쉬기'
    },
    {
        text: '세상에서 가장 야무지게 밥먹기'
    },
    {
        text: 'Vanila JS 마스터하기'
    }
]

const $app = document.querySelector('.app')

new App({
    $target: $app,
    initialState: data
})
  • 초기 데이터가 들어있다.
  • $app에 적용될 App 컴포넌트를 호출한다.

모든 코드들을 살펴보았으니 정상 작동 되는지 확인해볼 차례다.

🖨 결과

add로 Todolist를 추가해보자.


가장 실현하기 어려울 것 같다.


기초적인 TodoList를 만들어보며 대강의 VanilaJS 활용을 익혀보았다.
다음엔 쿠키와 로컬저장소에 대하여 알아보자.

profile
Hodie mihi, Cras tibi

0개의 댓글