visitors
23
일일 방문자 수

03월 12일 (수) 오후 17:30

Responsive image

조건에 따라 보이는 v-if, v-show

Created: 2023. 04. 24 06:17 오전
Updated: 2023. 05. 26 08:22 오전

글 목록이 길어지면 글 내용을 찾기 어려워집니다. 그래서 먼저 글 목록을 보여주고 선택한 글의 내용이 나오도록 하는 것이 좋습니다.

조건에 따라 다르게 보이기

현재 보여주어야 할 값을 main이라는 변수에 저장하고, 이 값이 list이면 글 목록이, content이면 글 내용이 보이도록 구현하겠습니다.

loading

우선 기본적으로 두 컴포넌트 다 display: none 을 통해 보이지 않도록 해놓겠습니다.

html
PostContent.vue
      <style>
/* ... */
.post-content {
  display: none;
}
</style>
    
html
      <style>
.post-list {
  display: none;
}
/* ... */
</style>
    

그리고 App 컴포넌트에서 main의 변수를 만들고 이 값에 따라 active라는 class를 가질 수 있도록 해보겠습니다. 그리고 이 class를 가지면 display: block 이 되도록 하겠습니다.

html
      <template>
	<!-- ... -->
  <PostList :class="main == 'list' ? 'active' : null"	... />
  <PostContent :class="main == 'content' ? 'active' : null" ... />
</template>

<script setup>
import { ref } from "vue";
// ...

const main = ref("list");
</script>

<style>
.active {
  display: block;
}
</style>
    

그리고 main 값은 카테고리를 선택하면 글 목록이, 글을 선택하면 글 내용이 표시됩니다. 이를 위해 적합한 두 개의 함수가 있습니다. changeCategorychangePost입니다.

javascript
      const changeCategory = function (category) {
  selectedCategory.value = category;
  main.value = "list";
};

const changePost = function (id) {
  selectedId.value = id;
  main.value = "content";
};
    

전체 코드를 보면 다음과 같습니다.

html
App.vue
      <template>
  <MenuBar @click-category="changeCategory" />
  <PostList
    :class="main == 'list' ? 'active' : null"
    @click-post="changePost"
    :selected-category="selectedCategory"
  />
  <PostContent :class="main == 'content' ? 'active' : null" :id="selectedId" />
</template>

<script setup>
import { ref } from "vue";
import MenuBar from "./components/MenuBar.vue";
import PostList from "./components/PostList.vue";
import PostContent from "./components/PostContent.vue";

const main = ref("list");

const selectedCategory = ref("HTML");
const changeCategory = function (category) {
  selectedCategory.value = category;
  main.value = "list";
};

const selectedId = ref(null);
const changePost = function (id) {
  selectedId.value = id;
  main.value = "content";
};
</script>

<style>
.active {
  display: block;
}
</style>
    

의도했던데로 잘 바뀌는 것을 볼 수 있습니다.

loading

다만 글이 바뀌면서 이전의 글이 잠시 남아있는데, getPageBlocks로 Notion 글을 가져와서 새로운 글이 저장되기 전까지 이전 글이 남아있기 때문입니다. getPageBlocks를 실행하기 전에 이전 글을 지우는 코드도 추가해두겠습니다.

javascript
PostContent.vue
      watch(props, () => {
	// 값 비우기
	blockMap.value = null;
  getPageBlocks(props.id).then((b) => {
    blockMap.value = b;
  });
});
    
loading

이제 잘 되네요! 이렇게 조건에 따라 class를 바꾸고 스타일을 적용해서 보이는 것을 조절할 수 있습니다. 이 방법도 좋지만 Vue에서는 더 편하게 사용할 수 있는 v-show 디렉티브가 있습니다.

v-show

조건에 따라 active class를 넣고 display: none 스타일을 적용했습니다. 이와 똑같이 적용할 수 있는 디렉티브가 바로 v-show입니다.

javascript
      <tag v-show="조건"></tag>
    

위에서 v-bind로 class를 적용하던 부분을 v-show로 바꿔보겠습니다.

