본문 바로가기
개발자의 정보/JS & framework

대규모 Vue.js 3 애플리케이션 구축을 위한 6가지 팁

by pastory 2021. 7. 30.

https://vueschool.io/articles/vuejs-tutorials/6-tips-for-building-large-scale-vue-js-3-applications/?fbclid=IwAR1BOzf54d7NQ5S8FYfy_wABsq-tRwvGPqbdAeTyDKdHVSWhMyg5MHnkWo0

Vue.js 3은 크고 작은 애플리케이션을 구축하기 위한 견고한 프레임워크입니다. 대규모 Vue.js 애플리케이션을 구조화하는 방법 시리즈에서 대규모 프로젝트에 이를 가장 잘 활용하는 방법을 탐구했습니다. 우리는 좋은 파일 구조가 무엇인지, 예측 가능성에 대한 몇 가지 표준, 깨끗하고 일관된 코드를 위해 ESLint 및 Prettier를 사용하는 방법을 조사했습니다.

이 기사에서는 Vue 커뮤니티와 대규모 Vue.js 애플리케이션 개발 경험에서 얻은 6가지 팁을 살펴보겠습니다.

팁 #1: 믹스인보다 컴포저블을 선호하세요

Vue 3는 반응 상태를 포함할 수 있는 재사용 가능한 논리 청크를 생성할 수 있는 기능을 제공합니다. 이러한 재사용 가능한 청크를 일반적으로 컴포저블 이라고 합니다. 그것들은 (다른 것들과 함께) 믹스인과 유사한 기능을 제공할 수 있지만 몇 가지 장점이 있습니다.

컴포저블:

  • 구성 요소 데이터, 방법 등의 투명한 소스 제공
  • 명명 충돌 제거
  • IDE에서 해석하고 자동 완성하는 등의 작업을 수행할 수 있는 일반 JavaScript입니다.

이러한 점 때문에 다음 대규모 Vue.js 3 애플리케이션을 위해 믹스인보다 컴포저블을 사용하는 것이 좋습니다.

컴포저블은 어떻게 생겼나요? 다음은 믹스인을 컴포저블과 비교하는 간단한 예입니다. 둘 다 Ajax 요청을 통해 게시물을 가져오고, 요청 상태를 유지하고, 가져온 게시물을 유지하기 위한 재사용 가능한 코드를 보여줍니다.

// FetchPostMixin.js
const REQUEST_IN_PROGRESS = "REQUEST_IN_PROGRESS";
const REQUEST_ERROR = "REQUEST_ERROR";
const REQUEST_SUCCESS = "REQUEST_SUCCESS";

export default {
  data() {
    return {
      requestState: null,
      post: null
    };
  },
  computed: {
    loading() {
      return this.requestState === REQUEST_IN_PROGRESS;
    },
    error() {
      return this.requestState === REQUEST_ERROR;
    }
  },
  methods: {
    async fetchPost(id) {
      this.post = null;
      this.requestState = REQUEST_IN_PROGRESS;
      try {
        const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
        this.post = await res.json();
        this.requestState = REQUEST_SUCCESS;
      } catch (error) {
        this.requestState = REQUEST_ERROR;
      }
    }
  }
};

// PostComponent.vue
<script>
import FetchPostMixin from "./FetchPostMixin";
export default {
  mixins:[FetchPostMixin],
};
</script>

이 믹스인을 사용하면 믹스인의 데이터 속성 등과 같은 이름을 가진 데이터 속성 등이 구성 요소에 포함되지 않도록 주의해야 합니다. 또한 구성 요소에 하나 이상의 믹스인이 등록되어 있으면 특정 데이터, 메서드 등이 어디에서 왔는지 한 눈에 알 수 없습니다.

이제 이를 컴포저블과 비교합니다.

//FetchPostComposable.js
import { ref, computed } from "vue";

export const useFetchPost = () => {

  // Request States
  const REQUEST_IN_PROGRESS = "REQUEST_IN_PROGRESS";
  const REQUEST_ERROR = "REQUEST_ERROR";
  const REQUEST_SUCCESS = "REQUEST_SUCCESS";
  const requestState = ref(null);
  const loading = computed(() => requestState.value === REQUEST_IN_PROGRESS);
  const error = computed(() => requestState.value === REQUEST_ERROR);

  // Post
  const post = ref(null);
  const fetchPost = async (id) => {
    post.value = null;
    requestState.value = REQUEST_IN_PROGRESS;
    try {
      const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
      post.value = await res.json();
      requestState.value = REQUEST_SUCCESS;
    } catch (error) {
      requestState.value = REQUEST_ERROR;
    }
  };
  return { post, loading, error, fetchPost };
};

