TypeScript learning - 22 JavaScript file type check

JavaScript file type check

Typescript versions after 2.3 support the use of – checkJs pairs js file for type check and error prompt.

You can ignore type checking by adding the / / @ TS nocheck annotation; Instead, you can check some by removing the – checkJs setting and adding a / / @ TS check comment JS file. You can also use / / @ TS ignore to ignore errors in this line. If you use tsconfig JSON and JS checks will follow some strict check tags, such as noimplicatany, strictNullChecks, etc. However, because JS checking is relatively loose, there may be some unexpected situations when using strict tags.

contrast. js files and There are some differences in the type check of ts files, which should be noted as follows:

Use JSDoc type to represent type information

In the. js file, the type can be the same as in It is inferred from the ts file. Similarly, when types cannot be inferred, they can be specified through JSDoc, just like in As in the ts file. Like TypeScript, – noimplicatany will report an error where the compiler cannot infer the type. (except for the literal amount of the object; it will be described in detail later)

The declaration modified by JSDoc annotation will be set to the type of this declaration. For example:

/** @type {number} */
var x;

x = 0;      // OK
x = false;  // Error: boolean is not assignable to number

You can find all JSDoc supported patterns and JSDoc documents here.

The inference of attribute comes from the assignment statement in the class

ES2015 does not provide a method to declare class attributes. Properties are dynamically assigned, just like object literals.

Yes js file, the compiler infers the attribute type from the attribute assignment statement inside the class. The type of property is the type of value assigned in the constructor, unless it is not defined in the constructor or undefined or null in the constructor. In this case, the type will be the union of all assigned value types. The properties defined in the constructor are considered to exist all the time, but those defined in the method and accessor are considered optional.

class C {
    constructor() {
        this.constructorOnly = 0
        this.constructorUnknown = undefined
    }
    method() {
        this.constructorOnly = false // error, constructorOnly is a number
        this.constructorUnknown = "plunkbat" // ok, constructorUnknown is string | undefined
        this.methodOnly = 'ok'  // ok, but y could also be undefined
    }
    method2() {
        this.methodOnly = true  // also, ok, y's type is string | boolean | undefined
    }
}

If a property has never been set in a class, they will be treated as unknown.

If the attribute of a class is only used for reading, its type is declared in the constructor with JSDoc. If it will be initialized later, you don't even need to assign a value to it in the constructor:

class C {
    constructor() {
        /** @type {number | undefined} */
        this.prop = undefined;
        /** @type {number | undefined} */
        this.count;
    }
}

let c = new C();
c.prop = 0;          // OK
c.count = "string";  // Error: string is not assignable to number|undefined

Constructor is equivalent to class

Before ES2015, Javascript used constructors instead of classes. The compiler supports this pattern and can recognize constructors as ES2015 classes. The attribute type inference mechanism is consistent with that described above.

function C() {
    this.constructorOnly = 0
    this.constructorUnknown = undefined
}
C.prototype.method = function() {
    this.constructorOnly = false // error
    this.constructorUnknown = "plunkbat" // OK, the type is string | undefined
}

Support CommonJS module

Yes js file, TypeScript can recognize the CommonJS module. For exports and module The assignment of exports is recognized as an export declaration. Similarly, the require function call is recognized as a module import. For example:

// same as `import module "fs"`
const fs = require("fs");

// same as `export function readFile`
module.exports.readFile = function(f) {
  return fs.readFileSync(f);
}

Support for module syntax in JavaScript files is much broader than in TypeScript. Most assignment and declaration methods are allowed.

Classes, functions, and object literals are namespaces

The classes in the. js file are namespaces. It can be used for nested classes, such as:

class C {
}
C.D = class {
}

Code before ES2015, which can be used to simulate static methods:

function Outer() {
  this.y = 2
}
Outer.Inner = function() {
  this.yy = 2
}

It can also be used to create simple namespaces:

var ns = {}
ns.C = class {
}
ns.func = function() {
}

Other changes are also supported:

// Function expression called immediately
var ns = (function (n) {
  return n || {};
})();
ns.CONST = 1

// defaulting to global
var assign = assign || function() {
  // code goes here
}
assign.extra = 1

Object literals are open

In the. ts file, when initializing a variable with an object literal, it also declares its type. New members can no longer be added to the object literal. This rule is in js file has been relaxed; Object literals have open types that allow you to add and access properties that were not previously defined. For example:

