서두에
TypeScript 의 타입 체크는 .ts 에 한정되지 않고, .js 도 서포트합니다
그러나파일 내용이 표준적인 JavaScript 코드만 포함하는 것을 보증하기 위해, .js 파일은 ES 구문 규범에 따라 체크되므로, TypeScript 타입 주석의 출현은 허가되지 않습니다:
.jsfiles are still checked to ensure that they only include standard ECMAScript features; type annotations are only allowed in.tsfiles and are flagged as errors in.jsfiles.
따라서 JSDoc 을 통해 JavaScript 에 추가의 타입 정보를 추가합니다:
JSDoc comments can be used to add some type information to your JavaScript code, see JSDoc Support documentation for more details about the supported JSDoc constructs.
동시에, .js 에 대한 타입 체크는 비교적 느슨하고, .ts 의 타입 체크와는 다릅니다. 차이는 주로 3 방면에 집중되어 있습니다:
-
타입 주석 방식
-
디폴트 타입
-
타입 추론 전략
P.S. 느슨한 전략 때문에, noImplicitAny, strictNullChecks 등의 엄격 체크 마크는 .js 안에서도 그다지 신뢰할 수 없습니다
一.체크开启
--allowJs 옵션은 JavaScript 파일의 컴파일을 허가하지만, 디폴트에서는 이러한 파일에 대해 타입 체크를 수행하지 않습니다. --checkJs 옵션을さらに开启하지 않는 한, 모든 .js 파일에 대해 검증을 수행합니다
| Option | Type | Default | Description |
|---|---|---|---|
--allowJs | boolean | false | Allow JavaScript files to be compiled. |
--checkJs | boolean | false | Report errors in .js files. Use in conjunction with --allowJs. |
另外, TypeScript 는 타입 체크를 제어하기 위해 사용되는 몇 가지 특수한 주석을 서포트합니다:
-
// @ts-nocheck: 파일 레벨, 타입 체크를 스킵 -
// @ts-check: 파일 레벨, 타입 체크를 수행 -
// @ts-ignore: 행 레벨, 타입 오류를 무시
이러한 주석은 더 세밀한 타입 체크 제어를 제공합니다. 예를 들어 일부 .js 파일만 체크하고 싶은 경우, --checkJs 옵션을开启하지 않고, 일부 .js 파일의 수행에 만 // @ts-check 주석을 추가합니다
二.타입 주석 방식
.js 파일 내에서는 JSDoc 을 통해 타입을 주석합니다. 예를 들어:
/**
* @type {number}
*/
var x;
x = 0;
// 오류 Type 'false' is not assignable to type 'number'.
x = false;
주의, JSDoc 은 주석 포맷에 요구가 있습니다. /** 로 시작하는 것만 인식합니다:
JSDoc comments should generally be placed immediately before the code being documented. Each comment must start with a
/**sequence in order to be recognized by the JSDoc parser. Comments beginning with /*, /***, or more than 3 stars will be ignored.
(Adding documentation comments to your code 에서 인용)
另外, 모든 JSDoc 마크가 서포트되는 것은 아닙니다. 화이트리스트는 Supported JSDoc 참조
三.디폴트 타입
另一方面, JavaScript 내에는 대량의 관용「패턴」이 존재하기 때문에, 디폴트 타입 방면에서는 상당히 느슨합니다. 주로 3 점에 나타납니다:
-
함수 파라미터는 디폴트로 옵션
-
지정되지 않은 타입 파라미터는 디폴트로
any -
타입이 느슨한 오브젝트 리터럴
함수 파라미터는 디폴트로 옵션
.js 파일 내의 모든 함수 파라미터는 디폴트로 옵션입니다. 따라서 실参数 수량이 형参数 수량보다 적은 것을 허가하지만,余分한 파라미터가 존재하는 경우는 여전히 오류가 됩니다. 예를 들어:
function bar(a, b) {
console.log(a + " " + b);
}
bar(1);
bar(1, 2);
// 오류 Expected 0-2 arguments, but got 3.
bar(1, 2, 3);
주의, JSDoc 으로 파라미터必填을 주석한 경우는 예외입니다:
/**
* @param {string} greeting - Greeting words.
* @param {string} [somebody] - Somebody's name.
*/
function sayHello(greeting, somebody) {
if (!somebody) {
somebody = 'John Doe';
}
console.log('Hello ' + somebody);
}
// 오류 Expected 1-2 arguments, but got 0.
sayHello();
sayHello('Hello');
sayHello('Hello', 'there');
// 오류 Expected 1-2 arguments, but got 3.
sayHello('Hello', 'there', 'wooo');
JSDoc 주석에 따르면, 상예 중 greeting 은必填, somebody 는 옵션입니다. 따라서 무参와 3 参는 오류가 됩니다
特殊的로, ES6 은 [디폴트 파라미터와 부정 파라미터](/articles/디폴트 파라미터와 부정 파라미터-es6 노트 4/) 를 통해 [옵션 파라미터](/articles/함수-typescript 노트 5/#articleHeader4) 를 암묵적으로 마크할 수 있습니다. 예를 들어:
/**
* @param {string} somebody - Somebody's name.
*/
function sayHello(somebody = 'John Doe') {
console.log('Hello ' + somebody);
}
// 正确
sayHello();
JSDoc 주석 (@param {string} somebody) 에서 보면 somebody 는必填이지만, 디폴트 파라미터 (somebody = 'John Doe') 는 somebody 가 옵션임을 나타냅니다. 타입 시스템은 이러한 정보를 종합하여 추론합니다
지정되지 않은 타입 파라미터는 디폴트로 any
JavaScript 에는 제네릭 파라미터를 나타내기 위한 구문이 제공되지 않았기 때문에, 지정되지 않은 타입 파라미터는 모두 디폴트로 any 타입이 됩니다
제네릭은 JavaScript 중에서 주로 2 종류의 형태로 출현합니다:
-
제네릭 클래스를 상속하고, Promise 등을 생성 (제네릭 클래스, Promise 등은 외부
d.ts에 정의) -
其它 커스텀 제네릭 (JSDoc 으로 제네릭 타입을 명시)
예를 들어:
// 제네릭 클래스를 상속 - .js
import { Component } from 'react';
class MyComponent extends Component {
render() {
// 正确 this.props.unknownProp 은 any 타입
return <div>{this.props.unknownProp}</div>
}
}
그 중에서 this.props 는 제네릭 타입을 가집니다:
React.Component<any, any, any>.props: Readonly<any> & Readonly<{
children?: React.ReactNode;
}>
.js 내에서 제네릭 파라미터의 타입을 지정하지 않는 경우, 디폴트로 any 가 되므로, 오류가 되지 않습니다. 그러나 같은 코드를 .tsx 내에서는 오류가 됩니다:
// .tsx
import { Component } from 'react';
class MyComponent extends Component {
render() {
// 오류 Property 'unknownProp' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'.
return <div>{this.props.unknownProp}</div>
}
}
Promise 의 씬도 동일합니다:
// .js
var p = new Promise((resolve, reject) => { reject(false) });
// p 타입은 Promise<any>
p;
// .ts
const p = new Promise<boolean>((resolve, reject) => { reject(false) });
// p 타입은 Promise<boolean>
p;
이 외부 선언 (d.ts) 으로부터의 제네릭 외에, 또 한 종류의 커스텀*「JavaScript 제네릭」*이 있습니다:
// .js 제네릭을 선언하지만, 타입 파라미터를 기입하지 않음
/** @type{Array} */
var x = [];
x.push(1); // OK
x.push("string"); // OK, x is of type Array<any>
// .js 제네릭을 선언하고, 동시에 타입 파라미터를 지정
/** @type{Array.<number>} */
var y = [];
y.push(1); // OK
y.push("string"); // Error, string is not assignable to number
즉 JSDoc 으로 정의된 제네릭으로, 타입 파라미터를 지정하지 않는 경우, 디폴트로 any 가 됩니다
타입이 느슨한 오브젝트 리터럴
.ts 내에서는, 오브젝트 리터럴로 변수를 초기화함과 동시에 해당 변수의 타입을 확정하고, 오브젝트 리터럴에 새 멤버를 추가하는 것을 허가하지 않습니다. 예를 들어:
// .ts
// obj 타입은 { a: number; }
let obj = { a: 1 };
// 오류 Property 'b' does not exist on type '{ a: number; }'.
obj.b = 2;
.js 내에서는 비교적 느슨합니다:
// .js
var obj = { a: 1 };
// 正确
obj.b = 2;
마치 인덱스 시그니처 [x:string]: any 를 가지고 있는 것처럼;
// .ts
let obj: { a: number; [x: string]: any } = { a: 1 };
obj.b = 2;
同樣로, JavaScript 내에서도 JSDoc 을 통해 그确切한 타입을 명시할 수 있습니다:
// .js
/** @type {{a: number}} */
var obj = { a: 1 };
// 오류 Property 'b' does not exist on type '{ a: number; }'.
obj.b = 2;
四.타입 추론 전략
타입 추론은 대입 추론과 컨텍스트 추론으로 나뉩니다. .js 에 대해서는 몇 가지针对性的인 추론 전략이 있습니다
대입 추론:
-
Class 멤버 대입 추론
-
생성 함수는 클래스와 동등
-
null,undefined,[]대입 추론
컨텍스트 추론:
-
부정 파라미터 추론
-
모듈 추론
-
네임스페이스 추론
Class 멤버 대입 추론
.ts 내에서는 클래스 멤버 선언 중의 초기화 대입을 통해 인스턴스 속성의 타입을 추론합니다:
// .ts
class Counter {
x = 0;
}
// x 타입은 number 로 추론
new Counter().x++;
그러나 ES6 Class 는 인스턴스 속성을 선언하는 구문 을 제공하지 않습니다. 클래스 속성은 동적 대입을 통해 생성됩니다. 이러한 JavaScript 관용「패턴」도 추론할 수 있습니다. 예를 들어:
class C {
constructor() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
method() {
// 오류 Type 'false' is not assignable to type 'number'.
this.constructorOnly = false;
this.constructorUnknown = "plunkbat";
this.methodOnly = 'ok';
}
method2() {
this.methodOnly = true;
}
}
class 선언 중의 모든 속성 대입은 (클래스 인스턴스) 타입 추론의 근거가 됩니다. 따라서 상예 중 C 클래스 인스턴스의 타입은:
// TypeScript
type C = {
constructorOnly: number;
constructorUnknown: string;
method: () => void;
method2: () => void;
methodOnly: string | boolean
}
구체적인 룰은 다음과 같습니다:
-
속성 타입은 생성 함수 중의 속성 대입을 통해 확정
-
생성 함수 중에서 정의되지 않은, 또는 생성 함수 중에서 타입이
undefined또는null의 속성 (此时는any), 그 타입은 모든 대입 중 오른쪽 값 타입의 연합 -
생성 함수 중에서 정의된 속성은 모두 반드시 존재한다고 간주하고, 其它 장소 (멤버 메서드 등) 에서 출현하는 것은 옵션으로 취급
-
클래스 선언 중에 출현하지 않는 속성은 모두 미정의로, 액세스하면 오류
생성 함수는 클래스와 동등
另外, ES6 之前, JavaScript 내에서는 생성 함수로 클래스를 대용했습니다. TypeScript 타입 시스템도 이「패턴」을「이해」할 수 있습니다 (생성 함수는 ES6 Class 와 동등). 멤버 대입 추론도 동일하게 적용됩니다:
function C() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
C.prototype.method = function() {
// 오류 Type 'false' is not assignable to type 'number'.
this.constructorOnly = false;
this.constructorUnknown = "plunkbat";
}
null, undefined, [] 대입 추론
.js 내에서는, 초기값이 null, undefined 의 변수, 파라미터 또는 속성은 모두 any 타입으로 간주되고, 초기값이 [] 인 것은 any[] 타입으로 간주됩니다. 예를 들어:
// .js
function Foo(i = null) {
// i 타입은 any
if (!i) i = 1; // i 타입은 여전히 any
var j = undefined; // j 타입은 any
j = 2; // j 타입은 any | number 즉 number
this.j = j;
this.l = []; // this.l 타입은 any[]
}
var foo = new Foo();
foo.l.push(foo.j);
foo.l.push("end");
同樣로, 여러 번 대입 시, 타입은 각 값 타입의 연합이 됩니다
부정 파라미터 추론
.js 내에서는 arguments 의 사용 상황에 따라 [부정 파라미터](/articles/디폴트 파라미터와 부정 파라미터-es6 노트 4/#articleHeader2) 가 존재하는지를 추론합니다. 예를 들어:
// .js
function sum() {
var total = 0
for (var i = 0; i < arguments.length; i++) {
total += arguments[i]
}
return total
}
// sum 타입은 (...args: any[]) => number
sum(1, 2, 3);
물론, JSDoc 을 통해 부정 파라미터를 선언할 수도 있습니다:
// .js
/** @param {...number} args */
function sum(/* numbers */) {
var total = 0
for (var i = 0; i < arguments.length; i++) {
total += arguments[i]
}
return total
}
// sum 타입은 (...args: number[]) => number
sum(1, 2, 3);
모듈 추론
.js 내에서는, CommonJS 모듈에 대해, exports, module.exports 의 속성 대입을 모듈导出 (export) 로서 식별하고, require 함수 호출은 모듈 도입 (import) 에 대응합니다. 예를 들어:
// .js
// 等价于 `import module "fs"`
const fs = require("fs");
// 等价于 `export function readFile`
module.exports.readFile = function(f) {
return fs.readFileSync(f);
}
P.S. 실제로, TypeScript 의 CommonJS 모듈에 대한 서포트는 이 타입 추론을 통해 완성됩니다
네임스페이스 추론
.js 내에서는, 클래스, 함수, 오브젝트 리터럴은 모두 네임스페이스로 간주됩니다. 그것들은 네임스페이스와 매우 유사하기 때문입니다 (모두 값과 타입의 이중의 의미를 가지며, 네스트를 서포트하고, かつ 삼자는 결합하여 사용할 수 있습니다). 예를 들어:
// .js
class C { }
C.D = class { }
// 또는
function Cls() {}
Cls.D = function() {}
new C.D();
new Cls.D();
특히 오브젝트 리터럴은, ES6 之前는 본래 네임스페이스로서 사용되었습니다:
var c = {};
ns.D = class {}
ns.F = function() {}
new c.D();
new c.F();
아직 댓글이 없습니다