명령형 프로그래밍
- 컴퓨터가 수행할 명령들을 순서대로 써 놓은 것.
- "어떻게 구현하는가"를 디테일하게 기술하는 것.
선언형 프로그래밍
- "무엇을 구현하는가"를 디테일하게 기술하는 것.
- HTML, SQL 등이 해당 됨.
- 화면을 그릴 때 약속된 규칙의 태그를 나열하면(무엇이 나타냐야 되느냐를 선언하면) 브라우저가 그려줌. 명령형으로 화면을 그리면 굉장히 복잡할 것임. 즉, HTML을 그리는 규칙, 브라우저의 HTML 랜더링 추상화 등 복잡한 코드를 표현하지 않아도 무엇을 그릴 것인가를 규칙에 따라 선언하면 됨.
예시 1-1) double 함수 만들기
//명령형 function decDouble(arr) { let results = []; for(let i = 0; i < arr.length; i++) { //어떻게 처리하는지에 대한 묘사 results.push(arr[i] * 2); } return results; } console.log(decDouble([1,2,3])); //2, 4, 6 ] //선언형 function imperDouble(arr) { return arr.map( number => number *2); //무엇을 원하는지에 대한 묘사 } console.log(imperDouble([2,3,6])); //[ 4, 6, 12 ]
- 명령형은 array를 어떻게 가공할 것인가를 일일히 넣은 반면, 선언형은 map이란 표현, 규칙으로 현재 배열에서 새로운 배열을 만듦.
- 선언형은 코드의 문맥도 깔끔해지고, 새로운 무언가를 붙이기도 편하다는 장점이 있음.
예시 1-2) 조건 추가하기
//명령형, 문자열 입력 방지하는 조건 추가 for(let i = 0; i < arr.length; i++) { if(typeof arr[i] === 'number'){ results.push(arr[i] * 2); } }
- for 안에 로직이 쌓이는 구조. 코드의 맥락을 보려면 쌓인 것에 대한 이해를 필요로하고, 규칙성 없어 유지보수, 확장이 어려움.
- 같은 형식으로 가공을 하는데 함수마다 차이가 있으면 곤란함.
arr .filter(param => typeof param === "number") .map(number => number *2);
- array에 있는 값들로 해야 할 일을 끊는다면 좀 더 간단하고 명료하게 표현할 수 있음.
예시 2-1) 토글 버튼 만들기
function ToggleButton({ target, text }) { const button = document.createElement('button'); let isInit = false; let clickCount = 0; // this 는 ToggleButton {} 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 main = document.querySelector('body'); new ToggleButton({ target : main, text :'Button1' });
- ToggleButton 스코프 안에 버튼을 만드는 모든 것을 넣는 추상화 과정을 거침.
- 새로운 기능을 추가할 때도 용이함.
- 타겟 단계의 이벤트는 파라미터와 상관없이, 그들이 등록된 순서대로 요소의 모든 리스너를 트리거함 → if문 안에 있어도 정상 실행되는 이유.
예시 2-2) 상태 추상화
function ToggleButton({ target, text, onClick }) { const button = document.createElement('button'); target.appendChild(button); this.state = { clickCount : 0, toggled : false } this.setState = (nextState) => { this.state = nextState; this.render(); } this.render = () => { button.textContent = text; button.style.textDecoration = this.state.toggled ? 'line-through' : 'none'; } button.addEventListener('click', () => { this.setState({ clickCount : this.state.clickCount + 1, toggled : !this.state.toggled }) if(onClick) { onClick(this.state.clickCount); } }) this.render(); } const main = document.querySelector('body'); new ToggleButton({ target : main, text :'Button1', onClick : (clickCount) => { if(clickCount % 2 === 0) { console.log('두번째클릭'); } } });
- 변하는 값에 따라 렌더링에 영향을 주는 것은 state로 추상화.
- render는 상태를 기반으로 바뀔 수 있도록 접근.
- DOM 접근을 최대한 제한하고 컴포넌트를 활성화시킴으로써 복잡도를 낮춤.
예시 2-3) 확장
일정 시간 뒤에 toggled를 해지하는 timer를 추가적으로 만들 수 있음.
function TimerButton ({ target, text, timer }) { const button = new ToggleButton ({ target, text, onClick : () => { setTimeout(() => { button.setState({ ...button.state, toggled : !button.state.toggled }) }, timer); }}); } new TimerButton({ target : main, text : '3초뒤 자동으로', timer : 1000 * 3 })
예시 2-4) 인터페이스 제작
function ButtonGroup({ target, buttons }) { const group = document.createElement('div'); let isInit = false; this.render = () => { if(!isInit) { buttons.forEach(({type, ...props}) => { if(type === 'toggle') { new ToggleButton({ target: group, ...props}); } else if (type === 'timer') { new TimerButton({ target : group, ...props}); } }) } target.appendChild(group); isInit = true; } this.render(); } const main = document.querySelector('body'); new ButtonGroup ({ target : main, buttons : [ { type : 'toggle', text : '토글버튼' }, { type : 'timer', text : '타이머', timer : 1000 * 3 } ] })
- 내부 코드 동작을 몰라도 인터페이스에 맞춰 버튼 제작 가능.
- 재사용성 및 공유 기능 향상.
- 외부에서 개입할 수 있는 요소를 최소한으로 시키고, 독립적으로 돌아갈 수 있으며, 상태를 기반으로 추상화된 UI를 만드는 게 중요함.
- 이런 개념이 추후 리액트, 스벨떼에 적용하기 쉬움.