一.JSDoc 과 타입 체크
.js 파일 내에서는 TypeScript 타입 주석 구문을 서포트하지 않습니다:
// 错误 'types' can only be used in a .ts file.
let x: number;
따라서, .js 파일에 대해, JavaScript 구문과 호환되는 타입 주석 방식이 필요 합니다. 예를 들어 JSDoc:
/** @type {number} */
let x;
// 错误 Type '"string"' is not assignable to type 'number'.
x = 'string';
이러한 특수한 형식 (/** 로 시작하는) 의 주석을 통해 타입을 표현하여, 그에 의해 JavaScript 구문과 호환합니다. TypeScript 타입 시스템은 이러한 JSDoc 마크를 해석하여 추가 타입 정보 입력을 취득하고, 타입 추론과 결합하여 .js 파일의 타입 체크를 수행합니다
P.S. .js 타입 체크에 관한 보다 많은 정보는, [JavaScript 파일 체크_TypeScript 노트 18](/articles/检查 javascript 文件-typescript 笔记 18/) 참조
二.서포트 정도
TypeScript 는 현재 (2019/5/12) 일부의 JSDoc 마크만 서포트 하고 있습니다. 구체적으로는:
-
@type:오브젝트를 기술 -
@param(또는@arg또는@argument):함수 파라미터를 기술 -
@returns(또는@return):함수 리턴값을 기술 -
@typedef:커스텀 타입을 기술 -
@callback:콜백 함수를 기술 -
@class(또는@constructor):該 함수는new키워드를 통해 호출해야 함을 나타냄 -
@this:此处this의 지향을 기술 -
@extends(또는@augments):상속 관계를 기술 -
@enum:한 세트의 관련 속성을 기술 -
@property(또는@prop):오브젝트 속성을 기술
P.S.완전한 JSDoc 마크 리스트는 Block Tags 참조
특별히, 제네릭에 대해, JSDoc 에는 적절한 마크가 제공되지 않았습니다. 따라서, 추가의 마크를 확장했습니다:
@template:제네릭을 기술
P.S. @template 마크로 제네릭을 기술하는 것은 Google Closure Compiler 에서 유래하며, 보다 많은 관련 토론은 Add support for @template JSDoc 참조
三.타입 주석 구문
TypeScript 는 JSDoc 타입 주석과 호환하며, 동시에 JSDoc 마크 중에서 TypeScript 타입 주석 구문을 사용하는 것도 서포트합니다:
The meaning is usually the same, or a superset, of the meaning of the tag given at usejsdoc.org.
맞습니다, 또 슈퍼세트 입니다. 따라서 any 타입에는 3 종류의 주석 방식이 있습니다:
// JSDoc 타입 주석 구문
/** @type {*} - can be 'any' type */
var star = true;
/** @type {?} - unknown type (same as 'any') */
var question = true;
// 모두 TypeScript 타입 주석 구문과 동등
/** @type {any} */
var thing = true;
구문 방면에서는, JSDoc 은 주로 Google Closure Compiler 타입 주석 에서 차용했으며, TypeScript 에는 자체의 [타입 구문](/articles/基本类型-typescript 笔记 2/) 이 있으므로, 이자에는 몇 가지 차이가 존재합니다
타입 선언
@typedef 마크를 사용하여 커스텀 타입을 선언합니다. 예를 들어:
/**
* @typedef {Object} SpecialType - creates a new type named 'SpecialType'
* @property {string} prop1 - a string property of SpecialType
* @property {number} prop2 - a number property of SpecialType
* @property {number=} prop3 - an optional number property of SpecialType
* @prop {number} [prop4] - an optional number property of SpecialType
* @prop {number} [prop5=42] - an optional number property of SpecialType with default
*/
/** @type {SpecialType} */
var specialTypeObject;
이하의 TypeScript 코드와 동등합니다:
type SpecialType = {
prop1: string;
prop2: number;
prop3?: number;
prop4?: number;
prop5?: number;
}
let specialTypeObject: SpecialType;
타입 참조
@type 마크를 통해 타입명을 참조합니다. 타입명은 기본 타입이어도, TypeScript 선언 파일 (d.ts) 내에 정의된 타입이어도, JSDoc 마크 @typedef 를 통해 정의된 타입이어도構いません
예를 들어:
// 기본 타입
/**
* @type {string}
*/
var s;
/** @type {number[]} */
var ns;
/** @type {Array.<number>} */
var nds;
/** @type {Array<number>} */
var nas;
/** @type {Function} */
var fn7;
/** @type {function} */
var fn6;
// 외부 선언 파일 중에 정의된 타입
/** @type {Window} */
var win;
/** @type {PromiseLike<string>} */
var promisedString;
/** @type {HTMLElement} */
var myElement = document.querySelector('#root');
element.dataset.myData = '';
// JSDoc @typedef 로 정의된 타입
/** @typedef {(data: string, index?: number) => boolean} Predicate */
/** @type Predicate */
var p;
p('True or not ?');
오브젝트 타입도 오브젝트 리터럴을 통해 기술하며, 인덱스 서명도 동일하게 적용됩니다:
/** @type {{ a: string, b: number }} */
var obj;
obj.a.toLowerCase();
/**
* 문자열 인덱스 서명
* @type {Object.<string, number>}
*/
var stringToNumber;
// 동등
/** @type {{ [x: string]: number; }} */
var stringToNumber;
// 수치 인덱스 서명
/** @type {Object.<number, object>} */
var arrayLike;
// 동등
/** @type {{ [x: number]: any; }} */
var arrayLike;
함수 타입에도 2 종류의 구문이 선택 가능합니다:
/** @type {function(string, boolean): number} Closure syntax */
var sbn;
/** @type {(s: string, b: boolean) => number} Typescript syntax */
var sbn2;
전자는 형식 파라미터명을 생략할 수 있고, 후자는 function 키워드를 생략할 수 있으며, 의미는 같습니다
동様に 타입 조합도 서포트합니다:
// 유니온 타입 (JSDoc 타입 구문)
/**
* @type {(string | boolean)}
*/
var sb;
// 유니온 타입 (TypeScript 타입 구문)
/**
* @type {string | boolean}
*/
var sb;
이자는 동등하며, 구문에 약간의 차이가 있을 뿐입니다
크로스 파일 타입 참조
특별히, import 를 통해_다른 파일 중에 정의된 타입을 참조_ 할 수 있습니다:
// a.js
/**
* @typedef Pet
* @property name {string}
*/
module.exports = {/* ... */};
// index.js
// 1. 타입을 참조
/**
* @param p { import("./a").Pet }
*/
function walk(p) {
console.log(`Walking ${p.name}...`);
}
// 1. 타입을 참조하고, 동시에 앨리어스를 붙임
/**
* @typedef { import("./a").Pet } Pet
*/
/**
* @type {Pet}
*/
var myPet;
myPet.name;
// 3. 추론된 타입을 참조
/**
* @type {typeof import("./a").x }
*/
var x = require("./a").x;
주의, 이 구문은 TypeScript 고유의 것입니다 (JSDoc 은 서포트하지 않습니다). JSDoc 중에서는 ES Module 도입 구문을 채택합니다:
// a.js
/**
* @typedef State
* @property {Array} layers
* @property {object} product
*/
// index.js
import * as A from './a';
/** @param {A.State} state */
const f = state => ({
product: state.product,
layers: state.layers,
});
이 방식은 실제의 import 를 추가합니다. 만약 순수한 타입 선언 파일 ( @typedef 만의 .js, d.ts 에 유사) 이라면, JSDoc 방식은 무용한 파일 (주석만 포함) 을 도입하게 되지만, TypeScript 방식에는 이 문제가 존재하지 않습니다
P.S.TypeScript 는 동시에 이 2 종류의 타입 도입 구문과 호환합니다. 보다 많은 관련 토론은 Question: Import typedef from another file? 참조
타입 변환
타입 변환 (TypeScript 중의 [타입 어설션](/articles/基本类型-typescript 笔记 2/#articleHeader4)) 구문은 JSDoc 과 일치하며, 둥근 괄호 전의 @type 마크를 통해 둥근 괄호 내의 식 타입을 설명합니다:
/** @type {!MyType} */ (valueExpression)
예를 들어:
/** @type {number | string} */
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString)
// 错误 Type '"hello"' is not assignable to type 'number'.
typeAssertedNumber = 'hello';
P.S.주의, 둥근 괄호가 필수 입니다.否则 인정되지 않습니다
四.일반적인 타입
오브젝트
일반적으로 @typedef 마크를 사용하여 오브젝트 타입을 기술합니다. 예를 들어:
/**
* The complete Triforce, or one or more components of the Triforce.
* @typedef {Object} WishGranter~Triforce
* @property {boolean} hasCourage - Indicates whether the Courage component is present.
* @property {boolean} hasPower - Indicates whether the Power component is present.
* @property {boolean} hasWisdom - Indicates whether the Wisdom component is present.
*/
TypeScript 타입과 동등합니다:
interface WishGranter {
hasCourage: boolean;
hasPower: boolean;
hasWisdom: boolean;
}
// 또는
type WishGranter = {
hasCourage: boolean;
hasPower: boolean;
hasWisdom: boolean;
}
만약일회성의 타입 선언 (재이용 불요, 추가로 타입을 정의하고 싶지 않음) 이라면, @param 마크를 사용하여 선언할 수 있습니다. options.prop1 형식의 속성명을 통해 멤버 속성의 네스트 관계를 기술합니다:
/**
* @param {Object} options - The shape is the same as SpecialType above
* @param {string} options.prop1
* @param {number} options.prop2
* @param {number=} options.prop3
* @param {number} [options.prop4]
* @param {number} [options.prop5=42]
*/
function special(options) {
return (options.prop4 || 1001) + options.prop5;
}
함수
@typedef 마크로 오브젝트를 기술하는 것과 유사하게, @callback 마크를 사용하여 함수의 타입을 기술할 수 있습니다:
/**
* @callback Predicate
* @param {string} data
* @param {number} [index]
* @returns {boolean}
*/
/** @type {Predicate} */
const ok = s => !(s.length % 2);
TypeScript 코드와 동등합니다:
type Predicate = (data: string, index?: number) => boolean
또 @typedef 특수 구문 (TypeScript 만 서포트, JSDoc 에는 없습니다) 을 사용하여 오브젝트 또는 함수의 타입 정의를 1 행에 통합할 수 있습니다:
/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */
/** @typedef {(data: string, index?: number) => boolean} Predicate */
// TypeScript 코드와 동등
type SpecialType = {
prop1: string;
prop2: string;
prop3?: number;
}
type Predicate = (data: string, index?: number) => boolean
파라미터
함수 파라미터는 @param 마크를 통해 기술하며, @type 구문과 같지만, 파라미터명이 1 개 추가됩니다. 예를 들어:
/**
* @param {string} p1 필수 파라미터
*/
function f(p1) {}
옵션 파라미터에는 3 종류의 표현 방식이 있습니다:
/**
* @param {string=} p1 - 옵션 파라미터 (Closure 구문)
* @param {string} [p2] - 옵션 파라미터 (JSDoc 구문)
* @param {string} [p3 = 'test'] - 디폴트 값이 있는 옵션 파라미터 (JSDoc 구문)
*/
function fn(p1, p2, p3) {}
P.S.주의, 접미 등호 구문 ({string=} 등) 은 오브젝트 리터럴 타입에는 적용되지 않습니다. 예를 들어 @type {{ a: string, b: number= }} 는 非法한 타입 선언으로, 옵션 속성은 속성명 접미 ? 로 표현해야 합니다
가변장 파라미터에는 2 종류의 표현 방식이 있습니다:
/**
* @param {...string} p - A 'rest' arg (array) of strings. (treated as 'any')
*/
function fn(p){ arguments; }
/** @type {(...args: any[]) => void} */
function f() { arguments; }
리턴값
리턴값의 타입 주석 방식도 유사합니다:
/**
* @return {PromiseLike<string>}
*/
function ps() {
return Promise.resolve('');
}
/**
* @returns {{ a: string, b: number }}
*/
function ab() {
return {a: 'a', b: 11};
}
P.S. @returns 와 @return 은 완전히 동등하며, 후자는 전자의 앨리어스입니다
클래스
컨스트럭터
타입 시스템은 this 에의 속성 대입에 기반하여 컨스트럭터를 추론하며, @constructor 마크를 통해 컨스트럭터를 기술할 수도 있습니다
이자의 차이는 @constructor 마크가 있는 경우, 타입 체크가 보다 엄격 해집니다. 구체적으로는, 컨스트럭터 중의 this 속성 액세스 및 컨스트럭터 파라미터를 체크하며, (new 키워드를 통하지 않고) 컨스트럭터를 직접 호출하는 것을 허가하지 않습니다:
/**
* @constructor
* @param {number} data
*/
function C(data) {
this.size = 0;
// 错误 Argument of type 'number' is not assignable to parameter of type 'string'.
this.initialize(data);
}
/**
* @param {string} s
*/
C.prototype.initialize = function (s) {
this.size = s.length
}
var c = new C(0);
// 错误 Value of type 'typeof C' is not callable. Did you mean to include 'new'?
var result = C(1);
P.S. @constructor 마크를 삭제하면, 이 2 개의 에러는 보고되지 않습니다
게다가, 컨스트럭터 또는 클래스 타입의 파라미터에 대해, TypeScript 구문에 유사한 방식으로 그 타입을 기술할 수 있습니다:
/**
* @template T
* @param {{new(): T}} C 컨스트럭터 C 가 반드시 동일 클래스 (또는 서브클래스) 의 인스턴스를 반환할 것을 요구
* @returns {T}
*/
function create(C) {
return new C();
}
P.S.JSDoc 은 Newable 파라미터를 기술하는 방식을 제공하지 않습니다. 상세는 Document class types/constructor types 참조
this 타입
대부분의 경우, 타입 시스템은 컨텍스트에 기반하여 this 의 타입을 추론할 수 있습니다. 복잡한 시나리오에서는 @this 마크를 통해 this 의 타입을 명시적으로 지정할 수 있습니다:
// 추론 타입은 function getNodeHieght(): any
function getNodeHieght() {
return this.innerHeight;
}
// this 타입을 명시적으로 지정, 추론 타입은 function getNodeHieght(): number
/**
* @this {HTMLElement}
*/
function getNodeHieght() {
return this.clientHeight;
}
상속
TypeScript 에서는, 클래스의 상속 관계는 JSDoc 을 통해 기술할 수 없습니다:
class Animal {
alive = true;
move() {}
}
/**
* @extends {Animal}
*/
class Duck {}
// 错误 Property 'move' does not exist on type 'Duck'.
new Duck().move();
@augments (또는 @extends) 는 베이스 클래스의 제네릭 파라미터를 지정하기 위해서만 사용됩니다:
/**
* @template T
*/
class Box {
/**
* @param {T} value
*/
constructor(value) {
this.value = value;
}
unwrap() {
return this.value;
}
}
/**
* @augments {Box<string>} 기술
*/
class StringBox extends Box {
constructor() {
super('string');
}
}
new StringBox().unwrap().toUpperCase();
그러나 JSDoc 과 다른 점은, @arguments/extends 마크는Class 에만 사용 가능하며, 컨스트럭터에는 적용되지 않습니다:
/**
* @constructor
*/
function Animal() {
this.alive = true;
}
/**
* @constructor
* @augments Animal
*/
// 错误 JSDoc ' @augments' is not attached to a class.
function Duck() {}
Duck.prototype = new Animal();
따라서, @augments/extends 마크의 작용은 매우 약하며, 비 Class 상속을 기술할 수 없고, 상속 관계를 결정할 수도 없습니다 (상속 관계는 extends 구에 의해 결정되며, JSDoc 의 기술은 카운트되지 않습니다)
열거
열거는 @enum 마크를 사용하여 기술하지만, TypeScript 열거 타입 과는 다르며, 주요차이는:
-
열거 멤버의 타입이 일치할 것을 요구
-
그러나 열거 멤버는 임의의 타입可以是
예를 들어:
/** @enum {number} */
const JSDocState = {
BeginningOfLine: 0,
SawAsterisk: 1,
SavingComments: 2,
}
/** @enum {function(number): number} */
const SimpleMath = {
add1: n => n + 1,
id: n => n,
sub1: n => n - 1,
}
제네릭
제네릭은 @template 마크를 사용하여 기술합니다:
/**
* @template T
* @param {T} x - A generic parameter that flows through to the return type
* @return {T}
*/
function id(x) { return x }
let x = id('string');
// 错误 Type '0' is not assignable to type 'string'.
x = 0;
TypeScript 코드와 동등합니다:
function id<T>(x: T): T {
return x;
}
let x = id('string');
x = 0;
복수의 [타입 파라미터](/articles/泛型-typescript 笔记 6/#articleHeader3) 가 있는 경우, 쉼표로 구분하거나, 복수의 @template 태그를 사용할 수 있습니다:
/**
* @template T, U
* @param {[T, U]} pairs 이항조
* @returns {[U, T]}
*/
function reversePairs(pairs) {
const x = pairs[0];
const y = pairs[1];
return [y, x];
}
// 동등
/**
* @template T
* @template U
* @param {[T, U]} pairs 이항조
* @returns {[U, T]}
*/
function reversePairs(pairs) {
const x = pairs[0];
const y = pairs[1];
return [y, x];
}
게다가, [제네릭 제약](/articles/泛型-typescript 笔记 6/#articleHeader8) 도 서포트합니다:
/**
* @typedef Lengthwise
* @property length {number}
*/
/**
* @template {Lengthwise} T
* @param {T} arg
* @returns {T}
*/
function loggingIdentity(arg) {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
TypeScript 코드와 동등합니다:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
특별히, @typedef 마크와 결합하여 제네릭 타입을 정의할際, 반드시 먼저 제네릭 파라미터를 정의 해야 합니다:
/**
* @template K
* @typedef Wrapper
* @property value {K}
*/
/** @type {Wrapper<string>} */
var s;
s.value.toLocaleLowerCase();
@template 와 @typedef 의 순서는 역전할 수 없습니다.否则 에러가 보고됩니다:
JSDoc ' @typedef' tag should either have a type annotation or be followed by ' @property' or ' @member' tags.
TypeScript 제네릭 선언과 동등합니다:
type Wrapper<K> = {
value: K;
}
Nullable
JSDoc 중에서는, 명시적으로 Null 가능 타입과 비 Null 타입을 지정할 수 있습니다. 예를 들어:
-
{?number}:number | null를 나타냄 -
{!number}:number를 나타냄
그러나 TypeScript 중에서는 명시적으로 지정할 수 없습니다. 타입이 Null 을 포함하는지 여부는 --strictNullChecks 옵션에만 의존 합니다:
/**
* @type {?number}
* strictNullChecks 를开启時, 타입은 number | null
* strictNullChecks 를关闭時, 타입은 number
*/
var nullable;
/**
* @type {!number} 명시적으로 비 Null 을 지정해도 무효, strictNullChecks 옵션에만 의존
*/
var normal;
아직 댓글이 없습니다