영화 검색 사이트: 즉시실행함수, 년도를 반복문으로 출력

상황


year 데이터 부분에 2023년부터 1985년도까지 선택할 수 있도록 데이터들을 넣어줘야 한다. 하지만 해당 년도들을 모두 수작업으로 작성하기는 효율적이지 않다.
그렇기에 우리는 즉시실행함수를 사용하여 해당 년도들을 반복문으로 출력하도록 한다.

  • ()()

    해당 형태를 만들면 즉시실행함수 사용가능!
    (function () {})() 을 만들어 함수를 실행하는데 더 간략하게 화살표함수로 변경해주도록 하자. (() ⇒ {})()

  • 2023년도에서 1985년도

    시작(i)을 2023년도로 시작해서 1씩 감소하도록 해서 마지막에 1985년도까지만 반복되도록 한다.

    const years = []
    for (let i = 2023; i >= 1985; i -= 1) {
      years.push(i)
    }
    
  • thisYear을 통해 최신년도로 갱신해주기

    현재는 2023년이지만 2024년, 2025년 등, 미래에 사용할 사용자를 위해서 최신년도를 개정해주도록 한다.

    변수 thisYear을 생성한다. new Date()라는 자바스크립트의 생성자함수를 적용하고 뒤에 getFullYear 메소드를 적용하면 현재 년도에 해당하는 숫자가 반환이 된다. 현재가 2024년 이라면 i값은 2024부터 시작한다.

    const years = []
    const thisYear = new Date().getFullYear()
    for (let i = thisYear; i >= 1985; i -= 1) {
      years.push(i)
    }
    

영화 검색 사이트: 시작하기, VueRouter, Bootstrap

사이트 소개


OMDb API 활용한 영화를 검색해주는 사이트이다.

페이지는 총 3페이지로 구성되어 있다.

  • 영화 검색 페이지 (Search, 메인)

    영화를 검색할 수 있는 페이지이다. 원하는 키워드로 영화 검색이 가능하며, 3가지 조건을 설정하여 영화를 검색할 수 있다.

  • 영화 페이지 (Movie)

    검색을 통해 나타난 영화를 클릭하면 해당 영화의 상세 설명을 확인할 수 있는 페이지이다.

  • 개발자 소개 페이지 (About)

    해당 페이지를 만든 개발자를 소개한 페이지이다.

  • 기술

    비동기, 백엔드 API, Vuex, Vue Router 등의 기술등을 적용해서 페이지를 제작할 것이다.

Vue Router 구성


Router

특정한 페이지를 만들때, 페이지를 구분하기 위해서 사용한다.

Vue Router: 소개

npm i vue-router@4

vue router은 실제 브라우저에서 페이지 관리를 해줘야 하기 때문에 개발의존성이 아닌 일반의존성 모드로 설치한다.

  • src/main.js

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './routes/index.js'
      
    createApp(App)
      .use(router) 
      .mount('#app')
    

    use()는 플러그인을 연결할때 사용한다.

  • index.js

    페이지를 관리하는 구성파일이 된다.

    import { createRouter, createWebHashHistory } from 'vue-router'
    import Home from './Home'
    import About from './About'
      
    export default createRouter ({
      // Hash
      // https://google.com/#/search
      history: createWebHashHistory(),
      // pages
      // https://google.com/
      routes: [
        {
          path: '/',
          component: Home
        },
        {
          path: '/about',
          component: About
        }
      ]
    })
    
    • routes

      routes 옵션은 페이지를 구분해주는 개념이다.

    • history

      history 옵션은 hash 모드와 history 모드로 설정이 가능하다.
      우리는 hash 모드를 사용할 건데, hash 모드는 ‘#’이라는 기호와 함께 슬래쉬를 붙여서 해당 페이지에 접근하는 모드이다.
      https://google.com/#/search
      해쉬모드를 사용해야지만 특정 페이지에서 새로고침을 했을 때, 페이지를 찾을 수 없다는 메세지를 방지할 수 있다.
      history 모드는 서버에 세팅을 해야하기 때문에 이번 프로젝트에서 사용하는 것은 보류한다.

    • path

      path는 페이지를 구분해주는 경로를 의미한다.
      path 부분의 슬래쉬는 해당 페이지의 가장 메인 페이지에 접근을 하겠다는 의미이다.

    • component

      메인페이지에 접근했을 때, 어떤 vue.js의 컴포넌트를 사용할 것인지 명시한다.

    유용한 컴포넌트

    • RouterLink

      a태그를 대신하여 사용할 수 있는 컴포넌트이다.
      ’to’를 사용하여 이동하길 원하는 링크 작성한다.

