個人ブログにURLのOG画像カードつける
とりあえずパッケージ入れてみたら、Module not found: Can't resolve 'util/types'
その他パッケージを最新化してみる
node -v
npm install -g n
n latest
node -v
流れで、Next.js/Reactのバージョンを上げることになった
Linkの中にaタグ入ってたらダメになったらしい
node.js、どうやらバージョン管理ソフト入れずにhomebrewで直インストールだったらしい
絶対バージョン管理入れた方がいいけど踏襲
1069 brew list
1070 brew upgrade
1071 node -v
1072 npm run dev
1073 npm run dev
1074 npm outdaed
1075 npm outdated
1076 npm uninstall @notionhq/client
1077 npm install @notionhq/client
1078 npm uninstall react
1079 npm install react
1080 npm uninstall react-dom
1081 npm install react-dom
1082 npm run dev
Notionクライアントはそのままにしたいのでバージョン指定
これも更新したい……
npm install @notionhq/client@0.4.4
サーバーサイドの利用しか想定しないので、ビルドができないっぽい
困った
getStaticProps
の中ならopen-graph-scraperが動かせることがわかった
今日はここまで
GWまで閉じ
Caludeにコード吐いてもらってテキトーにやってたら、
open-graph-scraperが内部で使ってるundiciのモジュールをwebpackが上手くロードできなくてエラーになった
./node_modules/open-graph-scraper/node_modules/undici/lib/web/fetch/util.js
Module parse failed: Unexpected token (874:57)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| // 5. If object is not a default iterator object for interface,
| // then throw a TypeError.
> if (typeof this !== 'object' || this === null || !(#target in this)) {
| throw new TypeError(
| `'next' called on an object that does not implement interface ${name} Iterator.`
Next.jsのバージョンを雑に14に上げたのが影響かなと思ったけど、12に戻しても同じだった
open-graph-scraper
を使おうとすると発生するっぽい
Package.jsonを
"open-graph-scraper": "~6.0.0"
にしたら動く?
やっと動くところまで行ったけど、やりたかったことでは全然ないな
CORSの問題でOGP画像が取得できてないようだ
SSGしてるから、そのタイミングで取ればいいか
取得できたOGPデータ
{
ogTitle: '2023年上半期ふりかえり: 一番のヘタクソでいよう、その先へ',
ogType: 'article',
ogDescription: 'Written by 蔀',
twitterCard: 'summary_large_image',
ogImage: [
{
url: 'https://www.shetommy.com/_next/image?url=%2Fprofile.png&w=640&q=75'
}
],
requestUrl: 'https://www.shetommy.com/articles/2023%E5%B9%B4%E4%B8%8A%E5%8D%8A%E6%9C%9F%E3%81%B5%E3%82%8A%E3%81%8B%E3%81%88%E3%82%8A:%20%E4%B8%80%E7%95%AA%E3%81%AE%E3%83%98%E3%82%BF%E3%82%AF%E3%82%BD%E3%81%A7%E3%81%84%E3%82%88%E3%81%86%E3%80%81%E3%81%9D%E3%81%AE%E5%85%88%E3%81%B8',
success: true
}
できた〜!
paragraphブロック、リッチテキスト要素が配列として入ってきている
これは通常であれば一つだが、文中リンクがあった場合に配列として入ってくる模様
[
{
type: 'text',
text: {
content: '個人開発に関してもちょっと悩みがあって、人の役に立つものを何かつくりたいと思っているが、特に思いつかないので、結局',
link: null
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default'
},
plain_text: '個人開発に関してもちょっと悩みがあって、人の役に立つものを何かつくりたいと思っているが、特に思いつかないので、結局',
href: null
},
{
type: 'text',
text: { content: 'PoP(Pieces of Paper)', link: [Object] },
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default'
},
plain_text: 'PoP(Pieces of Paper)',
href: 'https://github.com/0si43/PiecesOfPaper'
},
{
type: 'text',
text: { content: 'と', link: null },
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default'
},
plain_text: 'と',
href: null
},
{
type: 'text',
text: { content: 'shetommy.com', link: [Object] },
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default'
},
plain_text: 'shetommy.com',
href: 'https://github.com/0si43/shetommy.com'
},
{
type: 'text',
text: {
content: '以降何もつくっていない。PoPを地道に改善していくのも粛々とやっていくが、ぼちぼちこのアプリも俺の中で役目を終えつつある感じもして、次に行きたい。が、アイディアがない。',
link: null
},
annotations: {
bold: false,
italic: false,
strikethrough: false,
underline: false,
code: false,
color: 'default'
},
plain_text: '以降何もつくっていない。PoPを地道に改善していくのも粛々とやっていくが、ぼちぼちこのアプリも俺の中で役目を終えつつある感じもして、次に行きたい。が、アイディアがない。',
href: null
}
]
VSCodeのブレイクポイント使えないと不便なのでClaudeに設定方法を聞いた
VSCodeでは、Node.jsアプリケーションのデバッグ機能が組み込まれています。npm run dev
で起動したアプリケーションにブレイクポイントを設定するには、以下の手順を踏みます。
-
プロジェクトのルートディレクトリに
.vscode
フォルダを作成します(まだない場合)。 -
.vscode
フォルダ内にlaunch.json
ファイルを作成します。 -
launch.json
に以下の設定を追加します:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug npm script",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run-script",
"dev"
],
"port": 9229
}
]
}
-
VSCodeのデバッグビューを開きます(サイドバーの虫のアイコン、またはCtrl + Shift + D)。
-
上部の実行ボタンの隣にあるドロップダウンメニューから "Debug npm script" を選択します。
-
ブレイクポイントを設定したい行の左側の余白をクリックして、ブレイクポイントを設定します。
-
デバッグビューの上部にある再生ボタン(緑の三角形)をクリックしてデバッグを開始します。
-
アプリケーションが起動し、設定したブレイクポイントで停止します。
これで、npm run dev
で起動したアプリケーションにブレイクポイントを設定し、デバッグを行うことができます。
デバッグコンソールやウォッチ式を使って変数の値を確認したり、ステップ実行によりコードの流れを追ったりすることができます。
注意:この方法では、アプリケーションがデバッグモードで起動されるため、パフォーマンスが通常より低下する可能性があります。
本番環境ではデバッグモードを無効にすることをお勧めします。
はい、VSCodeのデバッグ中にconsole.log
などのコマンドを使うことができます。デバッグコンソール(Debug Console)を使って、現在のスコープで有効な JavaScript コードを実行できます。
デバッグ中の便利な機能として:
-
ウォッチ式(Watch Expressions):監視したい変数や式を追加することで、その値を常に確認できます。
-
ホバリング:コード内の変数の上にマウスカーソルを置くと、その時点での変数の値がポップアップ表示されます。
-
デバッグコンソール(Debug Console):
console.log
の出力を確認したり、JavaScriptコードを実行したりできます。例えば、ブレイクポイントで停止中に変数の値を変更することもできます。 -
コールスタック(Call Stack):現在の実行位置に至るまでの関数呼び出しの流れを表示します。
-
ステップ実行:「ステップオーバー(Step Over)」、「ステップイン(Step Into)」、「ステップアウト(Step Out)」の機能を使って、コードを1行ずつ実行できます。
これらの機能を活用することで、コードの動作を詳細に調査し、バグの原因を特定しやすくなります。
console.log
は古典的なデバッグ方法ですが、VSCodeの豊富なデバッグ機能と組み合わせることで、より効率的にデバッグを行えるでしょう。
デバッグコンソールで式を評価した際に、undefinedが出力されるのは正常な動作です。
デバッグコンソールに式を入力すると、その式が評価され、結果が表示されます。しかし、式の評価自体は値を返さないため、undefinedが出力されます。これは、コンソールが式の評価を完了したことを示しています。
例えば、デバッグコンソールで以下のように入力したとします:
console.log("Hello, world!")
myObject
出力結果は以下のようになります:
Hello, world!
undefined
{
// myObjectの中身
}
最初の行はconsole.log
の出力、2行目のundefinedはconsole.log
式自体の評価結果、3行目以降はmyObject
の内容が表示されます。
したがって、undefinedが出力されるのは問題ではなく、むしろJavaScriptの式の評価動作を反映した正常な動作だと言えます。オブジェクトの内容が正しく表示されている限り、undefinedが出力されることを気にする必要はありません。
リンクカードの仕様
方針
- ブロックが丸ごとリンクを表していたらカード化する
- 文中リンクのように、一部がリンクだがそれ以外の情報もあるようなケースはそのままにしたい
詳細
- paragraph
- リッチテキスト要素が一つで、それに対してURLが指定されていたらカード化する
- bookmark
- カード化する
- link_preview
- Githubのプルリクとかで使える
- Notion上のアイキャッチである程度動的に情報取得ができる。リアルタイム性の高いOGPみたいなイメージ
- 別に必要ない気がするけど、簡単に対応できそうなんでやっとく
今なら "open-graph-scraper": "^6.4.0"
を指定しても動くな
最新バージョンは6.5.1
package.json
での依存関係のバージョン指定において、^
と~
の違いは以下の通りです。
-
^
(キャレット):-
^
は、指定したバージョンと互換性のある最新のマイナーバージョンまでアップデートできることを意味します。 - 例えば、
^1.2.3
と指定すると、1.2.3
以上2.0.0
未満の最新バージョンが使用されます。 - これにより、バグ修正や新機能が含まれる可能性のあるマイナーバージョンアップデートを受け取ることができます。
- ただし、メジャーバージョンの変更は含まれません。
-
-
~
(チルダ):-
~
は、指定したバージョンと互換性のある最新のパッチバージョンまでアップデートできることを意味します。 - 例えば、
~1.2.3
と指定すると、1.2.3
以上1.3.0
未満の最新バージョンが使用されます。 - これにより、バグ修正が含まれるパッチバージョンアップデートのみを受け取ることができます。
- マイナーバージョンやメジャーバージョンの変更は含まれません。
-
つまり、^
はメジャーバージョンを固定し、マイナーバージョンとパッチバージョンの変更を許容するのに対し、~
はメジャーバージョンとマイナーバージョンを固定し、パッチバージョンの変更のみを許容します。
一般的に、^
を使用することで、互換性のあるマイナーバージョンアップデートを受け取り、新機能やバグ修正の恩恵を受けることができます。ただし、プロジェクトの安定性を重視する場合や、特定のバージョンに依存している場合は、~
を使用してパッチバージョンのみを更新することができます。
最終的には、プロジェクトの要件やリスク許容度に応じて、適切なバージョン指定を選択することが重要です。
こういう書き方できるなら、もっと色々できるな
export type BlockWithChildren = ListBlockChildrenResponse['results'][number] & {
children?: BlockWithChildren[],
ogpData?: OgpData
}
参考にしたところのこの型がどこから持ってきたのかよくわからない
動きはするけど
export type OgpData = OpenGraphProperties & {
ogImage?: OpenGraphImage | OpenGraphImage[] | undefined;
};
Node.jsモジュールのtypes.d.ts
ファイルでインポートされている型を直接参照することは、一般的にはできません。これは、TypeScriptの型定義ファイルの制限によるものです。
型定義ファイル(.d.ts
)は、コンパイル時に使用される型情報を提供するためのものであり、実行時には存在しません。そのため、types.d.ts
ファイル内でインポートされた型は、そのファイル内でのみ有効であり、外部からは直接参照できません。
ただし、モジュールがその型を外部に公開している場合は、別の方法で型を参照することができます。以下の方法を試してみてください。
-
モジュールがエクスポートしている型を使用する:
import { OgObject } from 'module-name';
モジュールが
OgObject
型を直接エクスポートしている場合は、上記の方法で型を参照できます。 -
モジュールの型定義を拡張する:
declare module 'module-name' { export type OgObject = { // OgObjectの型定義をここに記述 }; }
モジュールの型定義を拡張することで、
OgObject
型を定義し、モジュールから型をインポートできるようにします。 -
型定義ファイルを手動でコピーする:
モジュールのtypes.d.ts
ファイルから必要な型定義をコピーし、自分のプロジェクトの型定義ファイルに貼り付けます。ただし、この方法はメンテナンス性が低く、型定義の更新に追従しにくいという欠点があります。 -
モジュールの型定義ファイルを参照する:
tsconfig.jsonの"include"
オプションに、モジュールの型定義ファイルのパスを追加します。{ "compilerOptions": { // ... }, "include": [ // ... "node_modules/module-name/types.d.ts" ] }
これにより、TypeScriptコンパイラがモジュールの型定義ファイルを認識し、型を参照できるようになります。ただし、この方法はモジュールの実装の詳細に依存するため、注意が必要です。
これらの方法でも型の参照ができない場合は、モジュールの提供する型情報が不十分である可能性があります。その場合は、モジュールのドキュメントを参照するか、モジュールの開発者にお問い合わせください。
一般的に、Node.jsモジュールがTypeScriptで書かれている場合、型定義ファイルを介して型情報を提供するのが一般的です。しかし、すべてのモジュールが適切な型定義を提供しているとは限りません。モジュールの型定義が不十分な場合は、手動で型を定義するか、代替のモジュールを探すことを検討してください。
import OgObject from 'open-graph-scraper/dist/lib/types.d'
のようにNode.jsモジュールの内部の型定義ファイルを直接インポートすることには、いくつかの懸念点があります。
-
型定義ファイルの安定性:
モジュールの内部の型定義ファイルは、モジュールの実装の詳細に依存しています。モジュールのバージョンアップや内部構造の変更により、型定義ファイルのパスや内容が変更される可能性があります。その場合、型定義のインポートが失敗したり、型の不一致が発生したりする可能性があります。 -
型定義ファイルの可視性:
モジュールの内部の型定義ファイルは、一般的に公開されておらず、外部からアクセスすることを想定していない場合があります。モジュールの作者が意図的に型定義を非公開にしている場合、直接アクセスすることは適切ではないかもしれません。 -
モジュールの更新への追従:
モジュールがバージョンアップされた場合、内部の型定義ファイルのパスや内容が変更される可能性があります。その場合、型定義のインポートを手動で更新する必要があります。これは、モジュールの更新に追従するのが難しくなる可能性があります。 -
型定義の不完全性:
モジュールの内部の型定義ファイルは、モジュールの実装のために用意されたものであり、公開用の型定義としては不完全である可能性があります。型定義が不完全な場合、型チェックが適切に行われない可能性があります。
これらの懸念点を考慮すると、直接モジュールの内部の型定義ファイルをインポートするのは、一般的には推奨されません。代わりに、以下のような方法を検討してください。
- モジュールが提供する公開用の型定義を使用する。
- モジュールのドキュメントや公式のサンプルコードを参照し、推奨される型定義の使用方法に従う。
- 型定義が不十分な場合は、自分で型定義を作成するか、
any
型を使用して型チェックを回避する。
ただし、状況によっては、モジュールの内部の型定義ファイルを直接インポートすることが唯一の選択肢である場合もあります。その場合は、モジュールのバージョンを固定し、型定義のインポートが失敗した場合に備えて適切なエラーハンドリングを行うことが重要です。
また、モジュールの作者に、公開用の型定義の提供を要望することも検討してください。これにより、将来的により安定した型定義が利用できるようになるかもしれません。
import
文における{}
の有無は、モジュールのエクスポート方法によって決まります。
-
デフォルトエクスポート(Default Export):
- モジュールがデフォルトエクスポートを使用している場合、
import
文で{}
を使用する必要はありません。 - デフォルトエクスポートは、
export default
を使用してエクスポートされます。 - 例えば、
import openGraphScraper from 'open-graph-scraper'
のように、{}
なしで直接インポートできます。 - デフォルトエクスポートは、モジュールごとに1つだけ存在できます。
- モジュールがデフォルトエクスポートを使用している場合、
-
名前付きエクスポート(Named Export):
- モジュールが名前付きエクスポートを使用している場合、
import
文で{}
を使用する必要があります。 - 名前付きエクスポートは、
export
キーワードを使用して個別にエクスポートされます。 - 例えば、
import { namedExport1, namedExport2 } from 'module'
のように、{}
内に必要なエクスポートの名前を指定してインポートします。 - 名前付きエクスポートは、モジュール内で複数存在できます。
- モジュールが名前付きエクスポートを使用している場合、
モジュールがデフォルトエクスポートと名前付きエクスポートの両方を持つ場合は、以下のようにインポートできます。
import defaultExport, { namedExport1, namedExport2 } from 'module';
この場合、デフォルトエクスポートはdefaultExport
という名前でインポートされ、名前付きエクスポートは{}
内で指定された名前でインポートされます。
import openGraphScraper from 'open-graph-scraper'
の場合、open-graph-scraper
モジュールはデフォルトエクスポートを使用しているため、{}
なしでインポートできます。
一方、import { namedExport } from 'module'
のように{}
を使用する場合は、module
モジュールが名前付きエクスポートを使用していることを示しています。
モジュールのドキュメンテーションや定義を確認することで、適切なインポート方法を判断できます。
import
文における{}
とtype
の有無により、4つのパターンがあります。それぞれのパターンの意味と使い方を整理します。
-
import { identifier } from 'module'
- これは名前付きのインポートです。
-
module
から特定のidentifier
(関数、クラス、変数など)をインポートします。 -
{}
内には、インポートしたい識別子を指定します。 - インポートされた識別子は、そのまま使用できます。
-
import { identifier as alias } from 'module'
- これも名前付きのインポートですが、
as
を使用してエイリアス(別名)を付けています。 -
module
から特定のidentifier
をインポートし、alias
という別名で使用します。 - エイリアスを使用すると、名前の衝突を回避したり、より意味のある名前を付けたりできます。
- これも名前付きのインポートですが、
-
import defaultExport from 'module'
- これはデフォルトのインポートです。
-
module
のデフォルトエクスポートをdefaultExport
という名前でインポートします。 -
{}
は使用しません。 - デフォルトエクスポートは、
export default
を使用してモジュールからエクスポートされます。 - モジュールごとに1つのデフォルトエクスポートのみが可能です。
-
import type { TypeName } from 'module'
- これは型のみのインポートです。
-
module
から特定の型TypeName
をインポートします。 -
import
の前にtype
キーワードを使用することで、値ではなく型のみをインポートすることを明示します。 - インポートされた型は、型アノテーションやジェネリックスなどで使用できます。
- 型のみのインポートは、実行時には影響しません。
これらのパターンを組み合わせることもできます。例えば:
import defaultExport, { namedExport1, namedExport2 as alias } from 'module';
import type { TypeName } from 'module';
上記の例では、デフォルトエクスポートと名前付きエクスポートを同時にインポートし、さらに型のみのインポートも行っています。
import
文の構文は、モジュールのエクスポート方法と対応している必要があります。モジュールがデフォルトエクスポートと名前付きエクスポートのどちらを使用しているかによって、適切なインポート構文を選択します。また、型のみをインポートする場合は、type
キーワードを使用して明示的に指定します。
じゃああんま良くなさそうだけど、この指定で行きます
import { type OgObject } from 'open-graph-scraper/dist/lib/types.d'
既存の型定義も微妙だな
これはNotion APIのクライアントをバージョンアップするときに対応するか
普通にデプロイのときにビルドエラーで怒られるので対処する
(run devでもビルドエラーになるようにしたいな)
この型指定に対して
declare type NotionProperty =
QueryDatabaseResponse['results'][number]['properties']
このビルドエラーが出ます。どうしたらいいでしょう?
Property 'properties' does not exist on type '{ parent: { type: "database_id"; database_id: string; } | { type: "page_id"; page_id: string; } | { type: "workspace"; workspace: true; }; properties: Record<string, { type: "title"; title: RichTextItemResponse[]; id: string; } | ... 17 more ... | { ...; }>; ... 7 more ...; url: string; } | { ...; } | { ...; } | { ....'.
ビルドエラーが発生している原因は、QueryDatabaseResponse['results'][number]
の型が複数の可能性があるためです。QueryDatabaseResponse['results']
の要素には、properties
プロパティを持つ型と持たない型が混在しています。
このエラーを解決するには、NotionProperty
の型を条件付きで定義する必要があります。以下のように、QueryDatabaseResponse['results'][number]
の型をチェックし、properties
プロパティが存在する場合にのみNotionProperty
の型を定義します。
declare type NotionProperty = QueryDatabaseResponse['results'][number] extends {
properties: infer P;
}
? P
: never;
この条件付きの型定義では、以下のようなことを行っています:
-
QueryDatabaseResponse['results'][number]
が{ properties: ... }
の形式を満たすかどうかを確認します。 - 条件が満たされる場合、
infer P
を使用してproperties
の型を推論し、NotionProperty
の型として使用します。 - 条件が満たされない場合、
never
型が割り当てられます。これは、properties
プロパティが存在しない場合にはNotionProperty
の型が定義されないことを意味します。
この修正により、NotionProperty
の型はproperties
プロパティが存在する場合にのみ定義されるようになります。これにより、ビルドエラーが解消されるはずです。
ただし、この方法ではNotionProperty
の型がnever
になる可能性があることに注意してください。実際にproperties
プロパティが存在しない場合を適切に処理する必要があります。
もしproperties
プロパティが常に存在することが保証されているのであれば、型アサーションを使用してproperties
プロパティの存在を明示的に示すこともできます。
declare type NotionProperty = QueryDatabaseResponse['results'][number]['properties'] as Record<
string,
any
>;
この場合、properties
プロパティが存在しない場合にはランタイムエラーが発生する可能性があるため、注意が必要です。
適切な方法は、QueryDatabaseResponse
の型定義と実際のデータ構造に依存します。可能であれば、モジュールの作者に型定義の改善を提案することも検討してください。
なるほど、NotionProperty
が常にnever
型になってしまうのは、QueryDatabaseResponse['results'][number]
の型がproperties
プロパティを持たない可能性が高いためだと思われます。
この場合、QueryDatabaseResponse['results']
の型を詳細に検査し、properties
プロパティを持つ型のみを抽出する必要があります。以下のように、条件付きの型とExtract
ユーティリティ型を使用して、properties
プロパティを持つ型のみを抽出できます。
type ResultWithProperties = Extract<
QueryDatabaseResponse['results'][number],
{ properties: Record<string, any> }
>;
declare type NotionProperty = ResultWithProperties['properties'];
ここでは、以下のようなことを行っています:
-
ResultWithProperties
型を定義し、QueryDatabaseResponse['results'][number]
の型から{ properties: Record<string, any> }
の形式を満たす型のみを抽出します。Extract
ユーティリティ型は、指定された型のユニオンから、指定された条件を満たす型のみを抽出します。 -
NotionProperty
型をResultWithProperties['properties']
として定義します。これにより、properties
プロパティを持つ型のみがNotionProperty
型に割り当てられます。
この修正により、NotionProperty
型はproperties
プロパティを持つ型のみを表すようになります。never
型になることはなくなるはずです。
ただし、この方法では、QueryDatabaseResponse['results']
の型が正確に定義されていることが前提となります。QueryDatabaseResponse
の型定義が不完全な場合や、実際のデータ構造と型定義が一致しない場合は、別の方法を検討する必要があります。
可能であれば、@notionhq/client
モジュールの型定義を確認し、QueryDatabaseResponse
の型定義が適切であるかどうかを確認することをお勧めします。型定義が不完全な場合は、モジュールの作者に問題を報告するか、自分で型定義を拡張することを検討してください。
正確な型定義を使用することで、TypeScriptの型安全性を最大限に活用し、開発体験を向上させることができます。
Extract
は、TypeScriptの組み込みユーティリティ型の1つです。Extract
は、指定された型のユニオンから、指定された条件を満たす型のみを抽出します。
Extract
の構文は次のようになります:
type Extract<T, U> = T extends U ? T : never;
-
T
は、抽出元の型のユニオンです。 -
U
は、抽出条件となる型です。
Extract
は、T
の各要素に対して、U
に割り当て可能であるかどうかを確認します。割り当て可能な場合は、その要素の型が抽出されます。割り当て可能でない場合は、never
型が割り当てられます。
例えば、以下のような使い方ができます:
type NumberOrString = number | string;
type OnlyNumber = Extract<NumberOrString, number>;
// OnlyNumber は number 型になる
type Animal = 'dog' | 'cat' | 'bird';
type Pet = Extract<Animal, 'dog' | 'cat'>;
// Pet は 'dog' | 'cat' 型になる
type User = { id: number; name: string } | { id: number; email: string };
type UserWithName = Extract<User, { name: string }>;
// UserWithName は { id: number; name: string } 型になる
上記の例では、Extract
を使用して、ユニオン型から特定の条件を満たす型のみを抽出しています。
Extract
は、型のフィルタリングや条件付きの型の抽出に非常に便利です。特に、複雑なユニオン型から特定のプロパティを持つ型のみを抽出する場合に役立ちます。
NotionProperty
の例では、Extract
を使用して、QueryDatabaseResponse['results'][number]
の型からproperties
プロパティを持つ型のみを抽出しています。これにより、NotionProperty
型がproperties
プロパティを持つ型のみを表すようになります。
Extract
は、TypeScriptの型システムをより柔軟かつ表現力豊かにするための強力なツールの1つです。適切に使用することで、型の安全性を維持しながら、必要な型のみを抽出することができます。
block.results[0]
の型がPartialBlockObjectResponse | BlockObjectResponse
となっており、PartialBlockObjectResponse
にはtype
プロパティが存在しないためエラーが発生しています。
この問題を解決するためには、型ガードを使用してblock.results[0]
の型を絞り込む必要があります。具体的には、block.results[0]
がBlockObjectResponse
型であることを確認してから、type
プロパティにアクセスします。
以下のように修正してみてください:
const block = await notion.blocks.children.list({
start_cursor: cursor,
block_id: blockId,
page_size: 1,
});
if (block.results[0] && 'type' in block.results[0]) {
const blockType = block.results[0].type;
if (blockType === 'paragraph') {
block.results[0].paragraph.text.forEach((textObject) => {
openingSentence += textObject.plain_text;
});
}
}
ここでは、以下のように型ガードを使用しています:
-
block.results[0]
が存在することを確認するために、block.results[0]
の真偽値をチェックします。これにより、block.results
が空の場合にエラーが発生することを防ぎます。 -
'type' in block.results[0]
を使用して、block.results[0]
にtype
プロパティが存在するかどうかを確認します。これにより、block.results[0]
がBlockObjectResponse
型であることを保証します。 -
block.results[0].type
を別の変数blockType
に代入します。これにより、TypeScriptはblock.results[0]
がBlockObjectResponse
型であると推論し、type
プロパティへのアクセスが可能になります。 -
blockType
を使用して、ブロックのタイプが'paragraph'
であるかどうかを判定し、適切な処理を行います。
この修正により、block.results[0]
がBlockObjectResponse
型でない場合やtype
プロパティが存在しない場合でもエラーが発生しなくなります。
型ガードを使用することで、実際のデータ構造に合わせて型を絞り込むことができます。これにより、型エラーを回避しつつ、正しい型情報に基づいてコードを記述できます。
JavaScriptの substr()
メソッドは非推奨(deprecated)となり、代わりに substring()
または slice()
メソッドの使用が推奨されています。
-
substring(start, end)
:-
start
: 開始位置のインデックス(0から始まる) -
end
: 終了位置のインデックス(省略可能) - 指定された開始位置から終了位置の直前までの部分文字列を返します。
- 終了位置を省略した場合、文字列の最後までが対象となります。
例:
const str = 'Hello, World!' const substr = str.substring(0, 5) console.log(substr) // 出力: 'Hello'
-
-
slice(start, end)
:-
start
: 開始位置のインデックス(0から始まる) -
end
: 終了位置のインデックス(省略可能) - 指定された開始位置から終了位置の直前までの部分文字列を返します。
- 終了位置を省略した場合、文字列の最後までが対象となります。
- 負のインデックスを指定すると、文字列の末尾からの位置として扱われます。
例:
const str = 'Hello, World!' const substr1 = str.slice(0, 5) console.log(substr1) // 出力: 'Hello' const substr2 = str.slice(-6) console.log(substr2) // 出力: 'World!'
-
substring()
と slice()
は似ていますが、以下の違いがあります:
-
substring()
は開始位置と終了位置を入れ替えても同じ結果になりますが、slice()
は開始位置が終了位置より大きい場合は空の文字列を返します。 -
substring()
は負のインデックスを0として扱いますが、slice()
は負のインデックスを文字列の末尾からの位置として扱います。
一般的には、slice()
を使用することが推奨されます。slice()
は配列でも使用されるメソッドであり、一貫性があります。また、負のインデックスを使用できるため、文字列の末尾からの位置を指定しやすくなります。
以上が substr()
の代替となるメソッドです。substring()
または slice()
を使用することで、非推奨となった substr()
を置き換えることができます。
PartialBlockObjectResponse
のせいで型推論失敗してるんだけど、この定義何用なんだ
TypeScriptでは、&
(交差型)を使用して複数の型を組み合わせることができます。また、特定のプロパティを除外するには、Omit
ユーティリティ型を使用することができます。
Omit
は、指定された型からプロパティを除外した新しい型を作成するためのユーティリティ型です。構文は以下のようになります:
type OmittedType = Omit<OriginalType, Keys>
-
OriginalType
: 元の型 -
Keys
: 除外するプロパティのキーを指定するユニオン型
例えば、以下のように使用できます:
type User = {
id: number
name: string
email: string
age: number
}
type UserWithoutAge = Omit<User, 'age'>
// UserWithoutAge は { id: number; name: string; email: string } 型になる
上記の例では、User
型からage
プロパティを除外した新しい型UserWithoutAge
を作成しています。
複数の型を&
で組み合わせた場合も、同様にOmit
を使用して一部のプロパティを除外することができます:
type BaseProps = {
id: number
className: string
}
type AdditionalProps = {
title: string
description: string
}
type CombinedProps = BaseProps & AdditionalProps
type PropsWithoutClassName = Omit<CombinedProps, 'className'>
// PropsWithoutClassName は { id: number; title: string; description: string } 型になる
上記の例では、BaseProps
とAdditionalProps
を&
で組み合わせたCombinedProps
型から、className
プロパティを除外した新しい型PropsWithoutClassName
を作成しています。
このように、Omit
ユーティリティ型を使用することで、&
で組み合わせた型定義から特定のプロパティを除外することができます。除外するプロパティを柔軟に指定できるため、型の再利用性が向上します。
Omit
は TypeScript 3.5 以降で利用可能です。古いバージョンの TypeScript を使用している場合は、同等の機能を持つカスタムユーティリティ型を定義することもできます。
はい、TypeScriptでは、Omit
ユーティリティ型を使用して、交差型から特定の型を除外することができます。ただし、Omit
は型のプロパティを除外するためのものなので、少し工夫が必要です。
交差型から特定の型を除外するには、以下のようなユーティリティ型を定義することができます:
type RemoveType<T, U> = {
[P in Exclude<keyof T, keyof U>]: T[P]
}
このユーティリティ型 RemoveType
は、以下のように動作します:
-
Exclude<keyof T, keyof U>
を使用して、型T
から型U
に存在するプロパティのキーを除外します。 -
P in ...
を使用して、除外されたキーを使用して新しい型を作成します。 -
T[P]
を使用して、型T
から除外されたプロパティの型を取得します。
これを使用して、CombinedProps
から AdditionalProps
を除外することができます:
type BaseProps = {
id: number
className: string
}
type AdditionalProps = {
title: string
description: string
}
type CombinedProps = BaseProps & AdditionalProps
type PropsWithoutAdditional = RemoveType<CombinedProps, AdditionalProps>
// PropsWithoutAdditional は { id: number; className: string } 型になる
上記の例では、RemoveType
ユーティリティ型を使用して、CombinedProps
からAdditionalProps
を除外しています。結果として、PropsWithoutAdditional
型はBaseProps
型と同じ型になります。
このアプローチは、Node.jsモジュールの中で定義された交差型に対しても適用できます。モジュールの型定義を直接変更することなく、必要な型を抽出することができます。
ただし、このアプローチには制限があります。除外する型が交差型の一部である必要があります。また、除外する型が複雑な場合や、再帰的な型の場合は、このアプローチでは完全に除外できない可能性があります。
そのような場合は、モジュールの型定義をローカルで拡張したり、型アサーションを使用したりするなど、状況に応じて適切な方法を検討する必要があります。
↑これは難しいので、Extractか型アサーションで対応する
このような場合、database.find()
が返す型をNotionPage
型に適合させるために、型アサーションを使用することができます。
以下のように、as
キーワードを使って型アサーションを行います:
const page = database.find((page) => getPageTitle(page as NotionPage) == title)
ここでは、page
をNotionPage
型にアサーションしています。これにより、getPageTitle
関数の引数の型がNotionPage
型に適合し、型エラーが解消されます。
ただし、型アサーションは慎重に使用する必要があります。型アサーションを使用する際は、実際のデータが期待する型と一致していることを確認してください。不適切な型アサーションを行うと、ランタイムエラーが発生する可能性があります。
別の方法として、getPageTitle
関数の引数の型をQueryDatabaseResponse['results'][number]
に変更することも検討できます。
function getPageTitle(page: QueryDatabaseResponse['results'][number]): string {
// ページタイトルを取得するロジック
}
const page = database.find((page) => getPageTitle(page) == title)
この方法では、getPageTitle
関数内でpage
がNotionPage
型の条件を満たしているかどうかを確認し、適切に処理を行う必要があります。
例えば、型ガードを使用してpage
がNotionPage
型の条件を満たしていることを確認できます:
function getPageTitle(page: QueryDatabaseResponse['results'][number]): string {
if ('properties' in page && typeof page.properties === 'object') {
// ページタイトルを取得するロジック
} else {
throw new Error('Invalid page format')
}
}
型ガードを使用することで、page
がNotionPage
型の条件を満たしていない場合に適切なエラーハンドリングを行うことができます。
どの方法を選択するかは、プロジェクトの要件や型の安全性に対する考え方によって異なります。型アサーションは手っ取り早い方法ですが、型の安全性が保証されません。一方、関数の引数の型を変更する方法は、型の安全性を維持しつつ、適切なエラーハンドリングを行うことができます。
プロジェクトの状況に応じて、適切な方法を選択してください。コードの可読性と保守性を考慮しながら、型の安全性とエラーハンドリングのバランスを取ることが重要です。
対応したので閉じ