JSDocでよくね?
綾坂こと.icon
// @ts-checkが通用するのはVSCodeのみbsahd.icon
deno checkでもチェックできる
また、TypeScriptの場合型チェックが通らないとそもそもコンパイルできないため動かないが、JSDocの場合型チェックをしないまま動かしてしまうことができる
そうなんだ…… 現代でJavaScriptをVSCode以外で書く人間いるのか……?綾坂こと.icon
2021年くらいまではscrapboxでJavaScript書いてましたtakker.icon
1年前くらいから今までずっとdevelopertoolでしか書いてないik.icon
2025-04-17追記 : AtCoderでコードテスト画面でJavaScript直書きをやってるのでここは嘘でした綾坂こと.icon
コードの途中に型情報を書くなという気持ちになる綾坂こと.icon
わからないbsahd.iconMijinko_SD.iconSummer498.icon
トランスパイルしたらどの道型情報外れるよ(暴論)Mijinko_SD.icon やはりDeno.icon君しか勝たんtakker.iconbsahd.icon
code:example.ts
function add(left: number, right: number): number {
return left + right;
}
code:example-with-tsdoc.ts
/**
* 「型についての情報が書かれている部分」と「ロジックが書かれている部分」は分離されてたほうがわかりやすいと思う
* @param left - 左項
* @param right - 右項
* @returns 和
*/
function add(left: number, right: number): number {
return left + right;
}
code:example.js
/**
* 「型についての情報が書かれている部分」と「ロジックが書かれている部分」は分離されてたほうがわかりやすいと思う
* @param {number} left - 左項
* @param {number} right - 右項
* @returns {number} - 和
*/
function add(left, right) {
return left + right;
}
「この関数は数値leftと数値rightを取ってleft+rightを返します。」より「leftは数値、rightは数値です。この関数はleft+rightを返します。」になってたほうが嬉しいな〜と個人的に思っている綾坂こと.icon
個人の好みだとは思うけれどやっぱりよくわからないMijinko_SD.icon
一番最初に見たものから離れられてないだけじゃないかな
これは逆も然り?綾坂こと.icon
「型についての情報が書かれている部分」と「ロジックが書かれている部分」は分離されてたほうがわかりやすくなっているとは感じないので興味深いtakker.icon
考えたことなかったので面白い
どういうところがわかりやすくなったと感じているのか知りたい
おもしろい、一理あるかもしれないmrsekut.icon
ただ、タイトルの「JSDocでよくね?」はsyntaxの話と、型の有用性の話が混じっている気もする
やりたければ、TypeScriptでも型だけ定義して、あとから実装に紐づけることもできる
code:ts
type Add = (left: number, right: number) => number;
const add: Add = (left, right) => left + right;
こうすれば、コードの途中に型情報は書いていない(?)
(コードってどこ?というのはある)
(こう書いたときに綾坂こと.iconの要求を満たせているのかわからない)
これいいかも綾坂こと.icon
関数名を型名にも使わなきゃいけないのがちょっと気になるが、これは私にとって読みやすい
ただ、結局leftとrightと返り値が何を表すのかをコメントで書くだろうと思うとJSDocでよくね?に戻ってくる 2025-05-27 これは一部嘘でした 綾坂こと.icon
/** @type {(a: bigint, b: bigint) => bigint} - aとbの最大公約数を求める */みたいな書き方をすることもあることに気づいた
なるほど。数学の関数定義と実装と似た表記だなSummer498.icon
$ f: \R\times\R\to\R
$ f:(x,y)\mapsto x+y
実装の方ってコロンじゃなくて何だっけ
更にわかり易い例で言えば、型定義ファイル.d.tsもある
あれは実装と型を完全に別ファイルに書いているので、コードの途中に型が書かれない
別ファイルに書かれると困るかも綾坂こと.icon
近く(できれば隣)に書いてあって欲しいけど、完全に一緒に書かれていてほしくない
果たしてあれが本当にわかりやすいと言えるだろうか
TypeScriptユーザがJSDocよりもTypeScriptを好むのは
そちらの方が記述量が少ない (syntax関連)
そちらの方が表現できる幅が広い (型の有用性)
とかそういうのが大きいと思う
要求しているモノが同じなら、コメント内に書くかどうかはそこまで重要ではないmrsekut.icon
syntaxの方は慣れとか好みの影響が大きい思う
TypeScriptをバリバリ好んで使う人にとっては、JSDocの表現力が自分の要求に全く届いていないから使っていないだけ
JSDocで十分ならそれで良い
TSユーザが小さいプログラムにも(JSDocではなく)TSを使うのは、
単にJSDocの記法を別途覚えるのが面倒、
プログラムが大きくなったときにTSに切り替えるのが面倒
とかそんな感じではないだろうか
両方Summer498.icon
小さいプログラムは JS で書くから JSDoc 使わないし、大きくなったら TS 使うし
更に、他の言語にまで話を展開するなら、型は実行時にも読まれる言語のほうが多いmrsekut.icon
その場合、コメントみたいに離れた世界にあるよりも、
同一のコードとして存在していたほうが処理系視点で扱いやすい
とかもありそう
JSにおいてもclass定義は型のようなもので、
ではこれを「コードから分離して書く」ならどうなるのか?というのは気になる
実用的なコードを書くときにはこう書きたくなるSummer498.icon
code:example.ts
function add(
left: number,
right: number
): number {
return left + right;
}
今から function add の定義をします。
引数は
left という名の number と
right という名の number で、
戻り値は number です。
実装は return left + right です
関数名が長くなるほど、型名が長くなるほど、こんな感じで改行したくなる
この書き方は JSDoc の書き方に近い
もしかしたら JSDoc をすんなり導入できるかもしれない
TypeScriptの機能性を知らないのか、ただ単に型定義の恩恵を知らないのか、どっちなんだMijinko_SD.icon
ちなみにMijinko_SD.iconは最初は型定義いらないやんって思ってた(今は違う)
型定義が無いと、そもそもその関数が入力に何を求めていて何を出力するのかさっぱりわからないよね…?
そもそもその関数が入力に何を求めていて何を出力するのかさっぱりわからない←同意 綾坂こと.icon
TypeScriptは結構厳密に型チェックしてくれるし、連想配列の型(interface, type, 他の言語で言う構造型)をUtility型を使うとかして部分的に変更して使い回すこともできる 厳密に型定義されると何が良いかと言うと、ぬるぽ(JSで言うTypeError)のような実行時エラーをコード実装時に気づくことができるため、開発効率が格段に上がること 部分的に変更できて何が良いかと言うと、型の柔軟性が高いからより厳密な型もより簡単に(回りくどいことをせずに)組めるということ
逆にJSDocを読み取るタイプの型チェッカーがどこまでできるのか知らないけれど、こういうことはできるのかな
code:possibleNull.ts
const foo = () => {
interface aryType {id: number, value?: unknown}
const possibleNull: aryType | undefined = ary.find(e => e.id === 1)
// この時点ではpossibleNullはaryType | undefined型扱い
if (!possibleNull) return
// このif文より下は、possibleNullはaryType扱いとなる(undefinedが消える)
// undefinedの可能性は消えたため、そのまま呼び出してもエラーにならない
console.log(possibleNull.id ** 10)
}
foo()
↓実行コマンド
型チェッカーの厳密度によると思うけれど、これができないなら厳密さの面でTypeScriptの方が勝るということになる
こういうことか……?綾坂こと.icon
https://gyazo.com/f271de560904803aac565bfd7a764558
左の赤波線部分は「'possibleNull' は 'undefined' の可能性があります。」
code:possibleNull.js
// @ts-check
/**
* @typedef {Object} AryType
* @prop {number} id
* @prop {unknown} value
*/
const foo = () => {
/** @type {AryType[]} */
/** @type {AryType|undefined} */
const possibleNull = ary.find(a => a.id === 1);
if (!possibleNull) return;
console.log(possibleNull.id ** 10);
};
foo();
JSDocで表現できない型がいくつかあるtakker.icon
無視していいエッジケース
readonlyとかas constとかas Typeなどの表現もできなかったはず これは方法を知らないだけかもしれない
できない気がする、TypeScriptってそんなことできたんだ綾坂こと.icon
強いてあげるならObject.freeze()で凍結してミスってたらエラー吐かせるくらい?
readonly は使いたいなぁSummer498.icon
いつも readonly やから逆に使わんでいい説あるけど
@readonly bsahd.iconあるんかSummer498.icon
コンパイル時に済ませたいのとbundle sizeを大きくしたくないのとで、なるべく型で制約をかけたいtakker.icon JSDocはオブジェクトのプロパティの型もかけるので、まあいいか……となる綾坂こと.icon
code:example.js
/** @type {{ name: string, age: number }} */
const userInfo = {
"name": "山田太郎",
"age": 18,
};
これでreadonlyできるかもtakker.icon
/** @type {Readonly<{ name: string, age: number }>} */は通るはずだし
知見takker.icon
いやできるんか〜い綾坂こと.icon
この辺の記事も詳しいmrsekut.icon
見覚えある記事だtakker.icon
declareもSymbolも使ったことないな……綾坂こと.icon
declare …… 調べてみたけどこれが使えそうなときはインポート後に適宜@typeしてあげればいいか〜と思っている
@typeだと無理ですtakker.icon
例えばこれはJSと@ts-checkで書けません
code:ts
declare const scrapbox: Scrapbox;
scrapbox.PopupMenu.addButton({
title: "全→半",
onClick: (text) => text
.replace(/A-Za-z0-9/g, s => String.fromCharCode(s.charCodeAt(0) - 0xFEE0)) .replaceAll(".", ". ")
.replaceAll(",", ", ")
.replaceAll("(", " (")
.replaceAll(")", ") ")
.replaceAll(" ", " "),
});
code:js
// @ts-check
/** @typedef {import("jsr:@cosense/types@0.10.8/userscript").Scrapbox} Scrapbox */
// ↓型付けできない
scrapbox.PopupMenu.addButton({
title: "全→半",
onClick: (text) => text
.replace(/A-Za-z0-9/g, s => String.fromCharCode(s.charCodeAt(0) - 0xFEE0)) .replaceAll(".", ". ")
.replaceAll(",", ", ")
.replaceAll("(", " (")
.replaceAll(")", ") ")
.replaceAll(" ", " "),
});
(Node.js環境なら/** @typedef {import("https://jsr.io/@cosense/types/0.10.8/scrapbox.ts").Scrapbox} Scrapbox */に置き換える)
Node.jsってURLインポートできなかった気がするbsahd.icon
pnpm/yarnならjsr入るけど("pnpm add jsr:@cosense/types"で)
だめなのかtakker.icon
Node.jsに詳しい人修正しといて~
パッケージ側でだめだったbsahd.icon
パッケージ側でnpmベースシステムに対応する必要があるっぽい
code:txt
bsahd@bsahdarch ~ 0
$ mktemp -d
/tmp/tmp.jssQtROtLr
bsahd@bsahdarch ~ 0
$ cd /tmp/tmp.jssQtROtLr
bsahd@bsahdarch /tmp/tmp.jssQtROtLr 0
$ pnpm i jsr:@cosense/types
Packages: +1
+
Progress: resolved 1, reused 0, downloaded 1, added 1, done
dependencies:
+ @cosense/types <- @jsr/cosense__types 0.10.8
Done in 1.3s using pnpm v10.11.0
bsahd@bsahdarch /tmp/tmp.jssQtROtLr 0
$ node
Welcome to Node.js v23.11.1.
Type ".help" for more information.
import("@cosense/types")
Promise {
<pending>,
}
Uncaught:
Error ERR_PACKAGE_PATH_NOT_EXPORTED: No "exports" main defined in /tmp/tmp.jssQtROtLr/node_modules/@cosense/types/package.json imported from /tmp/tmp.jssQtROtLr/repl at exportsNotFound (node:internal/modules/esm/resolve:314:10)
at packageExportsResolve (node:internal/modules/esm/resolve:661:9)
at packageResolve (node:internal/modules/esm/resolve:774:12)
at moduleResolve (node:internal/modules/esm/resolve:854:18)
at defaultResolve (node:internal/modules/esm/resolve:984:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:780:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:687:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:305:38) {
code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}
2025-06-04 今まさにdeclareの出番がきました これはさすがに必要です綾坂こと.icon
CDN経由でclassをimportしたいとき、JSDocではどうしようもない
code:typedefs/declare/OriginStorage.d.ts
export class OriginStorage {
constructor(storageName: string);
storageName: string;
// ︙(略)
}
}
code:import/OriginStorage.mjs
// @ts-check
/// <reference types="./../typedefs/declare/OriginStorage.d.ts" />
export { OriginStorage };
code:test.js
//@ts-check
import { OriginStorage } from "./import/OriginStorage.mjs";
const testStorage = new OriginStorage("testStorage"); // ←これは補完が効く
JSDocでもできてほしいなぁ
Symbol …… /** @type {symbol} */はできるけどできないのは何だ……?
関数の出入りのタイミングでしか型の情報を表明できないのがなーnishio.icon
名前が定義されるタイミングでそれの型も定義されてて欲しくない?
@typeのこと……?綾坂こと.icon
code:example.js
/** @type {string} ユーザーの入力 */
const userInput = window.prompt();
Javaから入った身としては型宣言が無いのが怖くて仕方ないik.icon
拡張機能いくつか作ったから大分慣れたけどそれでも少し大きな拡張機能作るときは型宣言が無いと心配になる
まあ無い分の融通も受けてはいるんだけれども…
classベースで書かれた型無しJS libraryを読むとき苦労したtakker.icon
引数に何が入ってくるのか特定が非常に困難
deno非対応のライブラリだと妥協でjsdocにすることはあるが...bsahd.icon
今日deno+tsを使い始める => 井戸端にはts否定記事が書かれている
Pythonから入った身としてはコード内に型情報が入ることに特に違和感はない
(PythonではTSと同様の記法で型注釈をいれる)
表記的にすごく似ている
deno.iconts.iconTypeScript使って
これ見たい 綾坂こと.icon
実際はTypeScriptがいい理由よりJavaScriptがダメな理由を挙げる方が楽な気がするMijinko_SD.icon
知りたいのは"JSDocがダメな理由"かも 綾坂こと.icon
JavaScriptのダメなところを解消するためにはJSDocでは不十分だが、TypeScriptは十分であるとする理由
(素の)JavaScriptがダメなのはわかる
jsrだとentry pointsのJSファイルには必ず.d.tsが必要で、JSDocが認識されないtakker.icon JSDocを認識させるissueは立っているが、活動していない
認識してる!!bsahd.icon
JSで書くと、transpileせずにそのままbrowserで読み込める利点はあるtakker.icon
githubにあるコードの直リンクをimportできる
とはいえ、transpile不要という特徴が生かされる場面はあまり思いつかない
scrapbox内でコードを書く場合は重宝するかも
そのまま読み込めると外への公開が楽だな〜とは思っているかも綾坂こと.icon
GitHub Pagesとかは自動でtranspileやってくれると認識しているので、それが強い理由かと言われると微妙
現状トランスパイル不要の利点はまあまあ活かせるけれどMijinko_SD.icon
最近だとNode.jsがTypeScriptにネイティブ対応しようとしてるという話もあるし そのうちブラウザ内で直接TypeScriptが実行できるようになる気もする
(2024-09-19)ただ進展はそんなにない
難航してるっぽい
もしかして実行前だけでなく実行時もチェックしようとしてる??Mijinko_SD.icon
そうであれば難航するのも頷ける
型注釈部分を無視してよければそんなにむずくなさそう綾坂こと.icon
でも結局フロント作るときはVite.iconとかバンドラー使うでしょbsahd.icon
ChatGPT.icon4o mini(free)
JSDocとTypeScript、どちらを選ぶかはケースバイケースだと思います。JSDocの良さは、JavaScriptのコードベースで型の情報を追加できる点です。型情報を別のファイルに切り出さず、コード内にコメントとして書けるのは手軽で便利ですが、やはり型の厳密性や表現力ではTypeScriptにかなう部分は少ないですね。
TypeScriptはコンパイル時に厳密な型チェックが行われるため、開発時にエラーを早期に発見でき、バグを減らすことができます。特に大規模なプロジェクトでは、TypeScriptの方がコードの可読性とメンテナンス性を高めるため、選ばれることが多いです。しかし、JSDocはJavaScriptのコードベースでも利用でき、特に小規模なプロジェクトやライブラリでは手軽に使えるメリットがあります。
JSDocの型チェックがどこまでできるかはツールに依存しますが、基本的にはTypeScriptほど厳密な型検査は期待できません。とはいえ、簡易的な型情報を提供するには十分です。例えば、@typedefや@paramで型を指定することで、コードの理解を助けることができますが、深い型の構造や動的な型のチェックに関してはTypeScriptに軍配が上がります。
結局、プロジェクトの規模や求められる型の精度によって使い分けるのがベストです。TypeScriptをフル活用できる場合は、型の厳密性や他の言語との整合性を考えてTypeScriptが有利ですが、軽量なプロジェクトやすでにJavaScriptを使っているコードベースではJSDocが有用かもしれません。
綾坂こと.icon