Bootstrap 구성


npm i bootstrap

Bootstrap: Sass

!default

!default 플래그는 scss에서 제공하는 기능으로, 새롭게 지정되는 값이 있으면 기존 값을 무시한다는 의미이다.
bootstrap에서 지정한 파란색의 Primary 색상을 우리가 외부에서 수정하면 수정한 내용이 출력된다는 (즉, 커스터마이징) 것이다.

  • 변수 재정의

    재정의 하려는 색상(스타일)은 꼭, required보다 위에 적용해야 한다. 해당 import가 실행되기 전에 정의를 해야 bootstrap에서 기존에 정의해둔 기본값을 무시할 수 있기 때문이다.

    // Default variable overrides
    $primary: #FDC000;
      
    // Required
    @import "../../node_modules/bootstrap/scss/functions";
    @import "../../node_modules/bootstrap/scss/variables";
    @import "../../node_modules/bootstrap/scss/maps";
    @import "../../node_modules/bootstrap/scss/mixins";
    @import "../../node_modules/bootstrap/scss/root";
      
    @import "../../node_modules/bootstrap/scss/bootstrap";
    
  • $theme-colors

    해당 변수 안에 있는 값들을 모두 변경했다면, 위의 $primary와 같이 상단에 작성해준다.
    만약, 일부만 변경하고 다른 info, warning 등은 변경하지 않았다면, required 부분 뒤에 정의해야 한다. 따로 재정의를 하지 않았기에 앞에 사용하면 에러가 발생하게 된다.

BT: Containers


BootStrap: Containers

가운데 정렬을 시켜주는 레이아웃이다.
특정 사이즈에서 가운데 정렬을 시켜준다.

  • XX-Large > 1400px 과 1320px의 의미

    최대 화면 크기가 1400px일때 우리가 사용하는 .container의 가로 사이즈는 1320px이라는 의미이다.
    따라서 화면 크기가 1400px 이상일 때,1320px에서 가운데 정렬이 된다.

    만약 1200px과 1400px 사이라면 가로 사이즈는 1140px이 된다.

Error: listen EADDRINUSE: address already in use...

문제발생


npm run dev 명령어를 실행하던 중 다음과 같은 에러가 발생했다.

포트 에러 확인

해당 에러는 찾아본 결과 해당하는 포트가 사용중이라 실행할 수 없다는 내용이었다.
현재 내가 사용해야 하는 포트번호는 8080포트이다.
그럼 즉, 해당 포트를 비워주면 서버를 실행할 수 있게 된다는 것이다.

문제 해결


포트 PID 확인하기

lsof 명령어를 사용하면 포트 목록들이 모두 나오게 된다. 그러니 옵션 -i를 통해 찾고자하는 포트 번호만을 검색한다.

sudo lsof -i :8080(찾고자하는 포트 번호)

나의 포트 PID

나의 8080 포트는 PID는 71328이다.
이제 해당 포트를 비울 수 있게 된다.

포트 비우기

다음 명령어를 통해 포트를 비워준다.

sudo kill -9 71328(찾은 PID)

포트 지우기

포트를 비워준 후, 잘 비워졌는지 다시 확인해 본 후, 아무 반응도 없다면 잘 비워진 것이다.

다시 서버를 실행하면 서버가 잘 돌아가는 것을 확인할 수 있다.

서버 실행 완료

Vue.js: 컴포지션 API

개요


Vue.js: 컴포지션 API FAQ
Vue.js: 컴포지션 API: setup()

컴포지션 API

컴포지션(Composition) API는 옵션을 선언하는 대신 import한 함수를 사용하여 Vue 컴포넌트를 작성할 수 있는 API 세트이다.

