일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 백준구현문제
- 리액트
- 코딩테스트
- 자바스크립트
- 리액트댓글기능
- 리액트커뮤니티
- JS
- dp알고리즘
- 다이나믹프로그래밍
- 안드로이드 스튜디오
- HTML
- 프로그래머스JS
- 백준nodejs
- 코테
- 알고리즘
- js코테
- HTML5
- 프로그래머스
- 백준구현
- 백준js
- 백준
- 익스프레스
- 프로그래머스코테
- 백준알고리즘
- 몽고DB
- 포이마웹
- JS프로그래머스
- css기초
- CSS
- 백준골드
- Today
- Total
개발새발 로그
선언형 프로그래밍 방식을 바닐라 자바스크립트로 배워보자 본문
요새는 리액트만 공부해서 그런지 이런 고민이 생겼다.
"자바스크립트로 이런 기능 한번 만들어보세요!" 했을 때 어떻게 할 것인가?
기능을 구현할 수야 있겠지만 과연 그 기능을 남들이 봤을 때 "옳은 방식"으로 만들까?
다시한번 자바스크립트를 공부하려고 마음을 먹었고, 내가 과거에 배웠던 자바스크립트를 다시 꺼내왔다.
이번에 두 번째로 보는 것인데 다시 복습하면서 중요한 점들을 상기시키려고 한다.
📖명령형 프로그래밍 방식과 선언형 프로그래밍 방식
명령형 프로그래빙 방식이란?
프로그래밍의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임의 일종이다.
자연 언어에서의 명령법이 어떤 동작을 할 것인지를 명령으로 표현하듯이, 명령형 프로그램은 컴퓨터가 수행할 명령들을 순서대로 써 놓은 것이다.
쉽게 말해 어떻게(How) 할 건지를 설명하는 프로그래밍 방식이라고 한다.
선언형 프로그래밍 방식이란?
원하는 결과를 묘사하는 방식으로 코드를 작성하는 프로그래밍 패러다임의 일종이다.
쉽게 말해 무엇(What)을 할 건지를 설명하는 프로그래밍 방식이라고 한다.
대표적인 선언형 프로그래밍 방식이 HTML이라고한다.
선언형 프로그래밍 스타일로 코드를 작성하면 전체적인 가독성과 추상화 수준을 높여 개발자가 문제의 본질에 집중할 수 있도록 도와줍니다.
또한 이러한 작업을 통해 재사용성이 높고 병렬처리가 유리한 특징을 가지게 됩니다.
👨💻간단한 명령형과 선언형 프로그래밍 방식
function double(arr) {
let result = []
for (let i = 0; i < arr.length; i++) {
result.push(arr[i] * 2)
}
return result
}
document.querySelector("body").innerHTML = double([1, 2, 3])
위 코드가 명령형 프로그래밍 방식의 예시다.
코드가 어떻게 흘러가는지 하나 하나 작성한 모습이다.
그럼 이걸 선언형 프로그래밍 방식으로 바꾸면 아래와 같다.
function double(arr) {
return arr.map(number => number * 2)
}
map이라고 정의된 함수를 이용해 과정을 함축시켰다.
무엇을 원하는지에 대한 묘사를 한 것이다.
그럼 아래와 같은 명령형 코드는 어떻게 선언형으로 바꿀 수 있을까?
function double(arr) {
let result = []
for (let i = 0; i < arr.length; i++) {
if(typeof arr[i] === "number"){ // 타입 검사 추가
result.push(arr[i] * 2)
}
}
return result
}
타입 검사가 추가된 코드이다.
위 코드는 아래와 같이 선언형으로 바꿀 수 있다.
function double(arr) {
return arr
.filter(param => typeof param === 'number')
.map(number => number * 2)
}
좀 더 코드가 간단 명료해졌다.
이렇게 선언형프로그래밍과 명령형 프로그래밍을 간단히 알아보았다.
어쨌거나 명령형 프로그래밍 방식은 좋지않은 부분이 존재한다.
그럼 실제로 html요소에 넣을 기능을 만들 때 어떻게 해야할까?
나는 과거에 컴포넌트 방식으로 기능을 구현했었다.
이번에 다시 컴포넌트 방식을 정리하면서 복습하려고 한다.
👨💻컴포넌트 방식이란..
먼저 어떤 기능을 만들 것이냐면
간단하게 버튼을 만들고,
버튼을 화면에 그리고,
클릭하면 취소선이 그려지는 기능을 만들어보려고 한다.
먼저 명령형으로 만든다고 하면 어떻게 코드를 작성하게 될까?
// 버튼을 만든다.
const $button1 = document.createElement("button");
$button1.textContent = "Button1"
// 만든 버튼을 화면에 그린다.
const $main = document.querySelector("#app")
$main.appendChild($button1);
// 토글 버튼 함수
const toggleButton = ($button) => {
if($button.style.textDecoration === "line-through"){
$button.style.textDecoration = "none"
}else{
$button.style.textDecoration = "line-through"
}
}
// 버튼을 클릭하면 취소선이 그어진다.
$button1.addEventListener("click",()=>{
toggleButton($button1);
})
명령형 프로그래밍은 위 코드처럼 작성해볼 수 있다.
근데 만약 여기서 버튼이 여러개라면?
아마도 위 코드를 여러개 중복해서 만들어야 할 것이다.
그리고 코드가 어떻게 돌아가는지 예측하기 힘들어진다.
버그 또한 찾기 어려울 것이다.
참고로 버튼이 많다면 이벤트를 등록하는 부분은 아래와 같이 할 수있다.
(그래도 여전히 기능이 확장되면 가독성이 안좋아짐)
querySelectorAll("button").forEach((button)=>{ ... });
이걸 toggleButton이라는 함수로 추상화할 것이다.
바로 코드를 보
function ToggleButton({$target, text}){
const $button = document.createElement("button")// 버튼이 생긴다.
let isInit = false //render 함수가 밖에서 여러번 호출될 수 있으므로 초기화를 한번만 수행하도록 하기위함
this.toggle = ()=>{
if($button1.style.textDecoration === "line-through"){
$button1.style.textDecoration = "none"
}else{
$button1.style.textDecoration = "line-through"
}
}
// 렌더 함수에서 필요한 로직을 수행한다.
this.render = ()=>{
$button.textContent = text
if(!isInit){
$target.appendChild($button)
$button.addEventListener("click", ()=>{
this.toggle()
})
isInit = true
}
}
this.render(); // 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 : "버튼2"
})
아까 명령형 프로그래밍처럼 기능이 여기저기 흩어져있는게 아닌 한 곳에서 모두 처리하고 있다.
그리고 render() 함수를 이용하는데 이를 통해 렌더링 되는 시점이 명확하다.
하나의 개념으로 추상화 한 것이다.
이 코드를 흔히 컴포넌트 방식으로 추상화한다고 한다.
이 코드는 나중에 기능을 추가하거나 확장할 때도 용이하다.
그럼 예시로 3가지의 기능을 추가하면서 컴포넌트 방식에 대해서 더 자세히 알아보자
🛠️기능 추가하기 1 : 3번 클릭할 때마다 alert 띄우기
3번 클릭할 때마다 alert를 띄운다고 했을 때 아래와 같이 구현하면 되지않을까? 생각할 수 있을 것이다.
function ToggleButton({ $target, text }) {
const $button = document.createElement('button')
let isInit = false
let clickCount = 0; // 클릭카운트 상태
this.toggle = () => {
clickCount++ // 카운트 증가
if ($button.style.textDecoration === 'line-through') {
$button.style.textDecoration = 'none'
} else {
$button.style.textDecoration = 'line-through'
}
// alert 실행
if(clickCount % 3 === 0){
alert("3번째 클릭!")
}
}
this.render = () => {
$button.textContent = text
if (!isInit) {
$target.appendChild($button)
$button.addEventListener('click', () => {
this.toggle()
})
isInit = true
}
}
this.render()
}
이때 만약 모든 버튼에 해당 기능을 넣는 것이 아닌 특정한 버튼에만 해당 기능을 넣으려면 어떻게 해야할까?
이럴때는 함수를 호출하는 밖에서 그 행위를 주입하면 된다.
function ToggleButton({ $target, text, onclick }) { // onClick을 받아올 수 있도록 추가해줬다.
// ...
let clickCount = 0;
this.toggle = () => {
clickCount++
//...
if(onclick){
onclick(clickCount)
}
}
// ...
const button1 = new ToggleButton({
target: $app,
text: '버튼',
onclick: (clickCount)=>{ // 특정한 버튼에서만 alert기능을 수행한다.
if(clickCount % 3 === 0){
alert("3번째 클릭!")
}
}
})
이제 위 코드에서 수정할 부분이 하나 있다.
ToggleButton 컴포넌트의 상태를 만들어야 한다.
지금 코드에서 클릭하면 취소선을 나타내는 기능이 그저 text.Decoration이 존재하냐 안하냐로 구분하고 있다.
이런 식으로 하는 것 보다 UI의 상태를 추상화하고, 해당 상태에 따라서 render함수가 따라가도록 해야한다.
에를 들어 bold 스타일을 추가하거나 했을 때 if문을 textDecoration처럼 똑같이 추가해줘야 할 것이다.
그럼 if문이 점점 복잡해질 것이다.
그래서 아래와 같은 코드를 컴포넌트에 추가해줄 것이다.
this.state = {
clickCount: 0,
toggled: false
}
this.setState = nextState => {
this.state = nextState
this.render()
}
리액트를 많이 써봤다면 많이 봤을 코드이다.
실제로 나는 컴포넌트 방식으로 자바스크립트를 구현할 때 render, state, setState를 먼저 만들고 시작한다.
거의 '국룰" 처럼 사용하고 있는 방식이다.
그럼 위 코드를 어떻게 사용하는지 다시 코드로 보자
function ToggleButton({ $target, text, onclick }) {
const $button = document.createElement('button')
//상태 생성
this.state = {
clickCount: 0,
toggled: false,
isInit: false
}
this.setState = nextState => {
this.state = nextState
//상태를 업데이트하고 다시 렌더링을 수행한다.
this.render()
}
this.toggle = () => {
// 상태로 기능 관리
if (this.state.toggled) {
$button.style.textDecoration = this.state.toggled
? 'line-through'
: 'none'
}
// 상태 업데이트
this.setState({
clickCount: this.state.clickCount + 1,
toggled: !this.state.toggled
})
if (onclick) {
onclick(this.state.clickCount)
}
}
this.render = () => {
$button.textContent = text
if (!this.state.isInit) {
$target.appendChild($button)
$button.addEventListener('click', () => {
this.toggle()
})
this.state.isInit = true
}
}
this.render()
}
const $app = document.querySelector('#app')
const button1 = new ToggleButton({
target: $app,
text: '버튼',
onclick: clickCount => {
if (clickCount % 3 === 0) {
alert('3번째 클릭!')
}
}
})
UI에 접근하는 것이 아니라 상태를 기반으로 렌더링 하는 구조가 되었다.
이 방식이 조금 더 선언적인 방식이되면서 복잡도를 낮추게 되었다.
DOM을 여기저기서 접근하는 것을 최대한 제한 시킨 것이다.
🛠️기능 추가하기 2 : ToggleButton 이외에 5초 뒤에 토글되는 버튼 만들기
이제 기존 ToggleButton을 확장한 예시를 보여주려고 한다.
이 부분은 바로 코드로 보면 이해가 빠르게 될 것이다.
function TimerButton({ $target, text, timer = 3000 }) {
const button = new ToggleButton({
$target,
text,
onClick: () => {
setTimeout(() => {
button.setState({
...button.state,
toggled: !button.state.toggled
})
}, timer)
}
})
}
new TimerButton({
$target: $app,
text: '3초뒤에 토글됩니다.'
})
기존의 ToggleButton에서 기능을 확장한 것이다.
🛠️기능 추가하기 3 : Button Group 만들기
해당 기능 또한 기존의 ToggleButton을 확장해보려고한다.
코드를 보면 빠르게 이해될 것이다.
function ButtonGroup({ $app, 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 })
}
})
$app.appendChild($group)
}
isInit = true
}
this.render()
}
const $app = document.querySelector('#app')
new ButtonGroup({
$target: $app,
buttons: [
{
type: 'toggle',
text: '토글 버튼'
},
{
type: 'toggle',
text: '토글버튼이다.'
},
{
type: 'timer',
text: '타이머',
timer: 1000
}
]
})
이 코드도 TimerButton과 같이 ButtonGroup을 사용하는 하단의 코드 부분을 보면 실제로 ButtonGroup이 어떻게 구현되는지는 모른다. 관심사가 아닌 것이다.
그리고 ButtonGroupt을 사용할 때 인터페이스에 맞춰서 사용하면 재사용성이 쉬워진다.
또한 외부에 어떤 요인에 의해서 결정되는 것이 아니라 $target과 buttons라는 파라미터로 격리된 값들에 의해서만 결정된다.
궁극적으로 이런 개념이 있어야 리액트나 Vue를 사용할 때 함정에 빠지지않는다.
이 과정으로 사고방식을 어떻게 해야할지 감을 잡아보는 것이었다.
📘마무리하며..
일단 자바스크립트로 선언적으로 어떻게 코드를 짜는지 컴포넌트 방식은 무엇인지 알아보았다.
나는 예전에 배웠던 내용인데도 이렇게 정리하고 나서야 제대로 기억할 수 있었다.
이후 더 내용이 있다.
오늘은 여기까지만 하지만 이후 내용을 더 공부해서 자바스크립트 능력을 다시 올려보려고 한다.
'자바스크립트' 카테고리의 다른 글
FE - 에러 핸들링 (0) | 2024.01.25 |
---|---|
D3.js 인터렉티브 적용하기 (1) | 2024.01.24 |
D3.js 데이터 로딩하는 함수 (0) | 2024.01.24 |
D3 척도와 축 - 선형 척도, 순서형 척도, 묶음 척도, 영역과 여백, x축과 y축(axis) (1) | 2024.01.24 |
D3.js 데이터 조인 (2) | 2024.01.24 |