- 명령형 프로그래밍
- 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임
- 즉 컴퓨터가 수행할 명령들을 순서대로 써놓은 것
- 어떻게 수행하는가에 초점
- 알고리즘을 명시, 목표는 명시하지 않는다
- 선언형 프로그래밍
- 무엇을 수행하는가에 초점
- 목표를 명시, 알고리즘은 명시하지 않는다
- ES5에 등장한 Array 함수들
: map, forEach, reduce, filter, fill 등
- 가급적이면 선언형으로 짜는 것이 좋다. 앞으로 js 프로젝트도 선언형 방향으로 코드 짤 것.
토글 버튼 만들기
<main class="app"></main>
in html파일- <main> 태그 : <body> 요소의 메인 콘텐츠를 정의
- 따라서 하나의 문서에는 단 하나의 <main> 요소만이 존재
- 자식들은 중심 주제 또는 주요 기능과 직접적 관련 or 확장되어야 함
- (app은 내가 붙여준 이름인 것임 아무거나 해도 ㄱㅊ / class이면 (appendChild할 target 등을) .app으로 가져오고, id면 #app으로 가져옴) -
헷갈렸던 개념
- example3.js (명령형 코드)
const $button1 = document.createElement('button'); $button1.textContent = "button1"; const $button2 = document.createElement('button'); $button2.textContent = "button2"; const $button3 = document.createElement('button'); $button3.textContent = "button3"; const $main = document.querySelector('#app'); $main.appendChild($button1); $main.appendChild($button2); $main.appendChild($button3); //토글 버튼으로 만들기 const toggleButton = ($button) => { if (!$button.style.textDecoration) { $button.style.textDecoration = "line-through"; } else { $button.style.textDecoration = ""; } } document.querySelectorAll('#app button') .forEach($button=> $button.addEventListener('click', e => { toggleButton(e.target); }))
- 추상화 되도록 컴포넌트로 변경하기(선언적)
function ToggleButton ({$target, text}) { const $button = document.createElement('button'); let isInit = false; //초기화가 안됐음 this.toggle = () => { if ($button.style.textDecoration === "") { $button.style.textDecoration = "line-through"; } else { $button.style.textDecoration = ""; } } this.render = () => { $button.textContent = text; if (!isInit) { $target.appendChild($button); $button.addEventListener("click",()=> {this.toggle()}); isInit = true; //초기화 표시 } }; this.render(); } const $app = document.querySelector('#app'); const button1 = new ToggleButton ({ $target : $app, text : "버튼1" }) const button2 = new ToggleButton ({ $target : $app, text : "버튼2" }) const button3 = new ToggleButton ({ $target : $app, text : "버튼3" })
- 같은 객체를 여러번 호출하는 경우를 대비해서 isInit을 선언
- (주의) 함수형 컴포넌트에서 표현식(화살표 함수)로 바꾸면
ToggleButton is not a constructor
라는 에러가 나면서 통하지 않는다. - why ? ⇒ function 함수와 다르게 화살표 함수는 생성자가 아니기 때문에 new 연산자를 사용해서 객체 선언을 할 수 없다.
- 또한 에러는 나진 않지만 화살표 함수에서 this는 window를 가리키기 때문에, this를 빼고 선언과 호출을 하는 것이 좋다.
[기능추가1] 3번 클릭할 때마다 alert 띄우기
- 모든 버튼에 alert 이벤트 등록시키는 경우
function ToggleButton ({$target, text}) { const $button = document.createElement('button'); let isInit = false; let clickCount = 0; this.lineThrough = () => { ... } this.render = () => { $button.textContent = text; if (!isInit) { $target.appendChild($button); $button.addEventListener("click", () => { this.lineThrough(); clickCount++; if (clickCount % 3 === 0 ) { alert('3번 클릭했습니다'); } }); isInit = true; } }; this.render(); } const $app = document.querySelector('#app'); const button1 = new ToggleButton ({ $target : $app, text : "버튼1" }) ...
- 특정 객체에만 alert이벤트를 발생시키는 경우
function ToggleButton ({$target, text, onClick}) { ... $button.addEventListener("click", () => { this.lineThrough(); clickCount++; if (onClick) { onClick(clickCount); } }); ... const button1 = new ToggleButton ({ $target : $app, text : "버튼1", onClick : (clickCount) => { //clickCount는 생성자 함수에서만 아는 것 if (clickCount % 3 === 0) { alert('3번 클릭했습니다'); } } }) const button2 = new ToggleButton ({ $target : $app, text : "버튼2" }) ...
onClick을 매개변수로 넘겨주는 것으로 변경한다.
- 토글 카운트와 토글 여부를 상태로 관리할 수 있다.
⇒ 코드 복잡성을 줄이면서, 조금 더 선언적 프로그래밍스럽게
(이전) $button.style.textDecoration === "” 로 DOM 객체에 직접 접근해서 토글 여부를 판단
(이후) 상태에서 토글 여부를 확인함
function ToggleButton ({$target, text, onClick}) { const $button = document.createElement('button'); let isInit = false; //초기화가 안됐음 this.state = { clickCount : 0, isToggled : false } this.setState = (newState) => { this.state = newState; this.render(); } this.render = () => { $button.textContent = text; $button.style.textDecoration = this.state.isToggled ? "line-through" : ""; }; if(!isInit) { $target.appendChild($button); $button.addEventListener("click", ()=> { if(onClick) { this.setState({ clickCount : this.state.clickCount+1, isToggled : !this.state.isToggled }); onClick(this.state.clickCount); } }); } this.render(); } ...
또한, setState될 때마다 render함수가 호출하게 함에 따라 코드 배치를 수정했다.
[기능추가2] 누르면 3초 뒤에 자동 토글 되는 버튼 만들기
function TimerToggledButton ({$target, text, timer}) { const button = new ToggleButton({$target, text, onClick : () => { setTimeout(()=>{ button.setState({ ...button.state, isToggled: !button.state.isToggled }); },timer); }}) } //onClick에 setState가 있음에 따라 onClick이 있으면 // => ToggleButton에서 setState 따로x 하게 수정 function ToggleButton ({$target, text, onClick}) { ... $button.addEventListener("click", ()=> { if(onClick) { onClick(this.state.clickCount); } else { this.setState({ clickCount : this.state.clickCount+1, isToggled : !this.state.isToggled }) } }); } ... }
[기능추가3] ButtonGroup 만들기
button이 여러개인 배열을 매개변수로 던져줘서, ButtonGroup으로 한번에 표시하자.
각 button들은 type과 text를 원소로 갖는다.
function ButtonGroup({$target, buttons}) { const $group = document.createElement('div'); let isInit = false; this.render = () => { buttons.forEach(({type, ...buttonProps}) => { if(!isInit) { $target.appendChild($group); if (type === 'toggle') { new ToggleButton( { $target: $group, ...buttonProps, }); } else if (type === 'timer') { new TimerToggledButton( { $target: $group, ...buttonProps, }); } isInit = true; } }); } this.render(); } ... new ButtonGroup( { $target: $app, buttons: [{ type : 'toggle', text :'그룹원1', }, { type : 'timer', text :'그룹원2', }, { type : 'toggle', text :'그룹원3', }, { type : 'timer', text :'그룹원4', }] });