TIL/프로그래머스 웹 데브코스

JS_명령형 프로그래밍과 선언형 프로그래밍

codermun 2021. 8. 17. 13:31
728x90

명령형 프로그래밍과 선언형 프로그래밍

 

명령형 프로그래밍은

우리가 익히 해왔던 방식과 같이 컴퓨터가 수행할 명령들을 순서대로 기술해 놓은 것이다.

즉, "어떻게 구현하는가"를 디테일하게 기술하는 관점에 중점을 두는 프로그래밍 기법이다.

// 명령형
      // "어떻게" 처리하는지에 대한 묘사
      function double(arr){
        let res = [];
        for (let i = 0; i < arr.length; i++){
          res.push(arr[i] * 2)
        }
        return res
      // }

 


선언형 프로그래밍은

대표적으로 SQL Query 문과 HTML이 있다.

우리가 약속된 태그들을 쭉 나열하면서 실제로 어떻게 랜더링 되느냐에 대해서는 크게 신경 쓰지 않아도 된다.

무엇을 나타내야 하는지만 선언하면 브라우저가 대신 그려주는 형식과 같다.

즉, "무엇을 나타내야하는가"를 디테일하게 기술하는 관점에 중점을 두는 프로그래밍 기법이다.

 선언형 프로그래밍에서는 JS 또는 사용자 정의로 많은 함수와 라이브러리 등을 이용하는데 

선언형 프로그래밍을 함수형 프로그래밍이고도 한다.

// 선언형
      // "무엇을 (number => number * 2)" 원하는지에 대한 묘사
      function double(arr){
        return arr.map(number => number * 2)
      }
<script>
document.querySelector('body').innerHTML = double([1, 2, 3, 4,'a'])
</script>


console.log(double([1, 2, 3, 4, 'a')));

위와 같이 NaN이 보기 불편하다 코드를 고쳐보자.

명령형

// 명령형
      // "어떻게" 처리하는지에 대한 묘사
      function double(arr){
        let res = [];
        for (let i = 0; i < arr.length; i++){
          if (typeof arr[i] === 'number'){
          res.push(arr[i] * 2)
        }}
        return res
      }

 

선언형

선언형의 경우가 훨씬 코드의 가독성이 높다.

arr 작업을 하나하나 끊어한다는 사고방식으로 간단 명료하게 표현이 가능하기에 굉장히 매력적이다.

// 선언형
      // "무엇을" 원하는지에 대한 묘사
      function double(arr){
        return arr
        .filter(param => typeof param === 'number')
        .map(number => number * 2)
      }


좀 더 많은 조건이 필요한 예시를 보자

어떠한 객체가 있는 경우 털 색이 검정이고 귀가 접히지 않은 고양이들만 출력하는 함수를 

명령형과 선언형으로 작성해 알아보자.

 

명령형

// 명령형
      // "어떻게" 처리하는지에 대한 묘사
      function filterCats(cats){
        let res = [];
        for (let i = 0; i < arr.length; i++){
          if (cat){
            cat.colors.includes('black') &&
            cat.ear === 'unfolded'){
              res.push(value.name)
            }
          }  
        }
        return res

 

선언형

// 선언형
      // "무엇을" 원하는지에 대한 묘사
      function filterCats(cats){
        return cats.filter(cat => 
        cat &&
        cat.colors.includes('black') &&
        cat.ear === 'unfolded'
        ).map(cat => cat.name)
      }

 


특히나 UI를 만드는데 명령형 보다는 선언형의 방법이 훨씬 효율적이라고 한다

그 예를 알아보자.

 

버튼을 만들고 페이지에 그린 후 삭선이 그어지도록 만들어보자.

아래의 코드로 구현이 되었다.

// 버튼을 3개 만든다.
    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('body')
    $main.appendChild($button1);
    $main.appendChild($button2);
    $main.appendChild($button3);

    // 버튼을 클릭하면 삭선이 그어진다.
    $button1.addEventListener('click', () => {
      if ($button1.style.textDecoration === 'line-through'){
        $button1.style.textDecoration = 'none'
      } else {
        $button1.style.textDecoration = 'line-through'
      }
    })

    $button2.addEventListener('click', () => {
      if ($button2.style.textDecoration === 'line-through'){
        $button2.style.textDecoration = 'none'
      } else {
        $button2.style.textDecoration = 'line-through'
      }
    })

    $button3.addEventListener('click', () => {
      if ($button3.style.textDecoration === 'line-through'){
        $button3.style.textDecoration = 'none'
      } else {
        $button3.style.textDecoration = 'line-through'
      }
    })

 

하지만 중복되는 코드가 많다. 고쳐보자

// 버튼을 클릭하면 삭선이 그어진다.
    document.querySelectorAll('button').forEach($button => {
      $button.addEventListener('click', (e) =>{
        if (e.target.style.textDecoration === 'line-through') {
          e.target.style.textDecoration = 'none'
        } else {
          e.target.style.textDecoration = 'line-through'
        }
      })
    })
// 버튼을 클릭하면 삭선이 그어진다.
    const toggleButton = ($button) =>{
      if ($button.style.textDecoration === 'line-through'){
        $button.style.textDecoration = 'none'
      } else {
        $button.style.textDecoration = 'line-through'
      }
    }
    document.querySelectorAll('button').forEach($button => {
      $button.addEventListener('click', (e) =>{
        const {target} = e
        toggleButton(target)
      })
    })


선언형

하나의 추상화된 개념으로 토글 버튼을 만드는데 필요한 것을 묶어둔다고 이해하면 된다.

흔히 이야기하는 컴포넌트 방식으로 추가하는 방법이다.

버튼이 실행될 때 각각의 버튼은 다른 버튼들의 동작이나 상태에 아무런 영향을 끼치지 않는 안정한 상태로 버튼을 추가할 수 있다.

이처럼 아래와 같이 선언적으로 코드를 구현할 경우

토글 버튼의 어떤 속성을 추가하는 것, 확장하는 것이 명령형에 비해 

생성하는 것도 쉽고, 생성한 토글 버튼을 이용해 특정 작업, 기능에 활용하는데 굉장히 용이하다!!

function ToggleButton({
            $target,
            text
        }) {
            const $button = document.createElement('button');

            this.render = () => {
                $button.textContent = text

                $target.appendChild($button)
                
                $button.addEventListener('click', () =>{
                if ($button.style.textDecoration === 'line-through'){
                    $button.style.textDecoration = ''
                } else {
                    $button.style.textDecoration = 'line-through'
                }
            })
            }

            this.render();
        }

        const $body = document.querySelector('body');

        new ToggleButton({
            $target: $body,
            text: 'Button1'
        })

        new ToggleButton({
            $target: $body,
            text: 'Button2'
        })

        new ToggleButton({
            $target: $body,
            text: 'Button3'
        })


 

기능 추가하기

명령형의 경우 하나의 기능이 추가될 때마다 하나하나 기능을 추가해줘야 한다.

(물론, 명령형 프로그래밍도 위와 같이 하나하나 기능을 추가하지 않도록 프로그래밍할 수 있겠지만 선언형 프로그래밍이 얼마나 효율적인지 알기 위해서 위처럼 생각하자.)

하지만 선언형의 경우 관련 컴포넌트 하나만 기능을 추가하기만 하면 간편하게 사용할 수 있으며 로직을 파악하기도 더 쉽다.

 

1. 3번째 클릭할 때마다 alert 창 띄우는 기능 추가하기.

어떤 식으로 컴포넌트화를 시켜 확장시키는지, 왜 이런 게 선언형이라 볼 수 있고 선언형이 왜 좋은지를 생각하면서 진행해보자.

 

1-1. ToggleButton 안에서 기능을 추가하는 방법

function ToggleButton({
            $target,
            text
        }) {
            const $button = document.createElement('button');
            let clickCount = 0;

            this.render = () => {
                $button.textContent = text

                $target.appendChild($button)

                $button.addEventListener('click', () => {
                    clickCount++;
                    if ($button.style.textDecoration === 'line-through') {
                        $button.style.textDecoration = ''
                    } else {
                        $button.style.textDecoration = 'line-through'
                    }

                    if (clickCount % 3 === 0){
                    alert('3번쨰 클릭');
                }
                })
            }

            this.render();
        }

        const $body = document.querySelector('body');

        new ToggleButton({
            $target: $body,
            text: 'Button1'
        })

        new ToggleButton({
            $target: $body,
            text: 'Button2'
        })

        new ToggleButton({
            $target: $body,
            text: 'Button3'
        })

1-2. ToggleButton 밖에서 기능을 추가하는 방법

onClick이라는 함수를 받아 원하는 조건에 따라 alert 창을 띄울 수 있다.

render() 함수의 경우 외부의 상황에 의해서 실행될 수 있기 때문에 이렇게 작성해도 좋다!

function ToggleButton({
            $target,
            text,
            onClick
        }) {
            const $button = document.createElement('button');
            let clickCount = 0;

            this.render = () => {
                $button.textContent = text

                $target.appendChild($button)

                $button.addEventListener('click', () => {
                    clickCount++;
                    if ($button.style.textDecoration === 'line-through') {
                        $button.style.textDecoration = ''
                    } else {
                        $button.style.textDecoration = 'line-through'
                    }

                    if (onClick) {
                        onClick(clickCount)
                    }
                })
            }
            this.render();
        }

        const $body = document.querySelector('body');

        new ToggleButton({
            $target: $body,
            text: 'Button1',
            onClick: (count) => {
                console.log(count);
                if (count % 3 === 0) {
                    alert('세번쨰 클릭');
                }
            }
        })

        new ToggleButton({
            $target: $body,
            text: 'Button2',
            onClick: (count) => {
                console.log(count);
                if (count % 2 === 0) {
                    alert('두번쨰 클릭');
                }
            }
        })

        new ToggleButton({
            $target: $body,
            text: 'Button3'
        })

 

사실 위와 같이 버튼의 상태에 따라 선을 긋는지 안긋는지 정하고 있지만 이런 방식보다는

명확한 UI의 상태를 추상화하고 랜더 함수를 통해 상태의 변화를 따라가는 흐름으로 작성하는 것이 효율적이다.


이전까지는 항상 명령형으로만 작성하는 법을 알았다.

최근 들어 함수형, 선언형 프로그래밍을 다루는 곳이 많아지는 것 같고 확실히 레거시 코드가 선언형으로 되어있고 내가 선언형을 잘 이해한다면 정말 효율적으로 사용하고 활용할 수 있을 것 같다.

728x90