728x90
반응형

Vue.js는 컴포넌트 기반 아키텍처를 제공하여 확장 가능하고 유지보수가 용이한 애플리케이션을 구축하는 데 강력한 프레임워크입니다. Vue의 핵심 기능 중 하나인 Scoped CSS는 컴포넌트 내에서 정의된 스타일이 해당 컴포넌트에만 적용되도록 보장하여 애플리케이션 전반에 걸친 의도치 않은 스타일 영향력을 방지합니다. 그러나 v-html과 같은 동적 콘텐츠 렌더링 방식을 사용할 때 Scoped CSS를 효과적으로 적용하는 데 어려움을 겪을 수 있습니다.

이 블로그 포스트에서는 Scoped 스타일과 v-html과 관련된 일반적인 문제를 살펴보고, 그 근본 원인을 이해하며, Vue의 최신 기능을 활용하여 이를 해결하는 방법에 대해 논의하겠습니다.

목차

  1. Vue의 Scoped CSS 이해하기
  2. v-html의 도전 과제
  3. 문제 식별하기
  4. 해결 방법: :deep() 선택자 사용하기
  5. 최고의 실천 방법
  6. 결론

Vue의 Scoped CSS 이해하기

Scoped CSS는 컴포넌트에 특정한 스타일을 작성할 수 있게 해줍니다. 이는 컴포넌트 내에서 정의된 스타일이 다른 컴포넌트에 영향을 미치지 않도록 보장합니다. Vue는 각 요소에 고유한 데이터 속성을 자동으로 추가하여 스타일의 적용 범위를 제한합니다.

Scoped CSS의 예제

<template>
  <div class="example">
    <p>이것은 scoped된 단락입니다.</p>
  </div>
</template>

<script setup lang="ts">
</script>

<style scoped>
.example {
  background-color: #f0f0f0;
}

p {
  color: blue;
}
</style>

위 예제에서:

  • .example 클래스와 p 태그에 대한 스타일은 해당 컴포넌트에만 적용됩니다.
  • 다른 컴포넌트에서 동일한 클래스나 태그가 있어도 영향을 받지 않습니다.

v-html의 도전 과제

Vue의 v-html 디렉티브는 동적으로 원시 HTML 콘텐츠를 렌더링할 수 있게 해줍니다. 이는 API에서 가져온 콘텐츠나 사용자 생성 콘텐츠를 표시할 때 유용합니다. 그러나 Scoped CSS를 사용하는 컴포넌트 내에서 v-html을 사용할 경우, 동적으로 렌더링된 콘텐츠에 스타일이 예상대로 적용되지 않는 문제가 발생할 수 있습니다.

왜 이런 일이 발생할까요?

Scoped CSS는 컴포넌트의 요소에 고유한 속성을 추가하여 스타일을 적용합니다. 그러나 v-html을 통해 렌더링된 콘텐츠는 이러한 고유 속성을 받지 않기 때문에 Scoped 스타일이 적용되지 않습니다.

예제 시나리오

다음은 v-html을 사용하여 마크다운 콘텐츠를 렌더링하는 컴포넌트 예제입니다:

<template>
  <div class="content" v-html="renderedContent"></div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import MarkdownIt from 'markdown-it'

const props = defineProps(['content'])

const markdownParser = new MarkdownIt({
  html: true,
  breaks: true,
  linkify: true,
})

const renderedContent = computed(() => {
  return markdownParser.render(props.content || '')
})
</script>

<style scoped>
.content {
  font-family: Arial, sans-serif;
  color: #333;
}

ul, ol {
  padding-left: 20px;
}
</style>

이 예제에서:

  • 컴포넌트는 v-html을 사용하여 마크다운 콘텐츠를 렌더링합니다.
  • ulol 요소에 패딩을 적용하려 하지만, Scoped CSS로 인해 스타일이 적용되지 않습니다.

문제 식별하기

Scoped 스타일을 v-html과 함께 사용하면 동적으로 삽입된 HTML에 고유한 속성이 적용되지 않아 스타일이 의도대로 적용되지 않는 문제가 발생합니다. 특히 ul, ol, li 요소에서 들여쓰기가 제대로 적용되지 않는 현상이 나타날 수 있습니다.