// PostComponent.vue
<script>
import { useFetchPost } from "./FetchPostComposable";
export default {
  setup() {
    //clear source of data/methods
    const { 
      loading: loadingPost, //can rename
      error, fetchPost, post } = useFetchPost();

    return { loadingPost, error, fetchPost, post };
  },
};
</script>

이제 논리적 관심사별로 그룹화할 수 있을 뿐만 아니라 구성 요소에서 각 데이터 속성, 메서드 등의 출처를 명시적으로 볼 수 있습니다. 또한 구성 요소에 이미 존재하는 항목과 충돌하는 것을 방지하기 위해 컴포저블에서 오는 모든 항목의 이름을 쉽게 바꿀 수 있습니다.

팁 #2: 구성 요소 간에 항상 개체 복제

Vue에서 반응형 데이터로 작업할 때 구성 요소 간에 개체를 전달할 수 있습니다. 이것은 매우 편리할 수 있지만 의도하지 않은 부작용이 있을 수도 있습니다. 예를 들어 이 예를 들어 보겠습니다. 로그인 페이지에는 사용자 데이터 속성이 있습니다. namepassword 속성이 있는 개체이며 LoginForm 구성 요소에 전달됩니다.

// LoginPage.vue
<template>
  <LoginForm :user="user" />
</template>
<script>
import LoginForm from './LoginForm.vue'
export default {
  components: {LoginForm},
  data(){
    return {
      user: { name: '', password: ''}
    }
  }
}
</script>

로그인 양식은 사용자를 받아 속성을 직접 변경합니다.

// LoginForm.vue
<template>
  <div>
    <label>
      Name <input type="text" v-model="user.name">
    </label>
    <label>
      Password <input type="password" v-model="user.password">
    </label>
  </div>
</template>
<script>
export default {
  props:{
    user: Object
  }
}
</script>

로그인 페이지에 몇 가지 제목을 추가하여 진행 상황을 좀 더 잘 볼 수 있도록 하겠습니다.

<template>
  <h1>Parent Component</h1>
  <code>{{user}}</code>
  <h1>Child Component</h1>
  <LoginForm :form="user" />
</template>

이제 입력을 입력하면 LoginForm에서 사용자를 변경하여 실제로 상위 구성 요소에서도 사용자를 변경한다는 것을 알 수 있습니다.

이것은 구성 요소 간에 통신하는 방식이 아닙니다. 대신 LoginForm 구성 요소는 사용자 데이터가 변경될 때 이벤트를 내보내야 하고 LoginPage가 이벤트를 수신하고 사용자 데이터를 업데이트하려는 경우 그렇게 할 수 있지만 반드시 그래야 하는 것은 아닙니다.

Vue로 처음 작업을 시작했을 때 이것이 일찍 그리고 자주 문제를 일으켰다는 것을 알고 있습니다. 어떻게 해결해야 할까요? 객체를 속성으로 사용하는 구성 요소를 생성하는 경우 변경하기 전에 객체를 로컬 데이터 속성에 복제해야 합니다.

// LoginForm.vue
<template>
 ...
<input type="text" v-model="form.name">
<input type="password" v-model="form.password">
</template>
<script>
export default {
  //...
  data(){
    return {
      // spreading an object effectively clones it's top level properties
      // for objects with nested objects you'll need a more thorough solution
      form: {...this.user}
    }
  },
}
</script>

반응 객체를 Vuex 작업이나 구성 요소 인스턴스 외부의 다른 곳으로 전달할 때도 동일한 개념이 적용됩니다.

팁 #3: 네임스페이스 Vuex 스토어 모듈 사용

Vuex는 애플리케이션 전체 상태를 관리하기 위한 훌륭한 패턴을 제공하지만, 해당 전역 상태가 너무 많이 존재하는 경우 저장 파일이 부풀려지고 탐색하기 어려워 빠르게 끝날 수 있습니다.

이 문제를 해결하기 위해 Vuex는 스토어를 각각 고유한 도메인을 처리하는 다른 모듈로 나눌 수 있는 기능을 제공합니다(즉, 한 모듈은 게시물, 다른 사용자, 다른 댓글 등을 처리함).

모듈 없이

// store/index.js
export default {
  state:{
    posts:[],
    users:[],
    comments:[]
  },
  actions:{
    fetchPost(){},
    fetchPosts(){},
    fetchUser(){},
    fetchUsers(){},
    fetchComment(){},
    fetchComments(){},
  }
}

모듈 포함

// store/index.js
import posts from './modules/posts.js'
import users from './modules/users.js'
import comments from './modules/comments.js'
export default{
  modules:{ posts, users, comments }
}

//store/modules/posts.js 
// 그리고 다른 각 도메인에 대해서도 동일합니다.
// 공통 상태, 이름이 같은 작업(네임스페이스로 인해 가능)
// 및 적합으로 명명된 기타 도메인 특정 상태 및 논리
export default {
  namespaced: true,
  state:{
    items:[]
  },
  actions:{
    fetchOne(){},
    fetchAll(){}
  }
}

