Open18

TypeScript

まさきちまさきち

Provide / Inject

https://ja.vuejs.org/guide/components/provide-inject.html#prop-drilling

Vieで通常、親コンポーネントから子コンポーネントにデータを渡す必要がある場合、propsを使用するが、深くネストされたコンポーネントの場合は不便なので、provide / inject で解決する。


第 1 引数はインジェクションキーと呼ばれ、文字列または Symbol となる。インジェクションキーは、子孫のコンポーネントが、インジェクション(注入)に必要な値を探すのに使用される。

provide

<script setup>
import { provide } from 'vue'
provide(/* key */ 'message', /* value */ 'hello!')
</script>


inject

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>


Providing Type Safety

symbol型の型定義。asなどを使用する必要がない。

// types.ts
interface Product {
  name: string;
  price: number;
}
// symbols.ts
import { InjectionKey } from 'vue';
import { Product } from '@/types';

const ProductKey: InjectionKey<Product> = Symbol('Product');

https://logaretm.com/blog/type-safe-provide-inject/

まさきちまさきち

Vue watcher

https://ja.vuejs.org/guide/essentials/watchers.html

deep watch は、監視対象のオブジェクトのネストされた全てのプロパティをトラバースする必要があるため、大きなデータ構造で使用するときにはコストが高くなる。

watchEffect は同期的な処理中のみ依存先を追跡します。非同期のコールバックで使用する場合、最初の await の前にアクセスされたプロパティのみが追跡される。

watchEffectは依存先の追跡と副作用を 1 つのフェーズにまとめたものになります。同期処理を実行している間にアクセスしたすべてのリアクティブのプロパティを自動的に追跡します。これにより、より便利で一般的にコードが短くなりますが、そのリアクティブの依存先はあまり明示的にならなくなってしまう。



コールバックが実行されるタイミング

デフォルトでは、ユーザーが生成したウォッチャーのコールバックは Vue コンポーネントが更新される前に呼ばれる。コールバック内で DOM へアクセスしようとすると、DOM は Vue が更新を適用される前の状態となる。

Vue の更新後にウォッチャーコールバック内で DOM へアクセスしたいとき、flush: 'post' オプションで指定する必要がある。

watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})
まさきちまさきち

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

  • 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);
まさきちまさきち

npm ERR! code ERESOLVE

➜  frontend git:(main)npm install stylelint stylelint-scss --save-dev
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: frontend@0.1.0
npm ERR! Found: stylelint@15.11.0
npm ERR! node_modules/stylelint
npm ERR!   dev stylelint@"^15.11.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer stylelint@"^16.0.2" from stylelint-scss@6.1.0
npm ERR! node_modules/stylelint-scss
npm ERR!   dev stylelint-scss@"*" from the root project

npm ERR! code ERESOLVEが依存関係の解決に問題があることを示している。`


npm ERR! Could not resolve dependency:
npm ERR! peer stylelint@"^16.0.2" from stylelint-scss@6.1.0
npm ERR! node_modules/stylelint-scss
npm ERR!   dev stylelint-scss@"*" from the root project

上記エラーにより、stylelint-scss バージョン 6.1.0 が stylelint のバージョン ^16.0.2 を必要としていることを示しているが、プロジェクトには stylelint のバージョン 15.11.0 がインストールされているため、依存関係の競合が発生していることを示している。この場合の解決策としてstylelintのバージョンを^16.0.2にアップデートする必要がある。


dependencyとpeerDependencyについて

dependencyとは、そのnpmモジュールが動作するのに必要なモジュールやライブラリのこと。

たとえばアプリケーションのUI構築にReactを利用するとき、npm install reactを実行しますが、これはアプリケーションのdependencyにreactを追加することと同義です。Reactがなければそのアプリケーションは動かないため、そのアプリケーションはReactに対してdependencyをもっています。

peerDependencyは、そのNPMモジュールが一緒に利用することを想定しているモジュールやライブラリのこと。


--legacy-peer-depsとは

peerDependencyの依存解決をせずにインストールを続行するオプションのため、非推奨

https://zenn.dev/minamiso/articles/78b22716f3338d

まさきちまさきち

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] || []
      }
    }