var obj = { a: 1 };
obj.b = 2;  // Allowed

The expression of object literals is like having a default index signature [x:string]: any. They can be regarded as open mappings rather than closed objects.

Similar to other JS check behaviors, this behavior can be changed by specifying the JSDoc type, for example:

/** @type {{a: number}} */
var obj = { a: 1 };
obj.b = 2;  // Error, type {a: number} does not have property b

null, undefined, and empty arrays are of type any or any []

Any variable, parameter or property initialized with null and undefined, whose type is any, even in strict null check mode. Any variable, parameter or property initialized with [], whose type is any [], even in strict null check mode. There are multiple exceptions like the one above.

function Foo(i = null) {
    if (!i) i = 1;
    var j = undefined;
    j = 2;
    this.l = [];
}
var foo = new Foo();
foo.l.push(foo.i);
foo.l.push("end");

Function parameters are optional by default

Since optional parameters cannot be specified before ES2015, the All function parameters in the js file are considered optional. It is allowed to call a function with fewer parameters than expected.

One thing to note is that calling a function with too many parameters will get an error.

For example:

function bar(a, b) {
  console.log(a + " " + b);
}

bar(1);       // OK, second argument considered optional
bar(1, 2);
bar(1, 2, 3); // Error, too many arguments

Functions annotated with JSDoc will be removed from this rule. The JSDoc optional parameter syntax is used to represent the optionality. For example:

/**
 * @param {string} [somebody] - Somebody's name.
 */
function sayHello(somebody) {
    if (!somebody) {
        somebody = 'John Doe';
    }
    console.log('Hello ' + somebody);
}

sayHello();

Var args parameter declaration inferred from arguments

If there is a reference to arguments in the function body of a function, the function will be implicitly considered to have a VAR Arg parameter (for example: (... Arg: any [] = > any)). Use JSDoc's VaR Arg syntax to specify the type of arguments.

/** @param {...number} args */
function sum(/* numbers */) {
    var total = 0
    for (var i = 0; i < arguments.length; i++) {
      total += arguments[i]
    }
    return total
}

Unspecified type parameter defaults to any

Since there is no natural syntax in JavaScript to specify generic parameters, the unspecified parameter type defaults to any.

In the extends statement:

For example, react Component is defined to have two type parameters, Props and State. In a js file, there is no legal way to specify them in the extends statement. By default, the parameter type is any:

import { Component } from "react";

class MyComponent extends Component {
    render() {
        this.props.b; // Allowed, since this.props is of type any
    }
}

Use @ augments of JSDoc to specify the type explicitly. For example:

import { Component } from "react";

/**
 * @augments {Component<{a: number}, State>}
 */
class MyComponent extends Component {
    render() {
        this.props.b; // Error: b does not exist on {a:number}
    }
}

In the JSDoc reference:

The type parameter not specified in JSDoc defaults to any:

/** @type{Array} */
var x = [];

x.push(1);        // OK
x.push("string"); // OK, x is of type Array<any>

/** @type{Array.<number>} */
var y = [];

y.push(1);        // OK
y.push("string"); // Error, string is not assignable to number

In a function call

Calls to generic functions use arguments to infer generic parameters. Sometimes, this process cannot infer types, mostly because of the lack of inferential sources; In this case, the type parameter type defaults to any. For example:

var p = new Promise((resolve, reject) => { reject() });

p; // Promise<any>;

Supported JSDoc

The following list lists the currently supported JSDoc annotations that you can use to add type information to JavaScript files.

Note that tags not listed below, such as @ async, are not yet supported.

  • @type
  • @param (or @arg or @argument)
  • @returns (or @return)
  • @typedef
  • @callback
  • @template
  • @class (or @constructor)
  • @this
  • @extends (or @augments)
  • @enum

They represent the same meaning as usejsdoc Org is usually consistent or its superset. The following code describes the differences and gives some examples.

@type

You can use the @ type tag and reference a type name (original type, type declared in TypeScript, or specified in the @ typedef tag in JSDoc). You can use any TypeScript type and most JSDoc types.

/**
 * @type {string}
 */
var s;

/** @type {Window} */
var win;

/** @type {PromiseLike<string>} */
var promisedString;