일반적인 문제점

  • 들여쓰기 부족: 리스트(ul, ol)에 원하는 들여쓰기가 적용되지 않습니다.
  • 스타일 미적용: v-html을 통해 삽입된 다른 HTML 요소에도 스타일이 적용되지 않습니다.

해결 방법: :deep() 선택자 사용하기

Vue는 Scoped CSS의 경계를 넘어 자식 컴포넌트나 특정 요소에 스타일을 적용할 수 있는 :deep() 선택자를 제공합니다. 이는 v-html로 렌더링된 콘텐츠에 스타일을 적용하는 데 특히 유용합니다.

:deep()인가?

  • Scoped 스타일 유지: Scoped 스타일의 이점을 유지하면서 특정 요소에 스타일을 적용할 수 있습니다.
  • 유연성 제공: Scoped CSS의 제한을 우회하여 필요한 부분에만 스타일을 적용할 수 있습니다.

deprecated된 ::v-deep 대체

과거에는 ::v-deep 조합자를 사용하여 유사한 결과를 얻었지만, 이제는 ::v-deep이 deprecated 되어 :deep() 구문을 사용하는 것이 권장됩니다.

구문

:deep(selector) {
  /* 스타일 정의 */
}

최고의 실천 방법

  1. 가능한 한 Scoped CSS 사용 유지:

    • 컴포넌트 스타일의 격리를 유지하여 의도치 않은 스타일 영향력을 방지합니다.
  2. 동적 콘텐츠에는 :deep() 활용:

    • v-html이나 자식 컴포넌트에 스타일을 적용할 때 :deep()을 선택적으로 사용합니다.
  3. 전역 스타일의 과도한 사용 피하기:

    • scoped 속성을 제거하고 전역 스타일을 사용하는 것은 스타일 충돌을 유발할 수 있으므로 신중하게 사용합니다.
  4. 스타일 정리 및 주석 추가:

    • :deep()을 사용하는 부분에 명확한 주석을 추가하여 코드의 가독성을 높입니다.
  5. 컴포넌트 간 스타일 충돌 테스트:

    • :deep()으로 적용한 스타일이 다른 컴포넌트에 영향을 미치지 않는지 확인합니다.

결론

Vue.js에서 동적으로 렌더링된 콘텐츠를 스타일링하는 것은 Scoped CSS와 v-html 디렉티브를 사용할 때 도전 과제가 될 수 있습니다. 그러나 :deep() 선택자를 활용하면 Scoped 스타일의 이점을 유지하면서도 특정 요소에 필요한 스타일을 적용할 수 있습니다. 이를 통해 v-html로 삽입된 ul, ol 요소의 들여쓰기가 정상적으로 적용되며, 스타일 충돌 없이 깔끔한 UI를 유지할 수 있습니다.

스타일을 조직적으로 관리하고 :deep()을 적절히 사용함으로써 Vue.js 애플리케이션의 스타일링 문제를 효과적으로 해결할 수 있습니다. 최신 Vue의 모범 사례를 따르며 유지보수가 용이한 코드베이스를 구축하시기 바랍니다.

반응형
728x90
반응형

Vue 3에서 새롭게 추가된 <script setup>Composition API를 사용하는 컴포넌트 정의 방식을 더 간결하게 만들어 주는 구문입니다. 기존의 setup() 함수 방식과 비교했을 때, 더 직관적이고 효율적인 방식으로 컴포넌트의 로직을 작성할 수 있습니다.

<script setup>의 주요 특징 및 장점

1. 간결한 구문

<script setup>을 사용하면 불필요한 보일러플레이트 코드를 줄일 수 있습니다. export defaultsetup() 함수의 중첩 없이 바로 상태나 메서드를 선언하고 사용할 수 있습니다.

기존 방식 (setup 함수 사용):

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const count = ref(0)
    const increment = () => count.value++
    return { count, increment }
  }
})
</script>

<script setup> 방식:

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
</script>

<script setup> 방식은 훨씬 간단하게 상태와 메서드를 정의할 수 있으며, 불필요한 중첩이 사라집니다.

2. 더 나은 성능