html
      <template>
	<!-- ... -->
  <PostList v-show="main == 'list'" ... />
  <PostContent v-show="main == 'content'" ... />
</template>
    

그리고 기존에 적용했던 스타일들을 모두 제거해주겠습니다.

html
App.vue
      <style>
/* .active {
  display: block;
}*/
</style>
    
html
PostList.vue
      <style>
/*.post-list {
  display: none;
}*/

.post-list button {
  border: 0;
  background-color: transparent;
  color: rgb(101, 129, 139);
  cursor: pointer;
}

.post-list button:hover {
  color: blue;
}
</style>
    
html
PostContent.vue
      <style>
@import "vue-notion/src/styles.css";
/* .post-content {
  display: none;
}*/
</style>
    

이전 구현과 동일하게 적용되는 것을 볼 수 있습니다. display:none이 조건에 따라 자동으로 들어가고 있습니다.

loading

v-if

v-show 가 조건에 따라 스타일을 적용해서 보이고 숨겨주는 기능이라면 v-if는 조건에 따라 렌더링하는 기능입니다.

html
      <tag v-if="조건"></tag>
    

v-show 대신 v-if로 바꿔서 적용해보겠습니다.

html
      <PostList v-if="main == 'list'" ... />
<PostContent v-if="main == 'content'" ... />
    

HTML의 코드를 보시면 조건이 맞지않는 코드는 <!--v-if--> 로 바뀌면서 아예 코드가 나타나지 않습니다.

loading

그런데 글 내용이 전혀 나오지 않습니다. 왜냐하면 애초에 컴포넌트 자체가 없었기 때문에 watch가 작동하지 않기 때문이죠. v-if로 인해 조건이 맞으면 그 때서야 컴포넌트를 생성하고 setup함수를 실행한 다음 template을 마운트합니다.

처음엔 id가 null이지만, 글을 선택한 순간 id가 정해지면서 PostContent 가 생성됩니다. 즉, setup 함수를 실행할 때, id가 있으므로 watch를 쓸 필요가 없어집니다.

html
PostContent.vue
      <script setup>
// ...
const props = defineProps(["id"]);

blockMap.value = null;
getPageBlocks(props.id).then((b) => {
  blockMap.value = b;
});
</script>
    

이제 정상적으로 동작합니다.

loading

v-else-if, v-else

Javascript if문에서 else if, else 가 있는 것처럼 v-else-if, v-else 디렉티브도 있습니다.

html
      <tag v-if="조건1"></tag>
<tag v-else-if="조건2"></tag> <!-- 조건1이 안 맞는 경우 -->
<tag v-else-if="조건3"></tag> <!-- 조건1, 조건2가 안 맞는 경우 -->
<tag v-else></tag> <!-- 위 조건이 모두 맞지 않을 때 -->
    

이 때 주의할 점은 v-if를 사용한 태그 바로 아래에 v-else-if, v-else가 들어가야 합니다.

App.vue 코드를 수정하면 이렇게도 될 수 있습니다.

html
      <PostList v-if="main == 'list'" ... />
<PostContent v-else-if="main =='content'" ... />

<!-- 또는 -->
<PostList v-if="main == 'list'" ... />
<PostContent v-else ... />
    

v-show vs v-if

화면 상으로는 v-showv-if가 모두 조건에 따라 보이고 사라지게 하는 기능을 합니다. 하지만 내부 동작이 다르며, 어떤 것을 사용하는 것이 좋을까요? 다음과 같이 정리할 수 있습니다.

      조건이 자주 변경된다면 v-show, 처음에 보여줄 필요가 없다면 v-if
    

컴포넌트에 v-if를 적용하는 경우, setup 함수를 실행하고 템플릿을 마운트하는 긴 작업이 수행됩니다. 따라서 자주 보였다가 사라지는 경우 성능이 좋지 않을 수 있습니다. 그러나 처음에 보여주지 않아도 되는 요소라면, v-if를 사용하여 초기 로딩 속도를 높일 수 있습니다.