// You can specify an HTML Element with DOM properties
/** @type {HTMLElement} */
var myElement = document.querySelector(selector);
element.dataset.myData = '';

@Type can specify the union type - for example, the union of string and boolean types.

/**
 * @type {(string | boolean)}
 */
var sb;

Note that parentheses are optional.

/**
 * @type {string | boolean}
 */
var sb;

There are several ways to specify the array type:

/** @type {number[]} */
var ns;
/** @type {Array.<number>} */
var nds;
/** @type {Array<number>} */
var nas;

You can also specify the object literal type. For example, an object with a (string) and b (number) attributes uses the following syntax:

/** @type {{ a: string, b: number }} */
var var9;

You can use string and digital index signatures to specify map like and array like objects, using standard JSDoc syntax or TypeScript syntax.

/**
 * A map-like object that maps arbitrary `string` properties to `number`s.
 *
 * @type {Object.<string, number>}
 */
var stringToNumber;

/** @type {Object.<number, object>} */
var arrayLike;

These two types are equivalent to {[x: string]: number} and {[x: number]: any} in TypeScript. The compiler recognizes these two grammars.

You can specify the function type using TypeScript or Closure syntax.

/** @type {function(string, boolean): number} Closure syntax */
var sbn;
/** @type {(s: string, b: boolean) => number} Typescript syntax */
var sbn2;

Or directly use the unspecified Function type:

/** @type {Function} */
var fn7;
/** @type {function} */
var fn6;

Other types of Closure can also be used:

/**
 * @type {*} - can be 'any' type
 */
var star;
/**
 * @type {?} - unknown type (same as 'any')
 */
var question;

transformation

TypeScript draws on the conversion syntax in Closure. You can convert one type to another by using the @ type tag before the parenthesis expression

/**
 * @type {number | string}
 */
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString)

Import type

You can use the import type to import declarations from other files. This syntax is unique to TypeScript and different from JSDoc standard:

/**
 * @param p { import("./a").Pet }
 */
function walk(p) {
    console.log(`Walking ${p.name}...`);
}

Import types can also be used in type alias declarations:

/**
 * @typedef Pet { import("./a").Pet }
 */

/**
 * @type {Pet}
 */
var myPet;
myPet.name;

Import type can be used to get a value from the module.

/**
 * @type {typeof import("./a").x }
 */
var x = require("./a").x;

@param and @ returns

@The syntax of @ param is the same as that of @ param. Parameters can be declared optional using []:

// Parameters may be declared in a variety of syntactic forms
/**
 * @param {string}  p1 - A string param.
 * @param {string=} p2 - An optional param (Closure syntax)
 * @param {string} [p3] - Another optional param (JSDoc syntax).
 * @param {string} [p4="test"] - An optional param with a default value
 * @return {string} This is the result
 */
function stringsStringStrings(p1, p2, p3, p4){
  // TODO
}

The return value type of the function is similar:

/**
 * @return {PromiseLike<string>}
 */
function ps(){}

/**
 * @returns {{ a: string, b: number }} - May use '@returns' as well as '@return'
 */
function ab(){}

@typedef, @callback, @ param

@typedef can be used to declare complex types. Syntax similar to @ param.

/**
 * @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;

You can use object or object on the first line.

/**
 * @typedef {object} SpecialType1 - 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
 */
/** @type {SpecialType1} */
var specialTypeObject1;

@param allows similar syntax. Note that nested attribute names must be prefixed with parameter names:

/**
 * @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;
}

@callback is similar to @ typedef, but it specifies the function type instead of the object type:

/**
 * @callback Predicate
 * @param {string} data
 * @param {number} [index]
 * @returns {boolean}
 */
/** @type {Predicate} */
const ok = s => !(s.length % 2);

Of course, all of these types can be declared on one line using the syntax @ typedef of TypeScript:

/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */
/** @typedef {(data: string, index?: number) => boolean} Predicate */

@template

Declare generics using @ template:

/**
 * @template T
 * @param {T} p1 - A generic parameter that flows through to the return type
 * @return {T}
 */
function id(x){ return x }

Declare multiple type parameters with commas or multiple tags:

/**
 * @template T,U,V
 * @template W,X
 */

You can also specify a type constraint before the parameter name. Only the first item type parameter of the list will be constrained:

