撰寫 vue3 共用函數:超強版 useArrayTools

在 Vue 3 中撰寫「共用函數」有幾種方式,最常見的是透過 Composable(可組合函式) 的方式來實作。這是 Vue 3 中 Composition API 推出後的一個最佳實踐,用來撰寫可重用的邏輯。

基本範例:建立共用函式(Composable)

使用 ts(type script) 假設你有一個功能是格式化日期,你可以這樣寫:

建立一個 composable 檔案

src/
└── composables/
    └── useDateFormat.ts  # or .js

撰寫共用邏輯

// useDateFormat.ts
export function useDateFormat() {
  const formatDate = (date: string | Date): string => {
    const d = new Date(date)
    return d.toLocaleDateString('zh-TW', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    })
  }

  return { formatDate }
}

在元件中使用

<script setup lang="ts">
import { useDateFormat } from '@/composables/useDateFormat'

const { formatDate } = useDateFormat()
const today = formatDate(new Date())
</script>

<template>
  <p>今天日期:{{ today }}</p>
</template>

多個共用函式整理建議

可以依功能拆多個檔案,例如:

src/composables/
├── useDateFormat.ts
├── useFetch.ts
├── useAuth.ts
└── useClipboard.ts

如果只是一個簡單函式,不用 reactive,可以這樣

// utils/format.ts
export function formatCurrency(num: number): string {
  return `$${num.toFixed(2)}`
}

然後在元件或其他 composable 裡匯入使用。

什麼時候用 composable?什麼時候用純函式?

情境建議方式
需要用到 reactive、ref、watch、computed 等Composable
純邏輯處理,不涉及 Vue 的 reactivity純函式放在 utils

撰寫一個以id找尋陣列中相同id的共用函式範例

這是一個實用的共用函式範例,用來「在一個陣列中找出某個 ID 的項目」。

功能說明

  • 輸入一個陣列(例如來自 API 的資料)
  • 傳入目標 id
  • 回傳第一個 id 相同的項目(或 undefined

建立檔案 useFindById.ts

// composables/useFindById.ts

export function useFindById<T extends { id: number | string }>() {
  const findById = (array: T[], id: number | string): T | undefined => {
    return array.find(item => item.id === id)
  }

  return { findById }
}

這邊使用了泛型 T,讓你可以傳任何型別進來,只要那個型別有 id 欄位。

使用方式範例

<script setup lang="ts">
import { useFindById } from '@/composables/useFindById'

const { findById } = useFindById()

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
]

const user = findById(users, 2) // 找到 Bob
</script>

<template>
  <p v-if="user">找到的使用者:{{ user.name }}</p>
  <p v-else>找不到使用者</p>
</template>

延伸應用(找多筆符合條件)

如果想找所有符合某個 ID 的項目(例如有重複 ID 的情況),可以寫一個類似這樣的:

// composables/useFindAllById.ts

export function useFindAllById<T extends { id: number | string }>() {
  const findAllById = (array: T[], id: number | string): T[] => {
    return array.filter(item => item.id === id)
  }

  return { findAllById }
}

JavaScript 版本 的共用函式(Composable)範例

以下是 JavaScript 版本 的「以 id 找出陣列中符合的項目」的共用函式(Composable)範例

useFindById.js

// composables/useFindById.js

export function useFindById() {
  const findById = (array, id) => {
    return array.find(item => item.id === id)
  }

  return { findById }
}

使用方式範例(在 Vue 3 <script setup> 中使用)

<script setup>
import { useFindById } from '@/composables/useFindById'

const { findById } = useFindById()

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
]

const user = findById(users, 2)
</script>

<template>
  <p v-if="user">找到的使用者:{{ user.name }}</p>
  <p v-else>找不到使用者</p>
</template>

延伸:找所有符合 id 的項目(可重複 id)

// composables/useFindAllById.js

export function useFindAllById() {
  const findAllById = (array, id) => {
    return array.filter(item => item.id === id)
  }

  return { findAllById }
}

兩個共用函數寫在同一個 JS 檔案中的範例

以下是將 findByIdfindAllById 兩個共用函數寫在同一個 JS 檔案中的範例

useFindById.js