이러한 모듈 작업에 대한 정확한 구문을 보려면 공식 Vuex 문서를 방문하십시오. 상점 탐색이 더 쉬울 뿐만 아니라 작업을 호출하고 상태에 동적으로 액세스하는 것이 더 쉬울 것입니다.

성장하는 프로젝트에서 Vuex를 활용하고 있고 아직 모듈을 사용하고 있지 않다면 늦은 상태에서 네임스페이스 모듈로 리팩토링하는 것이 매우 고통스러울 수 있기 때문에 지금은 리팩토링할 시간입니다.

팁 #4: 테스트 작성

소프트웨어는 성장함에 따라 더욱 복잡해질 것이므로 소프트웨어가 성장함에 따라 일이 예상대로 계속 작동하는지 신속하게 확인할 수 있는 메커니즘을 갖추는 것이 중요합니다.

그렇다면 코드베이스를 테스트 가능하게 만드는 것은 무엇입니까? 제가 테스팅 전문가는 아니지만 제 경험을 통해 테스팅에 대해 몇 가지를 배웠습니다.

  1. "단위"에 더 집중할수록(클래스, 함수, 구성 요소 등) 테스트하기가 더 쉽습니다.
  2. 코드 조각이 의존하는 외부 종속성이 많을수록 일반적으로 테스트하기가 더 어렵습니다.

Vue 프로젝트의 경우 이는 테스트 가능성을 높이기 위해 수행할 수 있는 몇 가지 실용적인 단계가 있음을 의미합니다.

  1. 가능한 한 자주 구성 요소는 외부 종속성을 가지지 않도록 모든 소품을 다운/이벤트로 만듭니다.
  2. 컴포넌트 컨텍스트 외부에서 테스트할 수 있는 도우미 기능에 재사용 가능한 논리 추출
  3. 도우미 기능에 단일 책임을 부여하고 부작용을 일으키지 않도록 합니다.
  4. 구성 요소를 테스트할 때 Vue Test Utils보다 높은 수준의 추상화(그리고 그 위에 구축된)인 Vue Testing Library를 활용합니다.

무엇보다, 하기 싫을 때도 테스트를 작성하도록 하세요... 약속합니다. 나중에 스스로에게 감사할 것입니다. 아, 그리고 테스트 실행을 자동화하는 좋은 방법이 있고 테스트 실패로 인해 배포(CI/CD)가 방지됩니다. 그렇지 않으면 실행되지 않는 테스트 제품군이 생성됩니다.

팁 #5: SDK를 통해 REST API와 상호작용

먼저 SDK 란 무엇입니까? SDK를 실제 API와 상호 작용하기 위한 언어별 API로 생각할 수 있습니다. 따라서 axios를 호출하거나 구성 요소 내에서 직접 가져오는 대신 post.find(1)과 같은 것을 사용할 수 있습니다.

그렇다면 어떤 것을 쓰는 것이 좋을까요? API 엔드포인트에 대한 하드 코딩된 요청입니다.

axios('https://someapi.com/posts/1')

또는 리소스 클래스에서 자동 완성 가능한 메서드입니까?

post.find(1)

나에게 답은 분명하다. 하지만 조금 더 설득력이 필요할 수도 있습니다. 사용 가능한 경우 SDK를 활용하거나 고유한 REST API 엔드포인트에 대해 고유한 SDK를 생성하면 다음과 같은 이점을 제공할 수 있습니다.

  1. API 문서를 쏟을 필요가 줄어듭니다. 대신 IDE의 Intellisense 기능을 통해 SDK 메서드를 탐색하십시오.
  2. 실제 애플리케이션과 관련이 없는 API URL 구조를 유지하면 실제 API에 대한 업데이트가 더 간단해집니다. 예, SDK는 구조를 알아야 하지만 한 두 곳으로 제한되며 애플리케이션 코드베이스 전체에 흩어져 있지 않습니다.
  3. 오타를 만들 가능성이 훨씬 적습니다. 사실, 당신의 IDE는 아마 당신을 위해 그것의 절반을 입력할 것입니다.
  4. API 요청과 관련된 문제를 추상화하는 기능. 예를 들어, 요청 상태를 확인하는 것은 다음과 같이 간단할 수 있습니다. if(post.isLoading){}
  5. 백엔드에서 하는 것처럼 프론트엔드에서 리소스와 상호 작용하는 방식에 대해 유사한 구문을 만듭니다(물론 보안으로 인한 제한 있음). 예를 들어, Laravel을 사용하여 Rest API에 전원을 공급하는 경우 Spatie의 우수한 laravel-query-builder 패키지를 클라이언트 측에서 Laravel 모델 API를 모방하여 API 요청을 하는 다수의 프론트 엔드 라이브러리와 결합할 수 있습니다.
  6. REST API가 변경될 때(예: 엔드포인트 이름 또는 인증 방법이 변경된 경우) API 통합을 보다 쉽게 ​​리팩토링합니다.