<script setup>컴파일 단계에서 처리되기 때문에 런타임 성능에 이점을 제공합니다. 또한, Vue 컴파일러가 최적화 작업을 더 효율적으로 수행할 수 있습니다.

3. 명확한 코드 구조

이 구문을 사용하면 코드의 가독성이 크게 향상됩니다. 상태나 메서드를 정의할 때 불필요한 구문이 없어지고, 명확하게 로직을 작성할 수 있습니다.

4. TypeScript와의 통합

<script setup lang="ts">는 TypeScript와의 완벽한 통합을 지원합니다. 타입 정의, 자동 완성, 타입 검사가 자연스럽게 이루어지며, 타입 안전성을 보장합니다.

<script setup lang="ts">
import { ref } from 'vue'

const count = ref<number>(0)
const increment = (): void => {
  count.value++
}
</script>

5. 변수 자동 등록

props, emit 같은 요소들이 자동으로 스코프에 등록되므로, 추가적으로 선언할 필요가 없습니다. 이로 인해 코드가 더욱 간결해집니다.

<script setup>을 사용하는가?

<script setup>은 다음과 같은 이유로 Vue 3 프로젝트에서 선호됩니다:

  • 코드의 가독성을 높이고, 관리하기 쉽도록 단순화할 수 있습니다.
  • 불필요한 setup() 함수 정의를 피하고, 컴포넌트의 핵심 로직에 집중할 수 있습니다.
  • 더 나은 성능을 제공하며, 컴파일러가 코드를 더 효율적으로 처리할 수 있습니다.
  • Vue 3의 Composition API를 사용할 때 가장 권장되는 방법으로, 유지보수성이 뛰어납니다.

결론

Vue 3의 <script setup>은 컴포넌트 로직을 더 간결하고 직관적으로 작성할 수 있는 강력한 도구입니다. 더 나은 성능과 가독성을 제공하면서도 TypeScript와도 자연스럽게 통합되기 때문에, Vue 3 프로젝트에서 적극적으로 사용하는 것이 좋습니다.

참고 자료

  • Vue 공식 문서 - Composition API
  • Vue 공식 문서 - <script setup>
반응형
728x90
반응형

Vue 컴포넌트에서 methodwatch처럼 자주 사용되는 옵션 속성들이 있습니다. 이러한 속성들은 컴포넌트의 데이터 관리, DOM 조작, 상태 변화 감시, 라이프사이클 관리 등에 중요한 역할을 합니다. 이번 글에서는 Vue 컴포넌트에서 자주 사용하는 주요 속성들을 정리해보겠습니다.

1. data

  • 역할: 컴포넌트의 로컬 상태를 정의하는 곳입니다. data에서 정의한 속성들은 반응형으로 관리되며, 컴포넌트 내에서 사용됩니다.
  • 주로 사용하는 경우: 컴포넌트의 기본 상태값을 정의할 때 사용합니다.
data() {
  return {
    count: 0,  // 초기 상태값
    name: 'Vue'
  };
}

2. methods

  • 역할: 컴포넌트에서 호출할 수 있는 메서드를 정의하는 곳입니다. 이벤트 핸들러 또는 비즈니스 로직을 수행할 때 사용됩니다.
  • 주로 사용하는 경우: 이벤트 처리, 비즈니스 로직 실행 등에서 사용됩니다.
methods: {
  increment() {
    this.count++;  // 상태 변경
  },
  greet() {
    console.log('Hello ' + this.name);
  }
}

3. computed

  • 역할: 계산된 속성을 정의하는 곳으로, 상태에 의존하는 값을 계산하여 캐싱합니다. 종속된 상태가 변경될 때만 재계산되므로 성능 향상에 도움이 됩니다.
  • 주로 사용하는 경우: 상태를 가공하거나 계산된 값을 사용해야 할 때.
computed: {
  reversedName() {
    return this.name.split('').reverse().join('');
  }
}

4. watch

  • 역할: 특정 데이터나 상태를 감시하며, 값이 변할 때 실행되는 로직을 정의합니다. 비동기 작업을 트리거하거나 특정 데이터 변경에 반응할 때 유용합니다.
  • 주로 사용하는 경우: 데이터 변경 시 추가적인 작업이 필요할 때.