// composables/useFindById.js

export function useFindById() {
  const findById = (array, id) => {
    return array.find(item => item.id === id)
  }

  const findAllById = (array, id) => {
    return array.filter(item => item.id === id)
  }

  return {
    findById,
    findAllById,
  }
}

使用方式

<script setup>
import { useFindById } from '@/composables/useFindById'

const { findById, findAllById } = useFindById()

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 2, name: 'Bobby' },
]

const singleUser = findById(users, 2)      // 找到第一個 id 為 2 的使用者
const multipleUsers = findAllById(users, 2) // 找到所有 id 為 2 的使用者
</script>

<template>
  <p>第一位符合的使用者:{{ singleUser?.name }}</p>
  <p>所有符合的使用者:</p>
  <ul>
    <li v-for="u in multipleUsers" :key="u.name">{{ u.name }}</li>
  </ul>
</template>

擴充成「可以指定欄位名稱」的版本

不只找 id,也能找像是 user_idslugcode 等等欄位。

更新後的 useFindByKey.js

// composables/useFindByKey.js

export function useFindByKey() {
  const findByKey = (array, key, value) => {
    return array.find(item => item[key] === value)
  }

  const findAllByKey = (array, key, value) => {
    return array.filter(item => item[key] === value)
  }

  return {
    findByKey,
    findAllByKey,
  }
}

使用方式

<script setup>
import { useFindByKey } from '@/composables/useFindByKey'

const { findByKey, findAllByKey } = useFindByKey()

const users = [
  { user_id: 1, name: 'Alice' },
  { user_id: 2, name: 'Bob' },
  { user_id: 2, name: 'Bobby' },
]

const user = findByKey(users, 'user_id', 2)        // 找第一個 user_id 為 2 的人
const allUsers = findAllByKey(users, 'user_id', 2) // 找所有 user_id 為 2 的人
</script>

<template>
  <p>第一位符合的使用者:{{ user?.name }}</p>
  <p>所有符合的使用者:</p>
  <ul>
    <li v-for="u in allUsers" :key="u.name">{{ u.name }}</li>
  </ul>
</template>

全功能的陣列工具包:useArrayTools.js

支援 key/value 查找、包含/不包含、多值查找等,這個工具會包含以下幾個實用函數:

功能列表

函數名稱說明
findByKey找到第一個符合 key/value 的項目
findAllByKey找到所有符合 key/value 的項目
findIncludes找出 key 包含某字串的項目(適合模糊搜尋)
findByKeys支援多個欄位條件(AND 條件)
excludeByKey排除符合某個 key/value 的項目
uniqueByKey根據 key 去除重複項目

useArrayTools.js

// composables/useArrayTools.js

export function useArrayTools() {
  const findByKey = (array, key, value) => {
    return array.find(item => item[key] === value)
  }

  const findAllByKey = (array, key, value) => {
    return array.filter(item => item[key] === value)
  }

  const findIncludes = (array, key, keyword) => {
    return array.filter(item => {
      const target = item[key]
      return typeof target === 'string' && target.includes(keyword)
    })
  }

  const findByKeys = (array, conditions) => {
    return array.filter(item =>
      Object.entries(conditions).every(([key, value]) => item[key] === value)
    )
  }

  const excludeByKey = (array, key, value) => {
    return array.filter(item => item[key] !== value)
  }

  const uniqueByKey = (array, key) => {
    const seen = new Set()
    return array.filter(item => {
      if (seen.has(item[key])) return false
      seen.add(item[key])
      return true
    })
  }

  return {
    findByKey,
    findAllByKey,
    findIncludes,
    findByKeys,
    excludeByKey,
    uniqueByKey,
  }
}

使用範例

<script setup>
import { useArrayTools } from '@/composables/useArrayTools'

const {
  findByKey,
  findAllByKey,
  findIncludes,
  findByKeys,
  excludeByKey,
  uniqueByKey,
} = useArrayTools()

const users = [
  { user_id: 1, name: 'Alice', role: 'admin' },
  { user_id: 2, name: 'Bob', role: 'editor' },
  { user_id: 2, name: 'Bobby', role: 'editor' },
  { user_id: 3, name: 'Charlie', role: 'viewer' },
]

