TypeScriptのライブラリのinterfaceを拡張する

TypeScript + Nextで開発中、scriptタグを使う必要が出てきた。

(多分)バージョン11から import Script from 'next/script' を使ってScriptコンポーネント 使うようにしないと怒られる。

そこで <Script /> を使ったのだが、propsに渡す型でエラーでた。

import Script from 'next/script'

<Script
  foo="foo",
  bar="bar"
 />

// Property 'foo' does not exist on type 'IntrinsicAttributes & ScriptProps'.

まあScriptPropsにfooもbarも定義されていないので当然。

なのでScriptPropsを拡張する必要があった。

アンビエントモジュールの定義

ここではモジュールの型を変える必要があるのでモジュールそのものの型定義(script.d.ts)ファイルを用意する。

これは declare module 'モジュール名' で作れる。

import Script from 'next/script'

declare module 'next/script' {
  interface ScriptProps {
   foo: string
   bar: string
  }
}

export default Script

TypeScriptのコンパイラが自動でこのファイルが読み込まれ、next/scriptの型はこのアンビエントモジュールが優先されて使用されるようになる。

interface ScriptPropsにより既に定義されているScriptPropsに新しい定義をマージ。これでfoo, barがTSで使用できるようになった。

TypeScriptのtypeとinterfaceの違う部分を少し見る

typeはエイリアスであり、いろんな型を表すことができる

type A = number
type B = number | string
type C = {
  name: string
  age: number
}

interfaceはnumberとかnumber | stringのエイリアスは作れない

type A = number // error
type B = number | string // error
interface C { // ok
  name: string
  age: number
}

typeはオブジェクト型の交差には & を使用する

type A = {
  name: string;
};

type B = {
  age: number;
};

type Profile = A & B;

const profile: Profile = {
  name: 'name',
  age: 23,
};

interfaceはextendsで拡張する

interface A {
  name: string;
}

interface Profile extends A {
  age: number;
}

const profile: Profile = {
  name: 'name',
  age: 23,
};

interfaceは宣言のマージができる。
マージされたすべてのプロパティに従わなければいけない

interface Profile {
  name: string;
  age: number;
}

interface Profile {
  sex: string;
}

const user: Profile = {
  name: 'name',
  age: 23,
  sex: 'men',
};

typeは宣言のマージはできない

// error
type Profile = {
  name: string;
  age: number;
};

type Profile = {
  sex: string;
};

PrismaのschemaでHence the relation field must be optional as well.が出た

prismaスキーマ書いてた。

Userは1つのGroupに所属し、Groupは複数のUserを持つので "one to many" の関係性である。

Userは必ずしもGroupに所属しているとは限らない。

↓のようなshemaができた。

model User {
  groupId Int?
  group Group @relation(fields: [groupId], references: [id])
}

model Group {
  id Int @id @default(autoincrement())
  members User[]
}

これだと

"Error validating: The relation field group uses the scalar fields groupId. All those fields are optional. Hence the relation field must be optional as well."

というエラーが出る。

グループに所属していない可能性もあるので、groupIdはoptionalである。

にもかかわらず リレーションフィールドのgroupがoptionalになっていなかったが原因だった。

↓にすればエラーが消える

group Group? @relation(fields: [groupId], references: [id])

forkしたライブラリの特定のブランチを使いたい

解決策

末尾に #<ブランチ名> を追加

 "git+{CodeのHTTPSからコピったURL}#<ブランチ名>"

ストーリー

{
  "dependencies": {
    "react-native-elements": "git+{CodeのHTTPSからコピったURL}"
  }
}

こんな感じでライブラリを使っていたが特定のブランチを使いたかった。

その場合は

{
  "dependencies": {
    "react-native-elements": "git+{CodeのHTTPSからコピったURL}#<ブランチ名>"
  }
}

これでいけた

forkしたライブラリを使う時にdistがない

解決策

.gitignoreからdist外して、コミットする前にローカルでビルドしてdistを作成する

概略

TypeScropt製ライブラリをforkして変更加えてそれを使いたかった。

{
  "dependencies": {
    "react-native-elements": "git+{CodeHTTPSからコピったURL}
  }
}

こんな感じ

本来、npm publishとかするときにビルドされてdistが作成されることが多いが、forkしたものを参照するとこのプロセスがなくなる。 なのでdist作成されずにエラーになる。

なので少しスッキリしないやり方ではあるが、ローカルでビルドしてリモートにも反映。それをnpmで使用した。

DockerでNodeサーバー立てる時はTimeZoneに気を付ける

docker-composeでHapi製サーバーを立てたが、new Date()を使った処理でどうもうまく動かない箇所があった。 調べたらTimeZoneがUTCになっておられた。

TZ: Asia/Tokyoを指定すればok

 environment:
  TZ: Asia/Tokyo