Open16

TypeScript

まさきちまさきち

TypeScript配列 nullを許容しない方法

export type NonEmptyArray<T> = [T, ...T[]] | [...T[], T]

https://zenn.dev/chot/articles/321f58dfa01339


配列から空値を削除する方法

const array = [ 0, 1, 2," ", "", undefined, null];
const new_array = array.filter(Number.isFinite);

OR 

const array = [ "JavaScript", 0, 1, " ", "", undefined, null];
const new_array = array.filter(Boolean);

https://1-notes.com/javascript-remove-empty-from-array-with-filter/


曜日のみ取得

const getWeek = (date: string) => {
      const week = ['日', '月', '火', '水', '木', '金', '土']
      return week[new Date(date).getDay()]
}


switchを使用できない場合

    const useDate = computed<{ label: string; date: string }>(() => {
      const handlers: Record<number, () => { label: string; date: string }> = {
        [CONTRACT.COA]: () => ({
          label: '使用日',
          date: new DateTime(
            contractInfo.value?.photos
          ).toJpString(),
        }),
        [CONTRACT.MOTHER_PLAN]: () => ({
          label: '使用日',
          date: new DateTime(
            contractInfo.value?.photos?.date
          ).toJpString(),
        }),
      }
     const attendHandler = () => {
        const customerInfo =
          attendContractsInfo.value?.contract_customers.find(
            (c) => c.estimate_id === selectedTabId.value
          )
        return {
          label: '使用日',
          date: new DateTime(customerInfo?.use_date).toJpString(),
        }
      }
      // 該当する処理を実行
      return (handlers[selectedTabId.value] || attendanceHandler)()
まさきちまさきち

Awaited<T>

https://zenn.dev/okunokentaro/articles/01gm397f4bvdzkhskwh2q6dyf2


絞り込み

    const filter = ref({
      customerId: false,
      customerName: false,
      customerNameKana: true,
      contractId: false,
    })

    const handleSelectedOption = (type: string) => {
      Object.keys(filter.value).forEach((key: string) => {
        filter.value[key as keyof typeof filter.value] = key === type
      })
    }

    /** 絞り込み検索の処理 */
    const handleClickReseach = () => {
      if (filter.value.contractId) {
        filteredChecklist.value = checklistDate.value.filter(
          (checklist) =>
            String(checklist.contract_id).indexOf(searchText.value) > -1
        )
      }
      if (filter.value.customerId) {
        filteredChecklist.value = checklistDate.value.filter(
          (checklist) =>
            String(checklist.customer_id).indexOf(searchText.value) > -1
        )
      }
      if (filter.value.customerName || filter.value.customerNameKana) {
        filteredChecklist.value = checklistDate.value.filter((checklist) => {
          const name = checklist.first_name + checklist.last_name
          const kana = checklist.first_name_kana + checklist.last_name_kana
          return (
            name.indexOf(searchText.value) > -1 ||
            kana.indexOf(searchText.value) > -1
          )
        })
      }
      emit('clickReseach', filteredChecklist.value)
    }
まさきちまさきち

オブジェクトの配列における重複の削除

  • filterの場合
const uniqueUsers = users.filter(
  (element, index, self) => self.findIndex((e) => e.id === element.id) === index
);

「要素数が小さい配列を」「頻繁に(多数)処理する」という前提がある場合にfilterを使用することで効率が良くなる。

Arrayのfilterを使うアルゴリズムが本質的に二重ループである(いわゆる、計算量がO(n^2)のアルゴリズム)ことが原因です。この結果からは、要素数が増えてくるとオーバーヘッドよりも計算量の方が支配的になってくる

https://qiita.com/piroor/items/02885998c9f76f45bfa0

→ どれだけの量のデータが渡されるか事前に予想できない場面では、なるべく計算量が小さいアルゴリズムを採用する事が望ましい


  • Mapを使って重複を削除する場合
/**
 * IDが重複している要素を除去した配列を返す
 * @param items keyにidが存在するオブジェクトからなる配列
 * @returns ID重複のないオブジェクトの配列
 */
export const deleteDuplicateIdItem = <T extends { id: number }>(
  items: T[]
): T[] => {
  return Array.from(new Map(items.map((item) => [item.id, item])).values())
}

Mapを作成したらMap.prototype.values()でvalueのみを抽出して、Array.from()でシャローコピーする。

https://qiita.com/allJokin/items/28cd023335641e8796c5#mapを使って重複を削除する

        combinedData = Array.from(
          withEmergencyDate
            .reduce((map, item) => {
              const existing = map.get(item.contract_id)
              if (existing) {
                if (item.isEmergency || !existing.isEmergency) {
                  map.set(item.contract_id, item)
                }
              } else {
                map.set(item.contract_id, item)
              }
              return map
            }, new Map<number, ChecklistContract>())
            .values()
        )
まさきちまさきち

二次元配列 同時判定

以下の二次元配列でisHiddenがtrueとなっているデータを含まないように処理する

type Data = {
  item: string
  detail?: string
  isHidden?: boolean
}

const    mainCoordination: Data[][] = []

// まず、内側の配列に対してfilterを適用して非表示の要素を削除。
// 次に、外側の配列に対してfilterを適用して空の配列を除去。
const visibleItems = mainCoordination
  .map(innerArray => innerArray.filter(item => !item.isHidden))
  .filter(innerArray => innerArray.length > 0);
まさきちまさきち

Node.jsのsharpについてメモ

export const bufferDate = {
  toBase64: async (from: string) => {
    // Base64データをバイナリデータにデコード
    const imageBuffer = Buffer.from(base64, 'base64')

    // sharpインスタンスの生成と画像メタデータの取得
    const sharpInstance = sharp(imageBuffer)
    const metadata = await sharpInstance.metadata()
    const fileSizeMb = imageBuffer.length / (1024 * 1024) // バイトからMBへ変換

    let processedImage // 画像フォーマットに応じた圧縮処理の適用

    // ファイルサイズが3MB以上の場合
    if (fileSizeMb > 3 && metadata.format === 'png') {
      processedImage = sharpInstance.resize(800).png({ compressionLevel: 7 })
    } else if (fileSizeMb > 3 && metadata.format === 'jpeg') {
      processedImage = sharpInstance.resize(800).jpeg({ quality: 60 })
    } else {
      processedImage = sharpInstance
    }
    const rotatedImageAndBuffer =  await processedImage.rotate().toBuffer()

    // 処理した画像を再度Base64エンコードする
    const newBase64Image = rotatedImageAndBuffer.toString('base64')
    return `data:application/pdf;base64,${newBase64Image}`
  },
}
  • バイナリ【binary】:「0」と「1」の2文字しか表現できないコンピューターが読み取ることのできるデータ形式のことを指す(全ての情報を2進数に変換しているので、コンピュータが解釈するために用意されたデータは全てバイナリ形式となる)
  • base64:64進数を意味する言葉(すべてのデータをアルファベット(a~z, A~z)と数字(0~9)、一部の記号(+,/)の64文字で表すエンコード方式)
JSONなどで特殊文字を含まないように画像データをbase64でエンコードしたり、Webページの表示の際にリクエスト数を減らすために
base64でエンコードした画像をhtmlにそのまま埋め込むなどの用途で用いられている。
→ 文字列をバイナリ化 → base64でエンコード
  • エンコード:符号化を行う作業のこと
  • デコード:逆に符号化されたデータを元に戻す作業
  • Buffer.from():Node.js環境でバイナリデータを処理する為のオブジェクト
Buffer.from('テスト', 'utf8’); // <Buffer e3 83 86 e3 82 b9 e3 83 88> → 文字列をバイナリデータに変換している。
    ドキュメント: https://qiita.com/Yuki_Oshima/items/6346231d93ca342899f0
  • 「throw new Error(error)」と「throw error」の違い:
throw error の場合:キャッチされたエラーオブジェクトをそのまま再投する(エラーのオリジナルのスタックトレースやメッセージ、カスタムプロパティが保持される)
throw new Error(error) の場合:
既存のエラーオブジェクト error を新しい Error オブジェクトのコンストラクタに渡しています。
error が既に Error オブジェクトである場合は、スタックトレースやエラーメッセージが予期せず変更される可能性があります。
throw new Error(error) を使用する場面は、キャッチされたエラーに基づいて新しいエラーを生成し、それに関連する追加の情報やカスタムエラーメッセージを提供したい場合に限られる。

sharpの出力形式について: https://blog.kozakana.net/2019/04/sharp-output-format/
公式ドキュメント: https://sharp.pixelplumbing.com/api-output#png


バイトからMBへ変換

const fileSizeMb = imageBuffer.length / (1024 * 1024) // バイトからMBへ変換
1MB = 1024 x 1024 = 1,048,576バイト

バイト(Byte): データの基本単位(1バイトは8bit)
キロバイト(KB): 1KBは1024バイト(2^10=1024)
メガバイト(MB): 1MBは1024KB即ち1024×1024 バイト(2^20=1048576)バイトが1MB
たがって、バッファの長さ(バイト数)を 1024×1024で割ることによりMBを求めることができる。

まさきちまさきち

ブラケット構文でundefinedだった時の対応

...(list && { list })の記述はlistがundefinedでない限り、listオブジェクトを返却オブジェクトに展開して追加するためもの。...list,と書いた場合、listがundefinedの場合でも展開しようと試みるため、期待する動作をしない可能性があり、構文エラーになる。

return {
     {
        addition_type: 1
      },
    ...(list && { list }),
}
まさきちまさきち
type dressingItem = Exclude<
  dressingItemCreate['dressing_items'],
  null
>[number]
  • Exclude<..., null>により、この型からnullを除外 → nullを含まないdressing_itemsの型のみが残る。この時点ではまだ配列全体の型。
  • 最後に[number]を使うことで、この配列型から個々の要素の型を取得する。つまり、配列の全要素に共通する型を指す。
  • この手法を用いることで、配列内の個別の要素に対して型安全性を保証しつつ操作を行うことができます。このように、[number]を使うことで配列型から要素の型を抽出し、配列の中の個々の要素がどのような型であるかを明確に定義することが目的
まさきちまさきち

InstanceType

export default defineComponent({
  components: {
    ValidationObserver,
  },
}
const validationObserver = ref<InstanceType<typeof ValidationObserver>>()

const isValid = (await validationObserver.value.validateWithInfo()).isValid
まさきちまさきち
const roles = [1,2,3,4,5]
contractFamilyInfo?.forEach((fam, index) => {
   fam.family_role = roles[index % roles.length]
})

まさきちまさきち

TypeScriptのブラケット記法 Error

ブラケット記法でプロパティへアクセスした際に以下Errorが発生

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'xxx'. No index signature with a parameter of type 'string' was found on type 'xxx'

→ string型でキーを指定されており、存在しないキーを指定してしまう可能性がある為Errorとなる。

https://zenn.dev/katoaki/articles/37a8cff3a8a32a


対応

keyofやtypeofを指定して、存在するキーが格納されることをTypeScriptに知らせる。

    /** 五十音サブカラム */
    const getSubColumnSection = (
      kana: keyof typeof SUB_SYLLABARY_TABLE_MAP
    ) => {
      // すでに選択されているボタンを押した場合は非表示にする
      if (selectedMainButton.value === kana) {
        selectedMainButton.value = null
        subColumn.value = []
      } else {
        selectedMainButton.value = kana
        const columnKey =
          (SUB_SYLLABARY_TABLE_MAP[
            kana
          ] as keyof typeof JALAN_SYLLABARY_TABLE) || 'SUB_WA_COLUMN'
        subColumn.value = JALAN_SYLLABARY_TABLE[columnKey] || []
      }
    }
まさきちまさきち

Exponential Backoff(指数関数バックオフ)

https://developers.google.com/workspace/drive/labels/limits?hl=ja

https://qiita.com/sauna1137/items/1747d9c0910127899917

export const expentialBackoff aync(
   fn: () => Promise<T>,
   retries = 5,
   baseDelay = 100
): Promise<T> =>  {
  for(let attempt = 0; attempt <= retries; attempt++) {
     try{
        return await fn() 
     } catch(err) {
         if(attempt === retries) return throw new err

         const delay = Math.random() * baseDelay * 2 ** attempt
         console.warn(`Retry ${attempt + 1} after ${Math.round(delay)}ms`)
         await sleep(delay)
      }
    throw new Error('This should not be reached')
  }
}

const sleep(ms: number): Promise<void> {
   return new Primse((resolve) => setTimeout(resolve, ms))
}

const result = await exponentialBackoff(async () => {
     await fetch('https://api.example.com/data')
}
  • Math.random():ジッター(ばらつき)を加えることで同時アクセスを緩和

なぜ指数関数的に増加させる必要があるのか?Math.random() * baseDelay * attempt のような**線形増ではダメなのか?
指数関数的な待機時間(2^attempt)は短期的な再施行回数を避けるため。システムの安定性を守るため
線形(* attempt)でも動くが、負荷抑制効果は小さく、混雑や障害時には効果が弱い。
WebサービスやAPIのスロットリング制限に引っかかる可能性が上がる

リトライ回数 線形(delay * attempt) 指数(delay * 2^attempt)
1回目 100ms 100ms
2回目 200ms 200ms
3回目 300ms 400ms
4回目 400ms 800ms
5回目 500ms 1600ms

「指数バックオフ + ジッター戦略」ごとの実装(Full Jitter / Equal Jitter など)についても戦略として取れる。

まさきちまさきち

https://itnext.io/the-hidden-cost-of-js-arrays-ae39b5356e52

source と target に共通する値(交差する要素)があるかどうかを判定したい。
配列 arr = [] は、数値インデックスを持つオブジェクト。
arr[4] = null のように代入すると、配列の4番目に null が入るだけでなく、実質的に arr にプロパティ "4" が追加されたことと同じ意味になる。

const source = [1, 2, 3];
const target = [4, 5, 6];

function isIntersect() {
    const arr = [];
    target.forEach(item => arr[item] = null);
    return source.some(item => item in arr);
}

// ただシンプルに Setで良さそう
function isIntersect() {
  const set = new Set(target);
  return source.some(item => set.has(item));
}

これだと、データ量が多い場合に実行速度に影響が出る。

const source = [1, 2, 3];
const target = [4, 5, 6];

function isIntersect() {
  return source.some(item => target.includes(item));
}