본문 바로가기

오늘의 커밋

230814 vue3에서 typescript 사용하기, vuetify 사용하기, 양방향 바인딩

728x90

오늘의 해야할 일

이거를

이렇게 만들어야 한다.

어떻게해야하나.

  1. 헤더에 버튼을 추가할 컬럼을 추가해야한다.
  2. slot 을 이용하여 추가 컬럼을 넣는다.
    1. 라익디스

슬롯이랑 컬럼 명을 매핑시켜주면 된다.

일단 버튼은 만들었다.

다음은 버튼에 이벤트를 넣어야 한다.

이벤트는 컴포넌트 기준으로 아래와 같이 동작한다.

Button(v-btn) → DataTable(v-data-table-server) → page

처음에는 Button 컴포넌트에 props으로 클릭 이벤트 함수를 전달하여 구현하고자 했는데,

생각해보니 @click 디렉티브는 이벤트 버블링의 규칙이 적용되므로 굳이 함수로 전달하는 과정이 필요하지 않았다.

그냥 상위 컴포넌트에서 @click 디렉티브를 사용하여 이벤트를 구현하면, 자연스럽게 클릭 이벤트를 핸들링 할 수 있다.

물론, dataTable에서 클릭 이벤트가 일어나더라도 페이지에서 클릭 이벤트가 일어나는것은 아니므로,

해당 클릭 이벤트를 감지하여 커스텀 이벤트로 emit해주어야 한다.

이런식으로 클릭할때 이벤트를 호출하고 다이어로그를 띄울 수 있다.

<Dialog
  :dialogTitle="actionFlag ? '새 항목 추가' : '항목 수정'"
  :showDialog="showDialog"
  @update:showDialog="showDialog = $event"
>
  <template #content>
    <item-action
      @close="showDialog = false"
      :actionFlag="handleItemAction"
    />
  </template>
</Dialog>

const handleItemAction = (item: any) => {
  console.log(item);
  actionFlag.value = false;
  showDialog.value = true;
};

단, item(row)에 따라 각 row의 고유한 id를 dialog에 전달해줘야만 수정할 수 있도록 작성할 수 있다.

item-action 팝업에 추가적인 props가 필요하다.

partner_email_id는 선택적으로 제공해야 하는 props인데 현재 사용중인 defineProps로는 코드가 너무 관리하기 귀찮았다.

import { defineProps } from 'vue';

const props = defineProps({
  actionFlag: {
    type: Boolean,
    default: false,
  },
  emailId: {
    type: String,
    default: "",
    required: false, // 선택적인 props 지정
  },
});

이건 vue에서만 쓰는 패턴이므로 기존에 사용하는 type 정의 패턴과 달라 나중에 읽고 사용하기 불편할 것 같았다.

찾아보니 아래와 같이 사용할 수 있었다.

const props = defineProps<{
  addMemberFlag: boolean;
  partner_email_id?: string; // 선택적인 프로퍼티
}>();

그런데 이렇게하면 초기값을 줄 수가 없다.

그래서 또 찾아보니

const props = withDefaults(
  defineProps<{
    addMemberFlag: boolean;
    partner_email_id?: number;
  }>(),
  {
    addMemberFlag: false,
    partner_email_id: 0,
  }
);

이렇게 사용할 수 있었다.

그런데 dialog가 오픈할때 해당 row의 모든 데이터를 셋팅해주어야 한다.

우습게도 텍스트필드 컴포넌트를 만들면서 v-model이 정상적으로 동작하는지에 대한 테스트를 안했었는데,

생각보다 복잡했다.

// 부모
<TextField
      v-model="email"
      placeholder="example@atommerce.com"
      label=""
      :rules="emailRules"
    />

// 자식
const props = defineProps({
  modelValue: {
    type: String,
    default: "",
  },
...

const emit = defineEmits(["update:modelValue"]);

컴포넌트가 위와 같을때,

부모 컴포넌트와 자식 컴포넌트간의 양방향 바인딩을 위해서는 props에 **modelValue 를 추가해줘야 한다.

(당연하지만 이름은 딱히 관계없음)

<template>
  <div>
    <v-text-field
			...
      v-model="maskedInput"
      @input="handleInput"
			...
    >
      <template v-slot:append-inner>
        <span v-if="helperText">{{ helperText }}</span>
      </template>
    </v-text-field>
  </div>
</template>

const maskedInput = computed({
  get() {
    return props.modelValue;
  },
  set(value) {
    let resultValue = value;
    emit("update:modelValue", resultValue);
  },
});

const handleInput = (value) => {
  emit("update:modelValue", value);
};

이런식으로 computed를 사용하는 경우에 작성할 수 있다.

추가로 props를 해당 페이지에서 사용하는 ref에 초기화한다.

const email = ref(props.item ? props.item["email"] : "");
const gender = ref(props.item ? String(props.item["gender"]) : "");

이제 데이터를 화면에서 볼 수 있다.

저장하기 위해 코드를 아래와 같이 분기처리한다.

const registerOrUpdate = () => {
  console.log("Action initiated");
  const genderKey = gender.value === "1" ? 1 : gender.value === "2" ? 2 : 0;

  if (props.item && props.item.id) {
    // 수정
    const { execute, response } = useUpdateItem({
      id: uniqueId, // 고유 식별자를 사용
      itemId: props.item.id,
      email: email.value,
      department: department.value,
      position: position.value,
      gender: genderKey,
      birthYear: birthYear.value,
    });

    execute().then(() => {
      serverResponseToasts({
        successText: "Item successfully updated.",
        response: response.value,
      });
      if (response.value.errors.length === 0) {
        emit("close");
      }
    });
  } else {
    // 등록
    const { execute, response } = useRegisterItem({
      id: uniqueId, // 고유 식별자를 사용
      email: email.value,
      department: department.value,
      position: position.value,
      gender: genderKey,
      birthYear: birthYear.value,
    });

    execute().then(() => {
      serverResponseToasts({
        successText: "Item successfully registered.",
        response: response.value,
      });
      if (response.value.errors.length === 0) {
        emit("close");
      }
    });
  }
};

저장끝

728x90