// 單一欄位查找
const firstEditor = findByKey(users, 'role', 'editor')

// 多筆查找
const allEditors = findAllByKey(users, 'role', 'editor')

// 模糊搜尋
const nameContainsB = findIncludes(users, 'name', 'B')

// 多條件查找
const result = findByKeys(users, { user_id: 2, role: 'editor' })

// 排除某角色
const notAdmin = excludeByKey(users, 'role', 'admin')

// 去除重複 user_id
const uniqueUsers = uniqueByKey(users, 'user_id')
</script>

超強版 useArrayTools

這版除了查找、過濾、去重之外,會加入以下進階功能:

功能新增一覽:

函數名稱說明
sortByKey根據 key 排序,支援升冪/降冪
groupByKey根據 key 分組,回傳物件結構
chunk將陣列分割成固定大小的小陣列
paginate分頁工具(指定每頁筆數與第幾頁)

完整版本 useArrayTools.js

// composables/useArrayTools.js

export function useArrayTools() {
  // === 查找與篩選 ===
  
  // 找符合 key/value 的項目在陣列的第幾筆
  const findIndexByKey = (array, key, value) => {
      let index = -1;
      for (let i = 0; i < array.length; i++) {
          if (array[i][key] === value) {
              index = i;
              break;
          }
      }
      return index;
  };
    
  const findByKey = (array, key, value) =>
    array.find(item => item[key] === value)

  const findAllByKey = (array, key, value) =>
    array.filter(item => item[key] === value)

  const findIncludes = (array, key, keyword) =>
    array.filter(item => {
      const target = item[key]
      return typeof target === 'string' && target.includes(keyword)
    })

  const findByKeys = (array, conditions) =>
    array.filter(item =>
      Object.entries(conditions).every(([key, value]) => item[key] === value)
    )

  const excludeByKey = (array, key, value) =>
    array.filter(item => item[key] !== value)

  const uniqueByKey = (array, key) => {
    const seen = new Set()
    return array.filter(item => {
      if (seen.has(item[key])) return false
      seen.add(item[key])
      return true
    })
  }

  // === 排序 ===
  const sortByKey = (array, key, order = 'asc') => {
    return [...array].sort((a, b) => {
      const aVal = a[key]
      const bVal = b[key]
      if (aVal < bVal) return order === 'asc' ? -1 : 1
      if (aVal > bVal) return order === 'asc' ? 1 : -1
      return 0
    })
  }

  // === 分組 ===
  const groupByKey = (array, key) => {
    return array.reduce((groups, item) => {
      const groupKey = item[key]
      if (!groups[groupKey]) {
        groups[groupKey] = []
      }
      groups[groupKey].push(item)
      return groups
    }, {})
  }

  // === 分割 ===
  const chunk = (array, size) => {
    const chunks = []
    for (let i = 0; i < array.length; i += size) {
      chunks.push(array.slice(i, i + size))
    }
    return chunks
  }

  // === 分頁 ===
  const paginate = (array, page = 1, perPage = 10) => {
    const start = (page - 1) * perPage
    return array.slice(start, start + perPage)
  }

  return {
    findIndexByKey,
    findByKey,
    findAllByKey,
    findIncludes,
    findByKeys,
    excludeByKey,
    uniqueByKey,
    sortByKey,
    groupByKey,
    chunk,
    paginate,
  }
}

使用範例

<script setup>
import { useArrayTools } from '@/composables/useArrayTools'

const { 
  sortByKey, 
  groupByKey, 
  chunk, 
  paginate 
} = useArrayTools()

const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'editor' },
  { id: 3, name: 'Carol', role: 'editor' },
  { id: 4, name: 'David', role: 'viewer' },
  { id: 5, name: 'Eve', role: 'editor' },
]

// 排序(依照 name 升冪)
const sorted = sortByKey(users, 'name')

// 分組(依照 role)
const grouped = groupByKey(users, 'role')

// 分塊(每 2 筆一組)
const chunks = chunk(users, 2)

// 分頁(第 2 頁,每頁 2 筆)
const page2 = paginate(users, 2, 2)
</script>

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


內容索引