Vue

ref를 이용해서 resize 하기

milliwonkim 2022. 6. 18. 23:50
반응형
SMALL

뷰포트를 ref로 인식한 값을 이용해서 뷰포트 넓이에 따라 Vuetify의 Hamburger Icon와 네비게이션메뉴를 조건부 렌더링하도록 하는 로직을 구현해보았습니다

<template>
  <header class="header" ref="refs">
    <div class="logo-title-box">
      <p class="logo-title">ALLIGHT</p>
    </div>
    <nav class="nav-list">
      <v-app-bar-nav-icon v-if="show"></v-app-bar-nav-icon>
      <a-button v-else :key="headerButton" v-for="headerButton in headerList">
        {{ headerButton }}
      </a-button>
    </nav>
  </header>
</template><script>
import { onMounted, onUnmounted, ref } from '@vue/composition-api';
import AButton from './AButton.vue';export default {
  name: 'a-header',
  components: {
    'a-button': AButton,
  },
  setup() {
    const BUTTONS = ['상담받기', '피드', '나의 일기장', '로그인', '회원가입'];
    const headerList = ref(BUTTONS);    const refs = ref(null);    const show = ref(false);    const handleWidth = () => {
      if (refs.value.clientWidth < 640) {
        show.value = true;
      } else {
        show.value = false;
      }
    };    onMounted(() => {
      handleWidth();
      window.addEventListener('resize', handleWidth);
    });    onUnmounted(() => {
      window.removeEventListener('resize', handleWidth);
    });    return {
      refs,
      show,
      headerList,
    };
  },
};
</script>
<style lang="scss">
.header {
  display: flex;
  width: 100%;
  justify-content: space-between;
  margin: 24px 0;@media (max-width: 625px) {
    margin: 24px 0;
  }
}.nav-list {
  display: flex;
  justify-content: space-evenly;
  gap: 16px;
  align-items: center;
}.logo-title-box {
  display: flex;
  align-items: center;
}.logo-title {
  display: inline-block;
  font-size: 24px;
  font-weight: 600;
}
</style>

header width가 640px이 넘은 경우

header의 width가 640px 이하인 경우

resize를 하는 로직을 react의 hook처럼 외부화 시킬 수 있습니다.

// @/hooks/useResize.ts

import { onMounted, onUnmounted, Ref } from '@vue/composition-api';

interface IRefs {
  clientWidth: number;
}

function useResize(refs: Ref<IRefs>, result: Ref<boolean>): void {
  const handleWidth = () => {
    const refsObject = result;
    if (refs.value.clientWidth < 640) {
      refsObject.value = true;
    } else {
      refsObject.value = false;
    }
  };
  
  onMounted(() => {
    handleWidth();
    window.addEventListener('resize', handleWidth);
  });
  
  onUnmounted(() => {
    window.removeEventListener('resize', handleWidth);
  });
}

export default useResize;

// @/components/AHeader.vue
<template>
  <header class="header" ref="refs">
    <div class="logo-title-box">
      <p class="logo-title">ALLIGHT</p>
    </div>
    <nav class="nav-list">
      <v-app-bar-nav-icon v-if="show"></v-app-bar-nav-icon>
      <a-button
        v-else
        :key="headerButton.id"
        v-for="headerButton in headerList"
@clickHandler="handleRoute(headerButton.link)"
      >
        {{ headerButton.name }}
      </a-button>
    </nav>
  </header>
</template><script>
import { ref } from '@vue/composition-api';
import router from '@/router';
import { HEADER_BUTTONS } from '@/constants/constants';
import AButton from './AButton.vue';
import useResize from '../hooks/useResize';export default {
  name: 'a-header',
  components: {
    'a-button': AButton,
  },
  setup() {
    const headerList = ref(HEADER_BUTTONS);    const refs = ref(null);
    const show = ref(false);    useResize(refs, show);

    const handleRoute = (link) => {
      if (window.location.pathname !== link) {
        router.push(link);
      }
    };    return {
      refs,
      show,
      headerList,
      handleRoute,
    };
  },
};
</script>
반응형
LIST