Vue.jsで値の加工をどこで実装するか

アプリケーションを作っていると日付の書式編集だったり、カンマ編集などを行いたい場面がよくあります。

Vue.jsを使っている場合、それらをどこで実装できるかを考えてみます。

Vueは3.5 時点のものです。

今回は例として以下のような日付の書式編集の関数を使います。(実装は簡易的なものなのです😅)

```ts:src/util/formatDate.ts
function formatDate(date: Date, format: string): string {
  let formattedDate = format
  formattedDate = formattedDate.replace(/yyyy/g, date.getFullYear().toString())
  formattedDate = formattedDate.replace(/MM/g, String(date.getMonth() + 1).padStart(2, '0'))
  formattedDate = formattedDate.replace(/dd/g, String(date.getDate()).padStart(2, '0'))
  formattedDate = formattedDate.replace(/HH/g, String(date.getHours()).padStart(2, '0'))
  formattedDate = formattedDate.replace(/mm/g, String(date.getMinutes()).padStart(2, '0'))
  formattedDate = formattedDate.replace(/ss/g, String(date.getSeconds()).padStart(2, '0'))
  formattedDate = formattedDate.replace(/SSS/g, String(date.getMilliseconds()).padStart(3, '0'))
  return formattedDate
}

export { formatDate }
```

## 1.各画面でそのまま関数を利用する

以下のように各画面でそのまま `formatDate` 関数を利用します。

```vue
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { formatDate } from '@/util/formatDate'

const now = ref(new Date())

// 書式変換の適用
const formattedDate = computed(() => formatDate(now.value, 'yyyy-MM-dd HH:mm:ss'))
</script>

<template>
  <div>
    <h1>Ex01</h1>
    <span>{{ formattedDate }}</span>
  </div>
</template>
```

シンプルな書き方でわかりやすいかなと思います。
必要に応じてコンポジション関数に切り出すのもいいかなと思います。

## 2.変換用のコンポーネントを作る

書式変換用のコンポーネントを作り、コンポーネントの中で `formatDate` 関数を利用します。

```vue:src/components/DateFormat.vue
<script setup lang="ts">
import { formatDate } from '@/util/formatDate'
import { computed } from 'vue'

const props = defineProps<{
  date: Date
  format: string
}>()

// コンポーネントの中で書式変換
const formatted = computed(() => formatDate(props.date, props.format))
</script>

<template>
  <slot :value="formatted">
    {{ formatted }}
  </slot>
</template>
```

フォーマット済の文字列はslot経由で渡します。

```vue
<script lang="ts" setup>
import { ref } from 'vue'
import DateFormat from '@/components/DateFormat.vue'

const now = ref(new Date())
</script>

<template>
  <div>
    <h1>Ex02</h1>
    <div>
      <!-- フォーマットした値をそのまま表示したい場合 -->
      <DateFormat :date="now" format="yyyy/MM/dd HH:mm:ss" />
    </div>
    <div>
      <!-- フォーマットした値を利用したい場合 -->
      <DateFormat :date="now" format="yyyy/MM/dd HH:mm:ss" v-slot="{ value }">
        <span style="color: red">{{ value }}</span>
      </DateFormat>
    </div>
  </div>
</template>
```

変換用に作成した `DateFormat` コンポーネントを使うことで、フォーマットした結果を表示することができます。

特に加工が必要ない場合はそのまま利用すればいいですし、何か加工したい場合は`v-slot` で変換後の値を受け取ることができます。

## 3.グローバルプロパティを追加しテンプレートから利用する。

Vue2 の`filter` の移行方法として提示されていたものです。

グローバルプロパティに変換用の関数を定義することでテンプレートで利用します。

```ts:src/myFormatPlugin.ts
import type { App, ObjectPlugin } from 'vue'
import { formatDate } from './util/formatDate'

declare module 'vue' {
  interface ComponentCustomProperties {
    $fd: (date: Date, format: string) => string
  }
}

const myFormatPlugin: ObjectPlugin = {
  install(app: App): void {
    // グローバルプロパティに追加
    app.config.globalProperties.$fd = formatDate
  },
}

export { myFormatPlugin }
```

プラグインを作成し、`app.config.globalProperties` に日付編集の関数 `$fd` を定義しています。また`ComponentCustomProperties`を利用し型定義にも追加しています。

```ts:src/main.ts
import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'
import { myFormatPlugin } from './myFormatPlugin'

const app = createApp(App)

app.use(createPinia())
app.use(router)
// プラグインを利用
app.use(myFormatPlugin)

app.mount('#app')
```

作成したプラグインを `app.use` で読み込みます。

```vue
<script lang="ts" setup>
import { ref } from 'vue'

const now = ref(new Date())
</script>

<template>
  <div>
    <h1>Ex03</h1>
    <span>{{ $fd(now, 'yyyy/MM/dd HH:mm:ss') }}</span>
  </div>
</template>
```

テンプレートで関数を利用します。この方法だと画面側の実装は少なくて済みますが、computedを使っていないので、変換のコストが高い場合はパフォーマンスが気になりますね

## まとめ

値の加工の実装について3パターン考えてみました。

それぞれにメリットデメリットがあると思いますが、知っておくだけで何かあったときに役立つかなと思います。