/**
 * @template {string} K - K must be a string or string literal
 * @template {{ serious(): string }} Seriousalizable - must have a serious method
 * @param {K} key
 * @param {Seriousalizable} object
 */
function seriousalize(key, object) {
  // ????
}

@constructor

The compiler infers the constructor through the assignment of this attribute, but you can make the check more strict and the prompt more friendly. You can add a @ constructor tag:

/**
 * @constructor
 * @param {number} data
 */
function C(data) {
  this.size = 0;
  this.initialize(data); // Should error, initializer expects a string
}
/**
 * @param {string} s
 */
C.prototype.initialize = function (s) {
  this.size = s.length
}

var c = new C(0);
var result = C(1); // C should only be called with new

Through @ constructor, this will be checked in constructor C, so you will get a prompt in the initialize method. If you pass in a number, you will also get an error prompt. If you call C directly instead of constructing it, you will also get an error.

Unfortunately, this means that constructors that can be constructed and called directly cannot use @ constructor.

@this

The compiler can usually infer the type of this from the context. But you can use @ this to specify its type explicitly:

/**
 * @this {HTMLElement}
 * @param {*} e
 */
function callbackForLater(e) {
    this.clientHeight = parseInt(e) // should be fine!
}

@extends

When a JavaScript class inherits a base class, there is no place to specify the type of type parameter. The @ extends tag provides such a way:

/**
 * @template T
 * @extends {Set<T>}
 */
class SortableSet extends Set {
  // ...
}

Note that @ extends only works on classes. At present, the constructor cannot inherit the class.

@enum

@The enum tag allows you to create an object literal whose members have a certain type. Unlike most object literals in JavaScript, it does not allow additional members to be added.

/** @enum {number} */
const JSDocState = {
  BeginningOfLine: 0,
  SawAsterisk: 1,
  SavingComments: 2,
}

Note that @ enum is very different from @ enum in TypeScript, and it is simpler. However, unlike the enumeration of TypeScript, @ enum can be any type:

/** @enum {function(number): number} */
const Math = {
  add1: n => n + 1,
  id: n => -n,
  sub1: n => n - 1,
}

More examples

var someObj = {
  /**
   * @param {string} param1 - Docs on property assignments work
   */
  x: function(param1){}
};

/**
 * As do docs on variable assignments
 * @return {Window}
 */
let someFunc = function(){};

/**
 * And class methods
 * @param {string} greeting The greeting to use
 */
Foo.prototype.sayHi = (greeting) => console.log("Hi!");

/**
 * And arrow functions expressions
 * @param {number} x - A multiplier
 */
let myArrow = x => x * x;

/**
 * Which means it works for stateless function components in JSX too
 * @param {{a: string, b: number}} test - Some param
 */
var sfc = (test) => <div>{test.a.charAt(0)}</div>;

/**
 * A parameter can be a class constructor, using Closure syntax.
 *
 * @param {{new(...args: any[]): object}} C - The class to register
 */
function registerClass(C) {}

/**
 * @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
 */
function fn10(p1){}

/**
 * @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
 */
function fn9(p1) {
  return p1.join();
}

Known unsupported modes

It is not allowed to treat an object as a type in the value space unless the object creates a type, such as a constructor.

function aNormalFunction() {

}
/**
 * @type {aNormalFunction}
 */
var wrong;
/**
 * Use 'typeof' instead:
 * @type {typeof aNormalFunction}
 */
var right;

The = suffix on the literal attribute of the object cannot be specified. This attribute is optional:

/**
 * @type {{ a: string, b: number= }}
 */
var wrong;
/**
 * Use postfix question on the property name instead:
 * @type {{ a: string, b?: number }}
 */
var right;

Nullable types only work when strictNullChecks checking is enabled:

/**
 * @type {?number}
 * With strictNullChecks: true -- number | null
 * With strictNullChecks: off  -- number
 */
var nullable;

The non nullable type is meaningless and should be treated as its original type:

/**
 * @type {!number}
 * Just has type number
 */
var normal;

Unlike the JSDoc type system, TypeScript only allows types to be marked as packages that do not contain nulls. There is no explicit non nullable – if strictNullChecks is enabled, then number is non null. If it is not enabled, number can be null.

Keywords: Javascript Front-end TypeScript

Added by JustGotAQuestion on Mon, 07 Feb 2022 08:03:23 +0200