컴포지션 API 사용이유

  • 더 나은 로직 재사용성

    컴포지션 API의 가장 큰 장점은 컴포저블 함수의 형태로 깔끔하고 효율적인 로직 재사용이 가능하다는 것이다. 옵션 API의 기본 로직 재사용 메커니즘인 mixins의 모든 단점을 해결한다.

  • 보다 유연한 코드 구성

    동일한 논리적 문제를 처리하는 코드가 파일의 여러 부분에 분할되어있다.
    추후 해당 로직을 이해하는데 위아래 스크롤을 통해 확인을 해야 한다. 이러한 점들은 코드의 길이가 수백 줄이 넘어가면 어려움이 발생하게 된다.
    또한 논리적 문제를 재사용 가능한 유틸리티로 추출하려는 경우, 파일의 다른 부분에서 올바른 코드 조각을 찾아 추출하는 것이 어려워진다.

    Composition API

    하지만, 컴포지션 API를 사용하게 되면 이런 어려웠던 점들이 훨씬 간편해진다.
    더 이상 다른 옵션 블록 사이를 이동할 필요가 없어지며, 추출을 위해 코드를 섞을 필요가 없기 때문에 보다 간편하게 코드 그룹을 외부 파일로 이동시킬 수 있다.
    따라서, 컴포지션 API를 사용하는 것 만으로도 리팩토링을 위한 소모 시간이 감소하게 되어 대규모 코드베이스에서 장기적인 유지 관리에 큰 도움을 준다.

  • 더 나은 타입 추론

    컴포지션 API는 기본적으로 유형 친화적인 일반 변수와 함수를 주로 사용한다. 컴포지션 API로 작성된 코드는 수동 유형 힌트가 거의 필요 없어 전체 유형 추론에 큰 도움을 준다.

  • 더 작은 프로덕션 번들 및 더 적은 오버헤드

    컴포지션 API와 script setup으로 작성된 코드는 옵션 API에 비해 더 효율적이고 축소하기 쉽다. 모든 변수 이름을 안전하게 단축할 수 있기 때문에 더 간편하게 코드 작성이 가능하다.

반응형 데이터(반응성)


Vue.js: 반응형 API: 핵심

ref()및 reactive()를 사용하여 반응형 상태, 계산된 상태, 감시자를 생성한다. 해당 함수를 사용해야지만 컴포지션 API로 정리한 후, 반응성을 띈다.

  • 컴포지션 API 사용 X

    <template>
      <div @click="increase">
        8
      </div>
    </template>
      
    <script>
    export default {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        increase() {
          this.count += 1
        }
      }
    }
    </script>
    
  • 컴포지션 API 사용 O

    <template>
      <div @click="increase">
        8
      </div>
    </template>
      
    <script>
    export default {
      setup() {
        let count = 0
        function increase() {
          count += 1
        }
      
        return {
          count,
          increase
        }
      }
    }
    </script>
    

    반응성을 가지지 않음. count 값이 갱신되지 않는다.

  • 컴포지션 API 사용 및 반응성

    ref 기능을 사용하여 반응성을 가지도록 만든다.

    <template>
      <div @click="increase">
        8
      </div>
    </template>
      
    <script>
    import { ref } from 'vue'
    export default {
      setup() {
        let count = ref(0)
        function increase() {
          count.value += 1
        }
      
        return {
          count,
          increase
        }
      }
    }
    </script>
    

    count에 바로 숫자를 할당하는 것이 아니라 ref 함수를 호출하여 할당할 값인 0을 적어준다. ref가 실행이 되면서 반응성을 가지는 객체 데이터가 반환이 된다. 따라서 count는 반응성을 가질 수 있게 된다.
    하지만, 하나의 ‘객체’ 데이터가 반환이 되는 것이다 보니, count를 데이터로 직접 사용할 수 없다. 데이터로 활용하기 위해서 count의 객체 데이터의 value 속성을 통해 우리가 원하는 데이터에 접근 할 수 있다.

기본 옵션과 라이프사이클


Vue.js: 컴포지션 API: 수명주기 훅

