본문 바로가기

Vue/Vue3

[Vue3 & typescript] component의 composables/ts 로 emit과 props 보내기

이슈

pc와 mo 버전에서 공통적인 부분은 함께쓰기 위해 composables에 ts를 따로 빼서 관리하는데, 빼는 과정에서 emit과 props가 잘 옮겨지지 않았다. 
vue파일경로: src/views/fb-depth-select.vue
ts파일경로: src/composables/fb-depth-select.ts


ts 파일 따로 빼기 전 vue 소스
src/views/fb-depth-select.vue

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

  interface depthItem {
    name: string;
    value: string | number;
  }

  const emit = defineEmits(['update:selectCategories']);

  const props = defineProps({
    modelValue: {
      type: Array as PropType<depthItem[]>,
      default: () => [],
      required: false, 
    },
  })
  
  //카테고리 세팅
  const categoryList = ref<DepthItem[][]>([]);
  categoryList.value = [...props.modelValue];
  
  //선택한 값 (select컴포넌트 설정에 사용)
  const selectedList = ref<DepthItem[]>([]);
  selectedList.value = categoryList.value.map(v => v[0]);
  
  // ...
</script>
부모 컴포넌트로 보낼 emit을 만들고 (emit 사용하는 부분은 생략) 부모에서 받을 props를 따로 선언하였다.

 



파일분리 시도 (실패케이스)

1. vue 소스 > src/views/fb-depth-select.vue

<script setup lang="ts">
  import fbDepthSelectComposable from '@/composables/modules/fb-depth-select'
  
  const {
    categoryList,
    selectedList,
    //...
  } = fbDepthSelectComposable();

</script>
composables/fb-depth-select.ts 로 소스를 옮기고 
fb-depth-select.ts에서 fbDepthSelectComposable 라는 이름으로 내보낸 것을 import하였다.

return 하는 값들만 사용하기 위해 비구조화할당으로 선언하였다.

 

2. ts 소스 > src/composables/fb-depth-select.ts

import { PropType, ref, watch } from 'vue';
import { CustomEmit } from '@/interfaces/type';

interface DepthItem {
  name: string;
  value: string | number;
}

//여기가 이슈1
const emit = defineEmits(['update:selectCategories']);

//여기가 이슈2
const props = defineProps({
    modelValue: {
      type: Array as PropType<depthItem[]>,
      default: () => [],
      required: false, 
    },
})

export default function fbDepthSelectComposable(emit: CustomEmit<Emits>, props) {
  //카테고리 세팅
  const categoryList = ref<DepthItem[][]>([]);
  categoryList.value = [...props.modelValue];

  //선택한 값 (select컴포넌트 설정에 사용)
  const selectedList = ref<DepthItem[]>([]);
  selectedList.value = categoryList.value.map(v => v[0]);
  
  return {
    categoryList,
    selectedList,
  }
}
하단의 export default 로 fbDepthSelectComposable 라는 함수명으로 내보내고 그 위에 import, interface, emit, props 등을 위로 뺐다. 사실 emit과 props는 함수 안에 넣었었는데 에러가 나서 위로 뺐다. 그래도 defineProps와 defineEmits를 못찾는다고 오류가 났다.

 

 



해결방법

1. vue 소스 > src/views/fb-depth-select.vue

<script setup lang="ts">
  import fbDepthSelectComposable, { emits as depthSelectEmits, props as depthSelectProps } from '@/composables/modules/fb-depth-select'
  
  const emit = defineEmits(depthSelectEmits);
  const props = defineProps(depthSelectProps);

  const {
    categoryList,
    selectedList,
    disabled,
    selectCategory
  } = fbDepthSelectComposable(emit, props);
</script>
포인트 1
defineEmits와 defineProps는 여기 안에서만 사용이 가능하다.  (composables/ts 파일 안에서는 찾을 수 없음.)

포인트 2
composables 안에 있는 emits를 depthSelectEmits로, props를 depthSelectProps로 사용하기로 한다.
(emits as depthSelectEmits, props as depthSelectProps)

포인트 3
원래 소스처럼

const emit = defineEmits(['update:select-category'])  대신에
const emit = defineEmits(depthSelectEmits) 로 바꿔준다. 

* composables파일에서 보내주는 depthSelectEmits 에는 ['update:select-category'] 이게 들어있음.
  (아래 composables 파일 참고)

포인트 4

props도 emit과 동일한 원리로 해준다.

포인트 5
defineEmits와 defineProps로 진짜 우리가 생각하는 emit과 props 가 되었으므로 이걸 composables 파일 함수인 fbDepthSelectComposable에 넘겨준다.
fbDepthSelectComposable(emit, props)

 

 

2. composables/ ts 소스> src/composables/fb-depth-select.ts

import { PropType, ref, watch } from 'vue';
import { CustomEmit } from '@/interfaces/type';

interface DepthItem {
  name: string;
  value: string | number;
}

type Emits = 'update:selectCategory';
const emits: Emits[] = ['update:selectCategory'];

const props = {
  modelValue: {
    type: Array as PropType<DepthItem[][]>,
    default: () => [],
    required: false, 
  },
}

export default function fbDepthSelectComposable(emit: CustomEmit<Emits>, props) {
  //카테고리 세팅
  const categoryList = ref<DepthItem[][]>([]);
  categoryList.value = [...props.modelValue];

  //선택한 값 (select컴포넌트 설정에 사용)
  const selectedList = ref<DepthItem[]>([]);
  selectedList.value = categoryList.value.map(v => v[0]);
  
  return {
    categoryList,
    selectedList,
  }
}

export {
  emits,
  props,
}
포인트 1
vue파일 포인트 2번에서 as로 선언한 emits과 props는 맨 하단의 export로 내보낸 이 emits, props이다.
** emits과 props는 function 위에 const로 선언되어있다.


포인트 2
vue파일 포인트5번에서 보내준 emit과 props를 여기서 받는다.

export default function fbDepthSelectComposable(emit, props) {}

포인트 3
emit과 props는 vue파일에서 쓰던 것과 동일하므로 (define된 애들임) 그대로 사용하면 된다.

 

 

정리

composables 안에서는 defineProps나 defineEmits를 쓰지 못하므로 vue파일에서 선언해서 composables 함수로 넘겨준다.
반응형