💡 오늘 만들어 볼 것은?
초심자라면 누구나 한번쯤 만들어보고 간다는 Todo List의 가장 기초적인 버전을 만들어본다.
컴포넌트 방식으로 만들 것이다.
💡 컴포넌트
프로그램의 한 부분을 의미하며 재사용이 가능한 최소 단위를 말한다. 객체지향언어를 사용할 때 자주 사용되며 재사용이 가능하기 때문에 컴포넌트 단위로 분류하거나 이동 가능하다는 특징이 있다. 소프트웨어는 독립적으로 개발되지 않은 경우가 많고, 독립적으로 개발되어도 다른 모듈과의 호환을 생각하지 않고 개발한다. 이는 소프트웨어의 재사용을 어렵게 하고 유지보수 비용이 크게 증가하는 원인이 된다. 이러한 상황에서 소프트웨어의 재사용의 중요성과 필요성을 위해 나온 기술이 컴포넌트 기술이다.
Todo List 페이지의 구성 요소는 다음과 같다.
💡 이런 식의 컴포넌트 구성을 통하여 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();
}
💡 앞에 $가 붙은 변수는 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 생성 파라미터에 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
})
}
🖥 main.js
const data = [
{
text: '완전 기깔나게 숨쉬기'
},
{
text: '세상에서 가장 야무지게 밥먹기'
},
{
text: 'Vanila JS 마스터하기'
}
]
const $app = document.querySelector('.app')
new App({
$target: $app,
initialState: data
})
모든 코드들을 살펴보았으니 정상 작동 되는지 확인해볼 차례다.
🖨 결과
add로 Todolist를 추가해보자.
가장 실현하기 어려울 것 같다.
기초적인 TodoList를 만들어보며 대강의 VanilaJS 활용을 익혀보았다.
다음엔 쿠키와 로컬저장소에 대하여 알아보자.