라이프사이클 훅에 접두사 ‘on’을 추가함으로 컴포넌트의 라이프사이클 훅에 접근할 수 있다.

  • computed

    컴포지션 API 미사용

    computed: {
      doubleCount() {
        return this.count * 2
      }
    }
    

    컴포지션 API 사용

    import { computed } from 'vue'
    ...
    setup() {
      const doubleCount = computed(() => {
        return count.value * 2
      })
    }
    
  • watch

    컴포지션 API 미사용

    watch: {
      message(newvalue) {
          console.log(newValue)
      }
    }
    

    컴포지션 API 사용

    import { watch } from 'vue'
    ...
    setup() {
      watch(message, newValue => {
        console.log(newValue)
      })
    }
    
  • created

    created는 컴포넌트가 생성된 직후에서 실행이 되는 라이프사이클이다.

    컴포지션 API 미사용

    created() {
      console.log(this.message)
    }
    

    컴포지션 API 사용

    setup() {
      console.log(message.value)
    }
    

    setup이라는 메소드는 컴포넌트가 생성된 직후에 실행이 되기 때문에 setup 메소드 내부에서 실행되는 기본적인 로직은 created에 작성하는 것과 동일한 효과를 갖는다.
    따라서, created는 따로 import를 해야 할 것도, 메소드는 사용할 것도 없이 그냥 setup 내부에 작성하면 된다. (setup 내부 어디에서든지 사용해도 된다.)

  • mounted (onMounted)

    mounted는 html과 컴포넌트가 연결이 된 직후에 실행이 되는 라이프사이클이다.

    컴포지션 API 미사용

    mounted() {
      console.log(this.count)
    }
    

    컴포지션 API 사용

    import { onMounted } from 'vue'
    ...
    setup() {
      onMounted(() => {
        console.log(count.value)
      })
    }
    

    mounted를 컴포지션 API에서 사용하려면 onMounted로 작성해야 한다.

props, context


기존에는 this를 통해서 color (props 옵션에 정의된)에 바로 접근을 할 수 있었다.
this를 통해 $attrs(상속받은 모든 속성 개체)를 가져올 수 있었다.

컴포지션API에서는 this를 사용할 수 없기 때문에 this.colorprops.color로, this.$attrscontext.attrs로 바꿔서 사용할 수 있다.

컴포지션 API 미사용

props: {
	color: {
		type: String,
		default: 'gray'
	}
},
emits: ['hello'],
mounted() {
	console.log(this.color)
	console.log(this.$attrs)
},
methods: {
	hello() {
		this.$emit('hello')
	}
}

컴포지션 API 사용

import { onMounted } from 'vue'
...
props: {
	color: {
		type: String,
		default: 'gray'
	}
},
emits: ['hello'],
setup(props, context) {
	function hello() {
		context.emit('hello')
	}
	onMounted(() => {
		console.log(props.color)
		console.log(context.attrs)
	})

	return {
		hello
	}
}
  • props

    setup함수의 첫 번째 인자는 props이다.   setup함수 내부의 props는 반응형이며, 새 props가 전달되면 업데이트된다.
    props 객체를 분해할 경우, 분해 된 변수는 반응성을 잃게 되기에 항상 props.xxx 형태처럼 접근해야 한다.

  • context

    setup함수에 전달되는 두 번째 인자는 context객체이다.
    context 객체는 setup 내부에서 유용할 수 있는 다른 값을 노출한다. context 객체는 반응형이 아니기에 안전하게 분해 할당될 수 있다.

Vue.js: Vue 문법 Ep.9 컴포넌트2

컴포넌트: Slot


Vue.js: 슬롯

  • Fallback contents

App.vue(부모 컴포넌트) 파일에서 MyBtn 컴포넌트 태그 사이에 컨텐츠가 없을 때, MyBtn.vue(자식 컴포넌트) 파일의 slot 태그 사이에 있는 컨텐츠가 출력되는 것을 말한다.

  • 이름을 갖는 슬롯 (Named Slots)

    slot에 name 속성을 부여해서 순서가 어떻게 되었던 간에 해당 slot의 이름 순서에 맞게 화면에 출력하도록 만들어 준다.

    <template>
      <MyBtn>
        <template
          v-slot:
          icon>
          <span>(B)</span>
        </template>
        <template #text>
          <span>Banana</span>
        </template>
      </MyBtn>
    </template>
    

    v-slot을 통해서 원하는 컨텐츠에 이름을 지정해준다.
    이때, v-slot을 축약하여 ‘#’ 으로 작성할 수 있다.

    <template>
      <div class="btn">
        <slot name="icon"></slot>
        <slot name="text"></slot>
      </div>
    </template>
    

    App.vue 파일에서 icon과 text의 순서가 뒤바뀌어도 MyBtn에서는 slot의 순서가 icon이름을 갖는 것이 가장 먼저이기 때문에 icon 이름을 갖는 컨텐츠가 먼저 출력된다.

