[FrontEnd] Vue2.js TO Vue3.js

eunniverse·2024년 3월 3일
0
post-custom-banner

Vue 3로 마이그레이션을 고려해볼 만한 하는 이유

  • Vue 2는 2023년 12월에 지원이 종료된다.
  • Vue 3 는 성능 향상, 타입스크립트 지원 확대, 컴포지션 API 지원, 다양한 컴포넌트(Suspense, fragment, teleport) 등을 제공한다.
  • Vue 3 는 더 작은 번들 크기를 가지므로 초기 로딩 시간을 줄일 수 있다.

Vue3 특징 (with Vue2)

  • 성능 향상

    • 가상돔 최적화
      • 가상돔(Virtual Dom) 동작 원리
        1. UI 변경 시 전체 UI 를 가상 돔으로 렌더링
        2. 현재 가상 돔과 이전 가상 돔을 비교해 차이 계산
        3. 변경된 부분만 실제 돔에 반영
      • 기존 Vue2 에서는 매번 전체 트리를 확인했기 때문에 불필요한 탐색이 포함되었으나, Vue3에서는 템플릿 구문에 정적 요소와 동적 요소를 구분하여 트리를 순환할 때 동적 요소만 순환해서 렌더링 트리의 탐색을 최적화 하는 방식으로 변경됨
  • 강력한 타입스크립트 연계

    • Vue3 에 내부 로직 타입스크립트로 재작성
  • Inject 와 Provide

    • props 를 개선한 버전

    • 기존 props 사용 방식 (aka. prop 드릴링)

    • props 개선 방식 (aka. Inject & Provide)

    • provide 와 inject 사용 방법

      /* 
      	provide 사용법 
         - <script setup> 이 아니라면, setup 함수에서 사용
         - 앱 수준 제공 가능 : 앱에서 렌더링되는 모든 컴포넌트에서 사용
      */
      <script setup>
      import { ref, provide } from 'vue'
      import { fooSymbol } from './injectionSymbols'
      
      // 키 : 자식 컴포넌트에서 값을 조회할 때 사용
      // 값 : 제공되는 값으로 모든 타이 가능함
      provide(/* 키 */ 'message', /* 값 */ '안녕!')
      
      // key 중복방지를 위해 symbol 을 사용하여 key 생성 후 사용 가
      provide(fooSymbol, count)
      
      // 반응성 값을 주입시켰기 때문에 자식 컴포넌트에서도 hello 데이터를 변경할 수 있음
      const refData = ref(0);
      provide('hello', refData);
      </script>
      
      /*
      	inject 사용법
         - <script setup> 이 아니라면, setup 함수에서 동기적으로 호출이 필요함
      */
      <script setup>
      import { inject } from 'vue'
      import { fooSymbol } from './injectionSymbols'
      
      // message 로 주입된 데이터를 받음
      const message = inject('message')
      
      // 두번째 인자는 foo 키로 주입된 데이터가 없을 때 사용하는 기본
      const bar = inject('foo', 'default value')
      
      // fooSymbol 로 주입된 데이터를 받음
      const foo2 = inject(fooSymbol)
      
      // num 데이터가 없을 경우, 동작할 함수를 정의
      // 세번째 매개변수는 기본값이 팩토리함수로 처리되어야 하는지 나타냄
      const num = inject('num', () => { return Math.random() }, true);
      </script>
  • 빌트인 컴포넌트

    • Transition / TransitionGroup
      • vue 2 → vue 3 업그레이드할 때 수정할 부분

        transition 과 연관된 prop 명칭을 변경한다

        ex) v-enter → v-enter-from 로 변경

        // vue 2 
        .v-enter,
        .v-leave-to {
          opacity: 0;
        }
        
        .v-leave,
        .v-enter-to {
          opacity: 1;
        }
        
        // vue 3
        .v-enter-from,
        .v-leave-to {
          opacity: 0;
        }
        
        .v-leave-from,
        .v-enter-to {
          opacity: 1;
        }
      • 차이점

        | transition | transitionGroup |
        | --- | --- |
        | key 속성 x | key 속성을 필수로 가져야 함 |
        | 엘리먼트에 적용됨 | 목록 개별에 transition 이 적용됨 |
        // css 애니메이션 사용할 경우
        <Transition name="bounce">
          <p v-if="show" style="text-align: center;">
            안녕! 여기에 탄력적인 텍스트가 있어요!
          </p>
        </Transition>
        .bounce-enter-active {
          animation: bounce-in 0.5s;
        }
        .bounce-leave-active {
          animation: bounce-in 0.5s reverse;
        }
        
        // JS 에서 트랜지션 처리하는 경우
        <Transition
          @before-enter="onBeforeEnter"
          @enter="onEnter"
          @after-enter="onAfterEnter"
          @enter-cancelled="onEnterCancelled"
          @before-leave="onBeforeLeave"
          @leave="onLeave"
          @after-leave="onAfterLeave"
          @leave-cancelled="onLeaveCancelled"
        >
          <!-- ... -->
        </Transition>
        // 엘리먼트가 DOM에 삽입되기 전에 호출됩니다.
        // 이것을 사용하여 엘리먼트의 "enter-from" 상태를 설정합니다.
        function onBeforeEnter(el) {}
        
        // 엘리먼트가 삽입되고 1 프레임 후 호출됩니다.
        // 진입 애니메이션을 시작하는 데 사용합니다.
        function onEnter(el, done) {
          // CSS와 함께 사용되는 경우, 선택적으로 
          // 트랜지션 종료를 나타내기 위해 done 콜백을 호출합니다.
          done()
        }
        
        // 진입 트랜지션이 완료되면 호출됩니다.
        function onAfterEnter(el) {}
        
        // 진입 트랜지션이 완료되기 전, 취소될 때 호출됩니다.
        function onEnterCancelled(el) {}
        
        // 진출 훅 전에 호출됩니다.
        // 대부분의 경우 그냥 진출 훅을 사용해야 합니다.
        function onBeforeLeave(el) {}
        
        // 진출 트랜지션이 시작될 때 호출됩니다.
        // 진출 애니메이션을 시작하는 데 사용합니다.
        function onLeave(el, done) {
          // CSS와 함께 사용되는 경우, 선택적으로 
          // 트랜지션 종료를 나타내기 위해 done 콜백을 호출합니다.
          done()
        }
        
        // 진출 트랜지션이 완료되고,
        // 엘리먼트가 DOM에서 제거된 후 호출됩니다.
        function onAfterLeave(el) {}
        
        // v-show 트랜지션에서만 사용 가능합니다.
        function onLeaveCancelled(el) {}
    • KeepAlive
      • 컴포넌트 간에 동적으로 전환될 때 컴포넌트 인스턴스를 캐시할 수 있는 컴포넌트
    • Suspense (NEW!)
      • 컴포넌트 트리에서 비동기 의존성을 조정하기 위한 컴포넌트
      • 개발 완료된 안정된 상태 X
    • Teleport (이해필요..)
  • Composition API

    • 컴포넌트를 구성하고 조합하기 위해 사용하는 새로운 API 세트
    • 기존 Vue2 에서 제공되었던 Options API 가 폐기되는 것은 아니며, 플러그인을 추가하여 사용할 수 있음 (https://github.com/vuejs/composition-api)
    • WHY?
      • 더 나은 로직 재사용

      • 더 유연한 코드 구성

      • 더 나은 유형 추론

      • 더 작은 번들과 더 적은 오버헤드

        ** 연관된 로직은 하나로 묶어 생산성 높게 사용하기 위함 **

    • 예시
      • option API 사용 예시
        // src/components/UserRepositories.vue
        
        export default {
          components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
          props: {
            user: { type: String }
          },
          data () {
            return {
              repositories: [], // 1
              filters: { ... }, // 3
              searchQuery: '' // 2
            }
          },
          computed: {
            filteredRepositories () { ... }, // 3
            repositoriesMatchingSearchQuery () { ... }, // 2
          },
          watch: {
            user: 'getUserRepositories' // 1
          },
          methods: {
            getUserRepositories () {
              // `this.user`를 사용해서 유저 레포지토리 가져오기
            }, // 2
            updateFilters () { ... }, // 3
          },
          mounted () {
            this.getUserRepositories() // 1
          }
        }
      • composition API 사용 예시
        // src/components/UserRepositories.vue
        import { toRefs } from 'vue'
        import useUserRepositories from '@/composables/useUserRepositories'
        import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
        import useRepositoryFilters from '@/composables/useRepositoryFilters'
        
        export default {
          components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
          props: {
            user: { type: String }
          },
        
        	/**
        		props 가 resolved 될 때 실행되는 함수
        		 beforeCreate , created 는 없음 
        	**/
          setup(props) {
        
        		// 반응성 변수로 만들기
        		// toRefs 를 사용했기 때문에 value 로 접근
            const { user } = toRefs(props)
        
            const { repositories, getUserRepositories } = useUserRepositories(user)
        
            const {
              searchQuery,
              repositoriesMatchingSearchQuery
            } = useRepositoryNameSearch(repositories)
        
            const {
              filters,
              updateFilters,
              filteredRepositories
            } = useRepositoryFilters(repositoriesMatchingSearchQuery)
        
            return {
              repositories: filteredRepositories, // repositories 에 필터링된 데이터를 넣기 위함
              getUserRepositories,
              searchQuery,
              filters,
              updateFilters
            }
          }
        }
        // src/composables/useUserRepositories.js
        
        import { fetchUserRepositories } from '@/api/repositories'
        import { ref, onMounted, watch, toRefs } from 'vue'
        
        // composable 예시 1
        export default function useUserRepositories(user) {
          const repositories = ref([])
          const getUserRepositories = async () => {
            repositories.value = await fetchUserRepositories(user.value)
          }
        
        	// mounted 에서 
          onMounted(getUserRepositories)
        	
        	/**
        	  watch 가능 파라미터 
        			1. 감시를 원하는 참조변수 or function
        			2. 콜백함수
        			3. watchOptions (deep, immediate)
        	**/
          watch(user, getUserRepositories)
        
          return {
            repositories,
            getUserRepositories
          }
        }
        // src/composables/useRepositoryNameSearch.js
        
        import { ref, onMounted, watch, toRefs } from 'vue'
        
        // composable 예시 1
        export default function useRepositoryNameSearch(repositories) {
          const searchQuery = ref('')
        
          const repositoriesMatchingSearchQuery = computed(() => {
            return repositories.value.filter(repository => {
              return repository.name.includes(searchQuery.value)
            })
          })
        
          return {
            searchQuery,
            repositoriesMatchingSearchQuery
          }
        }
      • 알아두면 쓸 데 있을 것 같은 vue 지식 (aka. 알쓸같뷰)
        • refs 와 reactive
          • 공통점 : 반응성을 제공하기 위해 사용한다.
          • 차이점
            reactive()하위 중첩 속성까지 전파되는 깊은 반응성으로, 깊은 반응성이 싫다면 shallowReative() 를 사용하면 된다.
            객체 타입에서만 사용 가능
            사용 예시
            const state = { num: 11, status: {feeling: ‘good’}};
            const rState = reactive(state); |
            | ref() | javascript 영역에서는 .value 로 값을 접근할 수 있고, template 영역에서는 .value 를 사용하지 않는다. |
            | | 기본형, 참조형 모두 사용 가능 |
            | | 사용 예시
            const state = { num: 11, status: {feeling: ‘good’}};
            const rState = ref(state); const num = 11;
            const rNum = ref(num); |
        • composable 과 composition API
          • 정의
            • composition API 를 활용하여 상태 저장 로직을 캡슐화하고 재사용하는 함수
          • 특징
            • 여러 곳에서 로직을 재사용 하기 용이하다.
            • 하나의 composable 함수에서 다른 composable 함수를 호출할 수 있다.
            • composable 함수 이름은 use 로 시작하는 camelCase 로 지정하는 것이 관례다.
          • option API 에서 composable 적용
            • setup() 에서 호출되어야 하고, return 데이터 또한 setup() 에서 반환 되어야 한다.

              import { useMouse } from './mouse.js'
              import { useFetch } from './fetch.js'
              
              export default {
                setup() {
                  const { data, error } = useFetch('...')
                  return { x, y, data, error }
                },
                mounted() {
                  // setup()에서 노출된 속성은 `this`에서 접근할 수 있습니다.
                  console.log(this.x)
                }
              }
          • 예제 (링크)
            // 주로 쓰는 방법
            // 모든 컴포넌트에서 사용한다면 이 패턴을 반복해야하지만, 이 부분을 composable 함수로 뺀다면 재사용이 가능하다.
            <script setup>
            import { ref } from 'vue'
            
            const data = ref(null)
            const error = ref(null)
            
            fetch('...')
              .then((res) => res.json())
              .then((json) => (data.value = json))
              .catch((err) => (error.value = err))
            </script>
            
            <template>
              <div v-if="error">! 에러 발생: {{ error.message }}</div>
              <div v-else-if="data">
                로드된 데이터:
                <pre>{{ data }}</pre>
              </div>
              <div v-else>로딩...</div>
            </template>
            // useFetch.js
            import { ref, isRef, unref, watchEffect } from 'vue'
            
            export function useFetch(url) {
              const data = ref(null)
              const error = ref(null)
            
              async function doFetch() {
                // 가져오기 전에 상태 재설정..
                data.value = null
                error.value = null
                
                // watchEffect()에 의해 종속성으로 추적되도록
                // URL 값을 동기식으로 resolve합니다.
                const urlValue = unref(url)
                
                try {
                  // 인위적인 딜레이 / 무작위 애러
              	  await timeout()
              	  // unref()는 ref이면 ref 값을 반환합니다.
                  // 그렇지 않으면 값을 있는 그대로 반환합니다.
                	const res = await fetch(urlValue)
            	    data.value = await res.json()
                } catch (e) {
                  error.value = e
                }
              }
            
              if (isRef(url)) {
                // 설정하기: 입력 URL이 ref인 경우 반응적 다시 가져오기
                watchEffect(doFetch)
              } else {
                // 그렇지 않으면 한 번만 가져오기
                doFetch()
              }
            
              return { data, error, retry: doFetch }
            }
            <script setup>
            import { ref, computed } from 'vue'
            import { useFetch } from './useFetch.js'
            
            const baseUrl = 'https://jsonplaceholder.typicode.com/todos/'
            const id = ref('1')
            const url = computed(() => baseUrl + id.value)
            
            const { data, error, retry } = useFetch(url)
            </script>
            
            <template>
              Load post id:
              <button v-for="i in 5" @click="id = i">{{ i }}</button>
            
            	<div v-if="error">
                <p>! 오류 발생: {{ error.message }}</p>
                <button @click="retry">재시도</button>
              </div>
              <div v-else-if="data">로드된 데이터: <pre>{{ data }}</pre></div>
              <div v-else>로딩...</div>
            </template>
        •   <template>
              <**MyComponent** />
            </template>
            
            **// 동적 컴포넌트**
            <script setup>
            import Foo from './Foo.vue'
            import Bar from './Bar.vue'
            </script>
            
            <template>
              <component :is="Foo" />
              <component :is="someCondition ? Foo : Bar" />
            </template>
            
            **// 재귀 컴포넌트**
            import { FooBar as FooBarChild } from './components'
            
            <template>
              <FooBar />
            </template>
            
            **// 네임스페이스 컴포넌트**
            // 단일 파일에서 여러 컴포넌트를 가져올 때 사용
            <script setup>
            import * as Form from './form-components'
            </script>
            
            <template>
              <Form.Input>
                <Form.Label>레이블</Form.Label>
              </Form.Input>
            </template>
            ```
            

마이그레이션 방법 요약

  • vue 에서 제공하는 compat npm 설치 후 마이그레이션 진행
  • compat 이란
    • Vue3 를 지원하도록 사용하는 라이브러리 마이그레이션 빌드
    • 제약 사항
      • IE 11 지원 X
  • 순서
    1. vue-cli와 Vue 버전 업그레이드 후 @vue/compat 설치
      1. vue-cli 환경

          @vue/cli-service 업그레이드
          
      2. webpack 환경
          
          vue-loader 를 ^16.0.0 로 업그레이드
          
      "dependency": {
    • "vue": "^2.6.12",
    • "vue": "^3.1.0",
    • "@vue/compat": "^3.1.0"
      ...
      },
      "devDependency": {
    • "vue-template-compiler": "^2.6.12" + "@vue/compiler-sfc": "^3.1.0"
      }
    1. compat 모드 호환

      1. vue-cli 예시
      // vue.config.js
      module.exports = {
        chainWebpack: config => {
          config.resolve.alias.set('vue', '@vue/compat')
          config.module
            .rule('vue')
            .use('vue-loader')
            .tap(options => {
              return {
                ...options,
                compilerOptions: {
                  compatConfig: {
                    MODE: 2
                  }
                }
              }
            })
      }
    2. 컴파일 에러 고치기

    3. Vue 3 주요 변경사항에 따라 코드 수정하기

    4. 주요 패키지(vuex, vue-router 등) 버전 업그레이드 하기

    5. 그 외 라이브러리들 버전 맞추거나 교체하기

    6. @vue/compat 제거하기

https://velog.io/@kdeun1/Vue-3.1.0-beta-출시-번역-요약

profile
능력이 없는 것을 두려워 말고, 끈기 없는 것을 두려워하라
post-custom-banner

0개의 댓글