watch: {
  name(newVal, oldVal) {
    console.log(`name changed from ${oldVal} to ${newVal}`);
  }
}

5. props

  • 역할: 부모 컴포넌트로부터 전달받는 데이터를 정의하는 곳입니다. props로 전달된 값은 컴포넌트 내부에서 읽기 전용입니다.
  • 주로 사용하는 경우: 부모에서 자식 컴포넌트로 데이터를 전달할 때.
props: {
  title: {
    type: String,
    required: true
  }
}

6. emits

  • 역할: 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달할 때 사용됩니다. Vue 3에서 emits 옵션을 통해 어떤 이벤트가 발생할 수 있는지 정의할 수 있습니다.
  • 주로 사용하는 경우: 자식에서 부모로 상호작용을 전달할 때.
emits: ['custom-event'],

methods: {
  triggerEvent() {
    this.$emit('custom-event', { message: 'Hello Parent' });
  }
}

7. mounted

  • 역할: 컴포넌트가 DOM에 마운트된 후 호출되는 라이프사이클 훅입니다. 이 시점에서 컴포넌트가 완전히 렌더링되었으므로 DOM 조작을 하거나 API 호출 등을 할 수 있습니다.
  • 주로 사용하는 경우: 초기 데이터를 로드하거나, DOM 관련 로직을 실행할 때.
mounted() {
  console.log('Component is mounted');
  this.fetchInitialData();
}

8. beforeUnmount (Vue 3) / beforeDestroy (Vue 2)

  • 역할: 컴포넌트가 제거되기 직전에 호출되는 라이프사이클 훅입니다. 컴포넌트가 사라지기 전에 정리 작업을 할 수 있습니다.
  • 주로 사용하는 경우: 이벤트 리스너를 제거하거나 타이머를 해제할 때.
beforeUnmount() {
  clearInterval(this.timer);
}

9. created

  • 역할: 컴포넌트가 생성된 직후 호출되는 라이프사이클 훅입니다. dataprops에 접근할 수 있지만, 아직 DOM에는 접근할 수 없습니다.
  • 주로 사용하는 경우: 데이터 초기화나 비동기 호출 작업을 시작할 때.
created() {
  console.log('Component created');
}

10. components

  • 역할: 컴포넌트 내부에서 사용하는 자식 컴포넌트를 정의하는 곳입니다. 해당 컴포넌트에서 사용할 자식 컴포넌트를 등록할 수 있습니다.
  • 주로 사용하는 경우: 다른 컴포넌트를 로컬로 등록하고 사용할 때.
import ChildComponent from './ChildComponent.vue';

components: {
  ChildComponent,
}

11. template

  • 역할: 컴포넌트의 HTML 구조를 정의하는 곳입니다. 데이터 바인딩이나 이벤트 처리를 포함한 DOM 구조를 정의할 수 있습니다.
  • 주로 사용하는 경우: 컴포넌트의 UI를 정의할 때.
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
  </div>
</template>

Vue 라이프사이클 훅 정리

Vue 컴포넌트는 생성 -> 마운트 -> 업데이트 -> 소멸의 생명 주기를 따릅니다. 이 생명주기에서 적절한 라이프사이클 훅을 사용하여 컴포넌트의 상태를 제어할 수 있습니다. 예를 들어:

  • created: 컴포넌트가 생성될 때 호출.
  • mounted: 컴포넌트가 DOM에 추가된 후 호출.
  • updated: 데이터가 변경되어 DOM이 다시 렌더링된 후 호출.
  • beforeUnmount: 컴포넌트가 제거되기 직전에 호출.

요약

  • Vue 컴포넌트에서 자주 사용하는 속성들은 주로 데이터 관리, 상태 감시, 메서드 정의, DOM 상태 변경등과 관련됩니다.
  • data, methods, computed, watch는 컴포넌트 내에서 자주 사용되는 속성들이며, 라이프사이클 훅(created, mounted, beforeUnmount 등)은 컴포넌트의 상태 변화를 관리하는 데 사용됩니다.
반응형
728x90
반응형

Vuex는 Vue.js 애플리케이션에서 상태 관리를 체계적으로 할 수 있도록 도와주는 라이브러리입니다. Vuex에서 상태를 관리할 때, getter, mutation, action은 각각의 역할을 수행하며, 이들을 올바르게 이해하고 사용하는 것이 중요합니다.