이 접근 방식에는 단점이 있습니다. 실제로 REST API 외에 SDK를 문서화하고 SDK를 코딩하는 데 시간을 소비해야 합니다. 내 경험상 효율성 이점은 그만한 가치가 있습니다.

팁 #6: 타사 라이브러리 랩핑

대규모 Vue.js 애플리케이션을 빌드하기 위한 마지막 팁은 타사 라이브러리에 대한 래퍼를 만드는 것입니다. 예를 들어, 코드베이스에서 직접 axios를 사용하는 대신 Http와 같이 더 일반적으로 이름이 지정된 클래스를 만들 수 있습니다. 이 클래스의 메서드는 내부에서 axios를 호출합니다. 다음과 같이 보일 수 있습니다.

// Http.js
import axios from 'axios'
export default class Http{
  async get(url){
    const response = await axios.get(url);
    return response.data;
  }
  // ...
}

네, 알아요... 언뜻 보기에 이것은 무의미해 보입니다. 그러나 이 연습에는 몇 가지 편리한 이점이 있습니다.

인터페이스를 변경하지 않고 종속성 변경

어떤 이유로 인해 axios에서 전환해야 한다고 가정해 보겠습니다. 결국 사용자가 제어할 수 없는 타사 코드입니다. 따라서 http 솔루션에 fetch를 사용하기로 결정했습니다. 좋아, 그렇게 나쁘지 않아. 사용 중인 axios의 인스턴스에 대해 전체 코드베이스를 검색할 필요가 없습니다. 대신 Http.js 파일 한 곳에서 전환합니다.

// Http.js
export default class Http{
  async get(url){
    const response = await fetch(url);
    return await response.json();
  }
  // ...
}

axios 대신 fetchHTTP 요청을 하는 것과 같습니다.

또한 단일 개발자가 이 전환을 수행할 수 있지만 팀의 다른 사람이 이러한 일이 발생했음을 알고 있는지 여부는 그다지 중요하지 않습니다. 모든 사람은 이전과 같은 방식으로 Http 클래스를 계속 사용할 것입니다.

기능 확장을 위한 보다 분명한 경로

타사 종속성을 래핑하는 또 다른 이점은 관련 기능을 처리하기 위해 클래스 API를 확장하는 보다 분명한 방법을 종종 노출한다는 것입니다. 예를 들어, 아약스 요청이 실패할 때 항상 사용자에게 경고하고 싶을 수 있습니다. 요청하는 모든 곳에서 수동으로 try catch를 사용하지 마십시오. 대신 Http 클래스가 대신 처리하도록 하십시오.

export default class Http{
  async get(url){
    try {
      const response = await fetch(url);
      return await response.json();
    } catch (err) {
      alert(err);
    }
  }
  // ...
}

또는 요청에 대한 캐싱을 추가하고 싶을 수도 있습니다.

// Http.js
import Cache from './Cache.js' // some random cache implementation of your choice
export default class Http{
  async get(url){
    const cached = Cache.get(url)
    if(cached) return cached
    const response = await fetch(url);
    return await response.json();
  }
  // ...
}

여기의 가능성은 말 그대로 무한합니다.

여기에서 axios 라이브러리는 이러한 확장 중 일부를 처리할 수 있는 인터셉터라는 개념을 제공하지만 그 존재가 덜 명확하다는 점에 주목할 가치가 있습니다. 래퍼 클래스를 사용하여 확장을 만드는 것은 JavaScript로 무엇이 가능한지 아는 문제일 뿐입니다. 타사 코드에 "연결"할 적절한 위치를 찾기 위해 문서를 살펴볼 필요가 없습니다. 뿐만 아니라 일부 타사 종속성은 axios만큼 포괄적이지 않고 필요한 후크를 노출할 수도 있습니다.

마지막으로, 이 목록의 다른 팁 중 일부는 대규모 및 소규모 응용 프로그램에 모두 적용할 수 있지만, 이 팁은 실제로 대규모 측면에만 적용됩니다. 소규모 응용 프로그램의 경우 이점이 제 생각에는 오버헤드 가치가 없습니다.

결론

Vue 3는 대규모 웹 애플리케이션 개발을 위한 훌륭한 솔루션을 제공하지만 주의하지 않으면 결국에는 자신에게 불리하게 작용할 수 있습니다. Vue 3를 최대한 활용하려면 다음 대규모 애플리케이션에 이 6가지 팁을 사용하는 것을 고려하십시오!

추가하고 싶은 팁이 있습니까? 아래에 의견을 남기고 우리 모두가 더 나은 Vue.js 개발자가 될 수 있도록 도와주세요!

댓글