컴포넌트: Provide, Inject


Vue.js: Provide(제공),Inject(주입)

  • Prop 드릴링

    일반적으로 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달해야 할 때 props를 사용한다.
    그러나 큰 컴포넌트 트리가 존재하고 저 아래에 있는 컴포넌트에서 상위 컴포넌트의 무언가가 필요할 때, 아래 컴포넌트에서 위로 올라가기 위해 다른 부모 컴포넌트에도 동일한 props를 전달해야 한다.

    Prop 드릴링

    예를 들어, Footer 컴포넌트는 이 prop가 전혀 필요하지 않더라도 DeepChild가 접근할 수 있도록 선언하고 전달해야 한다. 더 긴 상위 체인이 있다면 해당 과정을 통해 더 많은 컴포넌트가 영향을 받게 된다. 이것을 prop 드릴링이라고 한다.

  • Prop 드릴링 해결방법

    우리는 provide와 inject로 props 드릴링을 해결할 수 있다. 부모 컴포넌트는 모든 자식 컴포넌트에 대한 의존성 제공자 역할을 할 수 있다. 하위 트리의 모든 컴포넌트는 깊이에 관계없이 상위 체인의 컴포넌트에서 제공(provide)하는 의존성을 주입(inject)할 수 있다.

    Provide와Inject

  • Provide와 Inject

    • Provide

      provide 옵션을 추가하여 컴포넌트 하위 항목에 데이터를 제공한다.

      export default {  
        provide: {    
          message: '안녕!'  
        }
      }
      

      provide 객체의 각 속성에 대해 키는 주입한 값을 올바르게 찾기 위해 사용되고, 값은 주입된다.

    • Inject

      inject 옵션을 통해 부모 컴포넌트에서 제공하는 데이터를 주입한다.

      export default {
        inject: ['message'],
        created() {
          console.log(this.message) // 주입된 값
        }
      }
      

      inject는 컴포넌트 자체 상태보다 먼저 구성되므로, data()에서 주입된 속성에 접근할 수 있다.

    • 반응형으로 만들기

      provide를 사용할 때는, 일반적으로 반응성을 제공할 수 없다.
      따라서, 데이터를 전달해서 한번 출력하는 용도로 사용하거나 반응성을 유지하게 하려면 추가 작업을 해야 한다.

      제공자로부터 반응형으로 연결된 주입을 만들기 위해, computed() 함수를 사용하여 계산된 속성을 제공한다.

      import { computed } from 'vue'
          
      export default {
        data() {
          return {
            message: '안녕!'
          }
        },
        provide() {
          return {
            // 계산된 속성을 명시적으로 제공
            message: computed(() => this.message)
          }
        }
      }
      

      기존에 있었던 this.message 부분을 지우고 computed 함수를 실행한다. 함수의 콜백으로 화살표 함수를 작성하고 반응성을 가지고 싶은 데이터를 반환하면 된다.

컴포넌트: Refs


ref

해당하는 특정한 요소를 지정한 이름으로 참조하겠다는 의미이다.
참조된 내용은 $refs 객체에 지정한 이름으로 들어가서 저장이 된다.

<template>
  <h1 ref="hello">
    Hello World!
  </h1>
</template>

<script>

export default {
  mounted() {
    console.log(this.$refs.hello)
  }
}
</script>
  • 주의할 점

    해당하는 요소들은 해당 컴포넌트가 html 구조와 연결이 된 직후에만 사용이 가능하다. 따라서 created()라는 라이프사이클에서는 해당 내용을 제대로 사용할 수 없다.