이 글에서는 Vuex에서 getter, mutation, action의 역할과 그 차이점에 대해 알아보겠습니다.

1. Getter

역할

Getter는 Vuex 상태(state)를 읽을 때 사용됩니다. Vuex의 상태를 가공하거나 필터링하여 컴포넌트에서 쉽게 가져올 수 있게 하며, Vue의 computed 속성처럼 동작합니다.

주로 사용되는 경우

  • 상태 값을 그대로 가져와 사용할 때.
  • 상태 값을 가공하거나 필터링해서 반환해야 하는 경우.
  • 상태 값에 의존한 계산된 데이터를 필요로 하는 경우.

예시

const state = {
  userInfo: { name: 'John', age: 30 }
};

const getters = {
  getUserName: (state) => state.userInfo.name,  // 상태의 특정 값을 반환
  getUserAge: (state) => state.userInfo.age,    // 상태의 특정 값을 반환
};

컴포넌트에서 사용

computed: {
  userName() {
    return this.$store.getters['getUserName'];  // userInfo.name을 가져옴
  },
  userAge() {
    return this.$store.getters['getUserAge'];   // userInfo.age를 가져옴
  }
}

2. Mutation

역할

Mutation은 Vuex 상태(state)를 직접 변경하는 메서드입니다. Vuex의 상태는 직접 변경할 수 없기 때문에 mutation을 통해서만 상태를 변경할 수 있습니다.

제약 사항

동기적으로만 실행되어야 하며, 상태를 변경하는 로직만 포함합니다.

주로 사용되는 경우

  • 상태를 즉시 변경해야 하는 경우(예: API 호출 후 데이터를 상태에 저장).

예시

const state = {
  userInfo: { name: '', age: 0 }
};

const mutations = {
  SET_USER_INFO(state, payload) {
    state.userInfo = payload;  // 사용자 정보를 상태에 저장
  }
};

컴포넌트에서 사용

methods: {
  updateUserInfo() {
    this.$store.commit('SET_USER_INFO', { name: 'John', age: 30 });
  }
}

3. Action

역할

Action비동기 작업을 처리하거나, 복잡한 로직을 처리하는 곳입니다. 액션은 비동기 작업 후 mutation을 커밋해서 상태를 변경할 수 있습니다.

주로 사용되는 경우

  • API 호출, 비동기 작업, 여러 개의 mutation을 연속적으로 실행할 때 사용됩니다.

예시

const actions = {
  async fetchUserInfo({ commit }) {
    const data = await fetchUserApi();  // 비동기 API 호출
    commit('SET_USER_INFO', data);      // mutation을 통해 상태 변경
  }
};

컴포넌트에서 사용

methods: {
  async loadUserInfo() {
    await this.$store.dispatch('fetchUserInfo');  // 액션 호출
  }
}

각 역할 간의 관계

  • Getter는 상태를 가져오기 위한 메서드입니다. 상태를 가공하거나 특정 조건을 만족하는 데이터를 추출할 때 사용합니다.
  • Mutation은 상태를 변경하는 유일한 방법입니다. 동기적으로 실행되어야 하며, 컴포넌트나 액션에서 호출됩니다.
  • Action비동기 작업 또는 복잡한 로직을 처리하는 메서드로, mutation을 커밋하여 상태를 변경합니다.

간단한 흐름 예시

  1. Action: 비동기 작업을 수행하거나 API 호출을 통해 데이터를 가져옵니다.
  2. Mutation: 액션에서 가져온 데이터를 사용해 상태(state)를 변경합니다.
  3. Getter: 컴포넌트에서 상태를 가공하거나 그대로 가져옵니다.

요약

  • Getter: 상태를 가공하여 반환하는 역할.
  • Mutation: 상태를 동기적으로 변경하는 역할.
  • Action: 비동기 작업을 처리하고, mutation을 통해 상태를 변경하는 역할.

이 구조를 통해 Vuex는 상태를 중앙 집중식으로 관리하고, 상태 변경과 관련된 로직을 명확하게 분리할 수 있습니다.

반응형