119. Using Actions

npm create vue@latest


이번에는 애플리케이션의 코드베이스를 액션을 사용하여 리팩토링하는 방법을 다룹니다.
현재 RegisterForm 컴포넌트에 작성된 register 함수는 사용자 인증 서비스와 데이터베이스에 두 개의 요청을 보내고 있습니다.
이 함수는 컴포넌트를 조작하는 로직과 상태를 조작하는 로직으로 나뉩니다.
상태를 조작하는 코드는 액션 함수로 이동시키고, 나머지는 컴포넌트 내부에 남겨둡니다.

우리는 Pinia 라이브러리의 액션을 사용하여 이를 구현합니다.
액션은 비동기 요청을 수행하고 상태에 접근할 수 있는 스토어 내의 함수입니다.
register 액션 함수는 인증 및 스토어 서비스에 요청을 보내고, 사용자가 로그인된 상태를 업데이트합니다.
이 함수는 모든 컴포넌트에서 접근 가능하며, 에러 처리는 각 컴포넌트에서 담당하도록 합니다.

리팩토링 과정에서 RegisterForm 컴포넌트는 필요없는 코드를 제거하고, register 액션을 createUser 라는 이름으로 매핑합니다.
이 액션 함수는 사용자가 입력한 데이터를 인자로 받아 사용자를 인증 및 스토어 서비스에 등록합니다.
이렇게 리팩토링을 통해, 액션은 전역적으로 접근 가능하고 재사용 가능한 함수가 되며, 관리자 대시보드와 같은 다른 부분에서도 동일한 액션을 사용할 수 있게 됩니다.

src/components/RegisterForm.vue
<script setup lang="ts">
import { ref } from 'vue'
import useUser from '@/stores/user'

const userStore = useUser()
const { register: createUser } = userStore

const schema = ref({
  name: 'required|min:3|max:100|alpha_spaces',
  email: 'required|min:3|max:100|email',
  age: 'required|min_value:18|max_value:100',
  password: 'required|min:9|max:100|excluded:password', // password 단어를 입력하면 유효성 검사를 통과할 수 없다.
  confirm_password: 'passwords_mismatch:@password', // 비밀번호 입력할 때, 비밀번호 재확인차 입력하는 필드 // 위의 password 부분과 일치하는지 확인함
  country: 'required|country_excluded:Antarctica',
  tos: 'required'
})

const userData = ref({
  country: 'USA'
})

const reg_in_submission = ref(false)
const reg_show_alert = ref(false)
const reg_alert_variant = ref('bg-blue-500')
const reg_alert_msg = ref('Please wait! Your acount is being created.')

const register = async (values: {
  age: number
  confirm_password: string
  country: string
  email: string
  name: string
  password: string
  tos: number
}) => {
  // VeeForm 안에 VeeField의 모든 유효성 검사를 통과해야지 values 값이 들어온다.
  // ex.
  // {
  //  age: 31
  //  confirm_password: "asdf"
  //  country: "Germany"
  //  email: "sdf@sdf.com"
  //  name: "sfdf"
  //  password: "asdf"
  //  tos: "1"
  // }

  reg_show_alert.value = true
  reg_in_submission.value = true
  reg_alert_variant.value = 'bg-blue-500'
  reg_alert_msg.value = 'Please wait! Your account is being created.'

  try {
    await createUser(values)
  } catch (error) {
    reg_in_submission.value = false
    reg_alert_variant.value = 'bg-red-500'
    reg_alert_msg.value = 'An unexpected error occured. Please try again later.'
    return
  }

  reg_alert_variant.value = 'bg-green-500'
  reg_alert_msg.value = 'Success! Your account has been created.'
}
</script>

<template>
  <!-- Registration Form -->
  <div
    class="text-white text-center font-bold p-4 rounded mb-4"
    v-if="reg_show_alert"
    :class="reg_alert_variant"
  >
    {{ reg_alert_msg }}
  </div>
  <VeeForm :validation-schema="schema" @submit="register" :initial-values="userData">
    <!-- Name -->
    <div class="mb-3">
      <label class="inline-block mb-2">Name</label>
      <VeeField
        type="text"
        name="name"
        class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
        placeholder="Enter Name"
      />
      <ErrorMessage class="text-red-600" name="name" />
    </div>
    <!-- Email -->
    <div class="mb-3">
      <label class="inline-block mb-2">Email</label>
      <VeeField
        type="email"
        name="email"
        class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
        placeholder="Enter Email"
      />
      <ErrorMessage class="text-red-600" name="email" />
    </div>
    <!-- Age -->
    <div class="mb-3">
      <label class="inline-block mb-2">Age</label>
      <VeeField
        type="number"
        name="age"
        class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
      />
      <ErrorMessage class="text-red-600" name="age" />
    </div>
    <!-- Password -->
    <div class="mb-3">
      <label class="inline-block mb-2">Password</label>
      <VeeField name="password" :bails="false" #default="{ field, errors }">
        <input
          type="password"
          class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
          placeholder="password"
          v-bind="field"
        />
        <div class="text-red-600" v-for="error in errors" :key="error">
          {{ error }}
        </div>
      </VeeField>
    </div>
    <!-- Confirm Password -->
    <div class="mb-3">
      <label class="inline-block mb-2">Confirm Password</label>
      <VeeField
        type="password"
        name="confirm_password"
        class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
        placeholder="Confirm Password"
      />
      <ErrorMessage class="text-red-600" name="confirm_password" />
    </div>
    <!-- Country -->
    <div class="mb-3">
      <label class="inline-block mb-2">Country</label>
      <VeeField
        as="select"
        name="country"
        class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
      >
        <option value="USA">USA</option>
        <option value="Mexico">Mexico</option>
        <option value="Germany">Germany</option>
        <option value="Antarctica">Antarctica</option>
      </VeeField>
      <ErrorMessage class="text-red-600" name="country" />
    </div>
    <!-- TOS -->
    <div class="mb-3 pl-6">
      <VeeField
        type="checkbox"
        name="tos"
        value="1"
        class="w-4 h-4 float-left -ml-6 mt-1 rounded"
      />
      <label class="inline-block">Accept terms of service</label>
      <ErrorMessage class="text-red-600 block" name="tos" />
    </div>
    <button
      type="submit"
      class="block w-full bg-purple-600 text-white py-1.5 px-3 rounded transition hover:bg-purple-700"
      :disabled="reg_in_submission"
    >
      Submit
    </button>
  </VeeForm>
</template>

<style scoped></style>