# SCSS에서 Flex 스타일을 반복적으로 사용하고 있을 때, 어떻게 추상화할 수 있을까?

SCSS를 사용하는 환경에서 Flex 박스로 레이아웃을 정의하며 계속 하위 트리로 내려간다고 가정을 할 때, 아래와 같은 구조가 발생하게 된다.

```javascript
// element.html
<div class="parent-container">
<div class="sub-parent-container>
...
</div>
</div>

// element.scss
.parent-container {
display: flex;
flex-direction: column;
row-gap: 5px;

.sub-parent-container {
display: flex;
flex-direction: column;
row-gap: 10px;
}
}
```

부모에서 Flex 스타일 속성을 정의하고, 또 자식 요소에서 레이아웃 구조 구성을 위해 Flex 스타일을 정의하게 되면 무한으로 Flex 스타일 속성을 매번 정의를 해줘야하는 상황이 발생한다.

이러한 반복 구조를 매번 직접 작성을 해주면 개발 피로도가 생각보다 엄청나다, 그래서 SCSS에서는 @mixin 키워드를 사용하여 이러한 문제들을 해결할 수 있다.

## 1. @mixin으로 스타일 반복 구조 줄여보기

flex 라는 mixin을 정의하고, 이 구조를 프로그래밍 언어의 함수처럼 사용을 할 수 있다. 먼저 .scss 파일에 아래와 같이 정의를 하면 된다.

```javascript
// scss define
@mixin flex($align: null, $justify: null, $direction: row, $gap: null, $wrap: null) {
display: flex;
flex-direction: $direction;
@if $align != null { align-items: $align; }
@if $justify != null { justify-content: $justify; }
@if $gap != null { gap: $gap; }
@if $wrap != null { flex-wrap: $wrap; }
}

// scss mxin usage
.parent-container {
@include flex(null, null, column, 5px, null);

.sub-parent-container {
@include flex(null, null, column, 10px, null);
}
}
```

그런데 여기서, Vue의 SFC scope SCSS를 사용하는 사람이라면, 해당 블록은 독립적으로 컴파일 되기 때문에 아무리 @mixin을 글로벌로 선언하더라도 모듈을 불러오지 못하기 때문에 사용을 하지 못한다. 그럴 경우에는 파일에서 @use 키워드를 통해 직접 임포트를 하거나, 아래와 같이 vite.config.js에 모든 SCSS에 자동 주입을 하는 방법이 있다.

```javascript
// plz add vite.config.js
css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@use "@/assets/style/mixin" as *;`
    }
  }
}
```

## 2. 컴포넌트로 추상화해보기

위의 @mixin 스타일을 그대로 사용해 컴포넌트로 만들기는 했지만, 위의 @mixin 스타일 적용을 해보았다면 아래 컴포넌트 구조화는 쉽게 이해가 될 수 있다.

컴포넌트를 래핑하여 하위 컴포넌트들의 엘리먼트 배치를 컴포넌트 선언단에서 제어하는 방식이다. 래핑된 컴포넌트들은 <slot/> 에 렌더링되면서 사전에 정의한 배치 구조로 나오게 된다.

```javascript
// Stack.vue
<template>
  <component
    :is="tag"
    class="stack"
    :style="stackStyle"
    v-bind="attrs"
  >
    <slot />
  </component>
</template>

<script setup lang="ts">
import { computed, useAttrs } from 'vue';

import type { CSSProperties } from 'vue';

type AlignItems = NonNullable<CSSProperties['alignItems']>;
type JustifyContent = NonNullable<CSSProperties['justifyContent']>;
type FlexWrap = NonNullable<CSSProperties['flexWrap']>;
type FlexDirection = NonNullable<CSSProperties['flexDirection']>;
type Gap = NonNullable<CSSProperties['gap']>;

interface Props {
    tag?: string;
    align?: AlignItems;
    justify?: JustifyContent;
    direction?: FlexDirection;
    gap?: Gap;
    wrap?: FlexWrap;
}

const props = withDefaults(defineProps<Props>(), {
  tag: 'div',
  align: 'stretch',
  justify: 'flex-start',
  direction: 'row',
  gap: '0',
  wrap: 'nowrap',
});

const attrs = useAttrs();

const stackStyle = computed(() => ({
  '--stack-align': props.align,
  '--stack-justify': props.justify,
  '--stack-direction': props.direction,
  '--stack-gap': `${props.gap}px`,
  '--stack-wrap': props.wrap,
}));
</script>

<style lang="scss" scoped>
.stack {
  @include flex(
    var(--stack-align),
    var(--stack-justify),
    var(--stack-direction),
    var(--stack-gap),
    var(--stack-wrap)
  );
}
</style>

// Stack Component Usage
<Stack :gap="2" :direction="'column'" class="parent-container">
<Stack :gap="3" :direction="column" class="parent-sub-container"></Stack>
</Stack>
```

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
