TS1: Basics¶
chapter 5: Using TypeScript Compiler¶
NPM¶
- The node_modules folder is typically excluded from version control because it contains a large number of files and because packages can contain platform-specific components that don’t work when a project is checked out on a new machine.
- using
npm install
on a different machine may generate different set of packages, due to packages versions expressed in ranges, to ensure consistency, we usepackage-lock.json
file to store the dependencies versions.
tsconfig¶
- top level fields:
{
"compilerOptions": {},
"include": [],
"exclude": [],
"files": [],
"references": [],
"compileOnSave": true
}
- compiler options:
{
"compilerOptions": {
"target": "es2018",
"outDir": "./dist",
"rootDir": "./src",
"noEmitOnError": true, // don't emit compiled files if there are errors
"lib": [
// tells the compiler about the runtime environment, which features will be available for the emitted js code.
// compiler can not convert advanced features, eg. Map, Set, generators, async functions, etc.. it is your responsibility
// to provide the correct js libs at runtime. it the wrong lib was provided during the runtime, the code will not work.
"es2018",
"dom",
"esNext"
],
// module also depends on the runtime environment, it does not depend on what environment you have while developing.
// instead, what matter is the environment on the client that will execute the code.
// commonjs is supported by node and all browsers by default.
// file extensions are required by commonjs in import statements. which may cause issues since complier has to deal with .js/.ts files.
"module": "commonjs", // commonjs, amd, system, umd, es2015, es2016, es2017, es2018, esnext
// moduleResolution sets the resolution strategy for module names.
// node: local node modules in the project folder, node_modules in the node_modules folder, and the built-in node modules.
// classic: node_modules in the node_modules folder, and the built-in node modules.
"moduleResolution": "node", // node, classic
"allowJs": true, // allow javascript files to be compiled
"allowSyntheticDefaultImports": true, // allow imports from modules with no default export
"baseUrl": "./", // base url for module resolution
"checkJs": true, // check javascript files for errors
"declaration": true, // generate a .d.ts file
"downlevelIteration": true, // enables support for iterators when targeting older versions of JavaScript.
"emitDecoratorMetadata": true, // include decorator metadata in emitted js, used with the experimentalDecorators option.
"esModuleInterop": true, // used with allowSyntheticDefaultImports, to allow non-ES6 imports from modules with no default exports.
"experimentalDecorators": true, // enable experimental support for ES7 decorators.
"forceConsistentCasingInFileNames": true, // force consistent casing in file names, regardless of the operating system.
"importHelpers": true, // enable legacy import helpers.
"isolatedModules": true, // compile each file as a separate module.
"jsx": "react", // enable JSX support.
"jsxFactory": "React.createElement", // the factory function to use when creating elements for JSX.
"noEmit": true, // do not emit outputs.
"noImplicitAny": true, // error on expressions and declarations with an implied 'any' type.
"noImplicitReturns": true, // requires all paths in a function to return a result.
"noUncheckedIndexedAccess": true, // disallow properties accessed via an index signature until checked against undefined values.
"noUnusedParameters": true, // report errors on unused parameters.
"paths": [], // specify locations used to resolve module dependencies.
"resolveJsonModule": true, // allow json files to be imported as js modules.
"skipLibCheck": true, // skip type checking of declaration files.
"sourceMap": true, // generate a source map for each output js file.
"strictNullChecks": true, // prevents null and undefined from being accepted as values for other types.
"strict": true, // enable strict mode.
"suppressExcessPropertyErrors": true, // prevent errors for objects that define properties not in their type.
"typeRoots": [], // specify locations where `declaration files (.d.ts)` can be found.
"types": [] // specify type definitions to be used.
}
}
- watch mode:
tsc --watch
- tsc-watch: library that watches for changes in ts files, recompile, and start executing the emitted js files.
npx tsc-watch --onsuccess "node dist/index.js"
- tsc compiler api guide: https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
tsconfig target¶
compilerOptions.target
: specifies the target version of the ECMAScript standard, the emitted js files will be compatible with this version.- possible values:
value | description |
---|---|
ES3 | 1999, 3rd edition, baseline for all browsers |
ES5 | 2009, 5th edition, focuses on consistency |
ES6 | 2015, 6th edition, added: classes, modules, arrow functions, promises |
ES2015 | 2015, equivalent to ES6 |
ES2016 | 2016, 7th edition, added: includes method on arrays and strings, exponential operator |
ES2017 | 2017, 8th edition, added: async/await, tools for objects |
ES2018 | 2018, 9th edition, added: spread and rest operators |
ES2019 | 2019, 10th edition, added: more array functions, error handling, JSON formatting |
ES2020 | 2020, 11th edition, nullish operator, optional chaining, and loading modules dynamically |
ESNext | experimental, the latest version of the ECMAScript standard |
chapter 7: Understanding TypeScript¶
- JS is a dynamically typed language, which means that the type of a variable is not known at compile time.
- TS is a statically typed language, which means that the type of a variable is known at compile time.
any¶
- allows the use of dynamic types, so compiler won’t check the type of a variable at compile time.
- any is given to all variables with no type specified, or when compiler can not infer the type, unless
noImplicitAny
is set to true.
union types¶
- allows the use of multiple types, eg.
string | number
. - you can implicitly use any method that’s shared between all types, eg.
toString()
. - in the example above
string | number
, you can usetoString()
on a string or a number. but you can’t use.charAt()
on a string or a number. - to work around that you need to check the type of the variable, eg.
if (typeof x === 'string') { x.charAt(0); }
let x: string | number = someMethod();
x.toString(); // works
x.charAt(0); // error
if (typeof x === "string") {
x.charAt(0); // works
} else {
// x is now definitely a number
x.toFixed(); // works
}
// -----------------
const numberX = x as number;
numberX.toFixed(); // works, `on your own risk`
// -----------------
const numberXX = Number(x);
numberXX.toFixed(); // works, `on your own risk` since numberXX may be a NaN
type assertions¶
- type assertions are used to tell the compiler that you know what type you want the variable to be.
- used in different ways:
const x = <string>y;
const x = y as string;
- No type conversion is happening when asserting a type, it is your own responsibility to make sure the type is correct.
- assertion from a union type must be one of the types in the union type.
- compiler will throw an error if you try to assert a type that is not in the union type.
let x: string | number = someMethod();
const y = x as string; // works
const z = x as number; // works
const w = x as boolean; // error, boolean in not in the union type
const z = x as unknown as boolean; // works, work around on your own risk
const zz = x as any as number; // works, work around on your own risk
type guards¶
- type guards are used to check if a variable is of a certain type.
- for primitive types,
typeof
is used to check the type. eg.if (typeof x === 'string') { ... }
- for custom types, it is your responsibility to distinguish between the types.like:
- for oop and classes,
if (x instanceof MyClass) { ... }
- for literal objects,
if(obj.uniqueProperty === 'someUniqueConventionalValue') { ... }
- for oop and classes,
never¶
- never is a type that can never be assigned to anything.
-
some situations where never is inferred:
const x = (() => { throw new Error('oops') })();
x will never gets a value, and the type is inferred to benever
.-
when a statement is behind a type guard that is always false, eg:
const x = 0; if (typeof x === "string") { // x is never here } const y: string | number = someMethod(); switch (typeof y) { case "string": break; case "number": break; default: // y is never here break; }
-
when initializing an array without typing it
const x = []; // never[] const y: T[] = []; // T[]
unknown¶
- safer alternative to
any
when you don’t know the type of the variable. -
unknown value can be assigned only
any
orunknown
type, unless a type assertion or type guard is used, in which the value gets a different type.const x: unknown = someMethod(); // x is of type unknown const x1: number = x; // error, unknown can not be assigned to number const y: any = x; // works, x can be assigned to any if (typeof x === "string") { console.log(x); // x is of type string here } let x2 = x; // x3 is of type unknown let x3 = x as string; // x4 is of type string
Nullable types¶
- TS treats
null
andundefined
as legal values for all types -
TS ignores
null
andundefined
when checking the type of a variable, unlessstrictNullChecks
is set to true.const func = (x: number) => { if (x > 0) return x.toFixed(); return null; }; const x6 = func(5); // x6 is of type string (null is ignored) const x7 = func(0); // x7 is of type string (null is ignored) // ----------------- if strictNullChecks is true ----------------- const x8 = func(5); // error, x8 is of type string | null const x9 = func(0); // error, x9 is of type string | null
-
ignoring nullable values can lead to runtime errors that is hard to detect during development.
strickNullChecks
:- tells the compiler not to ignore null/undefined, and prevent assigning them to other types.
- all values that are possibly null/undefined must be guarded before using them
-
guarding against null/undefined
typeof null === 'object'
, whiletypeof undefined === 'undefined'
- using
??
operator to check if a value is null/undefined eg.x ?? 'default'
- using
!== null
and!== undefined
to check if a value is null/undefined eg.x !== null && x !== undefined
- using falsy checks to check if a value is null/undefined eg.
x || 'default'
, note: this may get triggered for other falsy values too. - using
!
after a nullable value to assert that it does exist eg.x!
orx!.toFixed()
- using type assertions (casting) to ensure the value does exist eg.
(x as number).toFixed()
// examples on methods to remove null from a union type with nullable values const func = (x: number, format: boolean = false): string | number | null => { if (x > 0) { return format ? x.toFixed(2) : x; } return null; }; //default const x1 = func(5); // x1 is of type string | number | null // type assertion const x2 = func(0)!; // number | string (assertion) // type guard const x3 = func(0); if (x3 !== null) { // x3 is of type number | string (type guard) } // Definite Assignment Assertion let x8!: string | number; ((p) => { x8 = p!; })(func(5));