diff --git a/package.json b/package.json index 7b8e683f30e7..43fcf7ae2290 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "scripts": { "start": "yarn workspace typescriptlang-org start", "build": "yarn workspace typescriptlang-org build", + "test": "yarn jest", "playground": "concurrently -p \"[{name}]\" -n \"TS,WEB\" -c \"bgBlue.bold,bgMagenta.bold\" \"yarn workspace playground start\" \"yarn workspace playground-sandbox start\"", "deploy:dev": "yarn build && az storage blob upload-batch -s 'packages/typescriptlang-org/public' -d \\$web --account-name playgroundstatichosting --subscription 'TypeScript – Internal Services'" }, diff --git a/packages/examples/README.md b/packages/examples/README.md new file mode 100644 index 000000000000..1cd2ac2fb03a --- /dev/null +++ b/packages/examples/README.md @@ -0,0 +1,15 @@ +# TypeScript Example Code + +The English examples can be found in [`en/`](en/). + +# Deployment + +There is a table of contents JSON file which contains +all the useful metadata about the hierarchy and sort +order for the docs. + +It's likely that we'll need to create this per translation +in the future, but for now the table of contents will +default to english. + +The script is in [`scripts/generateTOC.js`](scripts/generateTOC.js). diff --git a/packages/examples/en/3-7/Fixits/Big number literals.ts b/packages/examples/en/3-7/Fixits/Big number literals.ts new file mode 100644 index 000000000000..9a08ef282a67 --- /dev/null +++ b/packages/examples/en/3-7/Fixits/Big number literals.ts @@ -0,0 +1,38 @@ +//// { compiler: { ts: "3.7-Beta", target: 99 }, order: 1 } + +// Did you know there is a limit to how big of a number you +// can represent in JavaScript when writing ? + +const maxHighValue = 9007199254740991; +const maxLowValue = -9007199254740991; + +// If you go one over/below these numbers +// then you start to get into dangerous territory + +const oneOverMax = 9007199254740992; +const oneBelowMin = -9007199254740992; + +// The solution for handling numbers of this size +// is to convert these numbers to BigInts instead +// of a number + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt + +// TypeScript will now offer a fixit for number +// literals which are above 2^52 (positive / negative) +// which adds the suffix "n" which informs JavaScript +// that the type should be BigNum + +// Number literals +9007199254740993; +-9007199254740993; +9007199254740994; +-9007199254740994; + +// Hex numbers +0x19999999999999; +-0x19999999999999; +0x20000000000000; +-0x20000000000000; +0x20000000000001; +-0x20000000000001; diff --git a/packages/examples/en/3-7/Fixits/Const to let.ts b/packages/examples/en/3-7/Fixits/Const to let.ts new file mode 100644 index 000000000000..b3ce6243459d --- /dev/null +++ b/packages/examples/en/3-7/Fixits/Const to let.ts @@ -0,0 +1,12 @@ +//// { compiler: { ts: "3.7-Beta" }, order: 1 } + +// New to 3.7 is the ability to quickly convert +// a const variable to a let when the value +// has been re-assigned. + +// You can try this by highlighting the below error +// and choosing to run the quick-fix. + +const displayName = "Andrew" + +displayName = "Andrea" diff --git a/packages/examples/en/3-7/Fixits/Infer From Usage Changes.ts b/packages/examples/en/3-7/Fixits/Infer From Usage Changes.ts new file mode 100644 index 000000000000..51f0ce30662d --- /dev/null +++ b/packages/examples/en/3-7/Fixits/Infer From Usage Changes.ts @@ -0,0 +1,38 @@ +//// { compiler: { ts: "3.7-Beta", noImplicitAny: false }, order: 2 } + +// With 3.7 TypeScript's existing 'infer from usage' +// code fix became smarter. It will now use a list of +// known important types (string, number, array, Promise) +// and infer whether the usage of a type matches the api +// of these objects. + +// For the next few examples, select the parameters of +// the functions, click the light bulb and choose +// "Infer Parameter types..." + +// Infer a number array: + +function pushNumber(arr) { + arr.push(12) +} + +// Infer a promise + +function awaitPromise(promise) { + promise + .then(value => console.log(value)) +} + +// Infer the function, and it's return type + +function inferAny(app) { + const result = app.use('hi') + return result +} + +// Infer a string array because a string +// was added to it + +function insertString(names) { + names[1] = "hello" +} diff --git a/packages/examples/en/3-7/Syntax and Messaging/Flattened Error Reporting.ts b/packages/examples/en/3-7/Syntax and Messaging/Flattened Error Reporting.ts new file mode 100644 index 000000000000..d908d7cddb74 --- /dev/null +++ b/packages/examples/en/3-7/Syntax and Messaging/Flattened Error Reporting.ts @@ -0,0 +1,66 @@ +//// { compiler: { ts: "3.7-Beta" }, order: 3 } + +// TypeScript's error messages can sometimes be a tad verbose... +// With 3.7, we've taken a few cases which could be particularly +// egregious. + + +// Nested Properties + +let a = { b: { c: { d: { e: "string" } } } }; +let b = { b: { c: { d: { e: 12 } } } }; + +a = b; + +// Before, it was 2 lines of code per nested property, which +// quickly meant people learned to read error messages by +// reading the first and then last line of an error message. + +// Now they're inline. :tada: + +// Previously in 3.6: +// +// Type '{ b: { c: { d: { e: number; }; }; }; }' is not assignable to type '{ b: { c: { d: { e: string; }; }; }; }'. +// Types of property 'b' are incompatible. +// Type '{ c: { d: { e: number; }; }; }' is not assignable to type '{ c: { d: { e: string; }; }; }'. +// Types of property 'c' are incompatible. +// Type '{ d: { e: number; }; }' is not assignable to type '{ d: { e: string; }; }'. +// Types of property 'd' are incompatible. +// Type '{ e: number; }' is not assignable to type '{ e: string; }'. +// Types of property 'e' are incompatible. +// Type 'number' is not assignable to type 'string' + + +// This can handle working through different types of objects, +// to still give a useful and concise error message. + +class ExampleClass { + state = "ok" +} + +class OtherClass { + state = 12; +} + +let x = { a: { b: { c: { d: { e: { f: ExampleClass } } } } } }; +let y = { a: { b: { c: { d: { e: { f: OtherClass } } } } } }; +x = y; + +// Previously in 3.6: +// +// Type '{ a: { b: { c: { d: { e: { f: typeof OtherClass; }; }; }; }; }; }' is not assignable to type '{ a: { b: { c: { d: { e: { f: typeof ExampleClass; }; }; }; }; }; }'. +// Types of property 'a' are incompatible. +// Type '{ b: { c: { d: { e: { f: typeof OtherClass; }; }; }; }; }' is not assignable to type '{ b: { c: { d: { e: { f: typeof ExampleClass; }; }; }; }; }'. +// Types of property 'b' are incompatible. +// Type '{ c: { d: { e: { f: typeof OtherClass; }; }; }; }' is not assignable to type '{ c: { d: { e: { f: typeof ExampleClass; }; }; }; }'. +// Types of property 'c' are incompatible. +// Type '{ d: { e: { f: typeof OtherClass; }; }; }' is not assignable to type '{ d: { e: { f: typeof ExampleClass; }; }; }'. +// Types of property 'd' are incompatible. +// Type '{ e: { f: typeof OtherClass; }; }' is not assignable to type '{ e: { f: typeof ExampleClass; }; }'. +// Types of property 'e' are incompatible. +// Type '{ f: typeof OtherClass; }' is not assignable to type '{ f: typeof ExampleClass; }'. +// Types of property 'f' are incompatible. +// Type 'typeof OtherClass' is not assignable to type 'typeof ExampleClass'. +// Type 'OtherClass' is not assignable to type 'ExampleClass'. +// Types of property 'state' are incompatible. +// Type 'number' is not assignable to type 'string' diff --git a/packages/examples/en/3-7/Syntax and Messaging/Nullish Coalescing.ts b/packages/examples/en/3-7/Syntax and Messaging/Nullish Coalescing.ts new file mode 100644 index 000000000000..c5e57fc4db26 --- /dev/null +++ b/packages/examples/en/3-7/Syntax and Messaging/Nullish Coalescing.ts @@ -0,0 +1,42 @@ +//// { compiler: { ts: "3.7-Beta" }, order: 2 } + +// The nullish coalescing operator is an alternative to || +// which returns the right-side expression if the left-side +// is null or undefined. + +// In contrast, || uses falsy checks, meaning an empty +// string or the number 0 would be considered false. + +// A good example for this feature is dealing with partial +// objects which have defaults a key isn't passed in. + +interface AppConfiguration { + // Default: "(no name)"; empty string IS valid + name: string; + + // Default: -1; 0 is valid + items: number; + + // Default: true + active: boolean; +} + +function updateApp(config: Partial) { + // With null-coalescing operator + config.name = config.name ?? "(no name)"; + config.items = config.items ?? -1; + config.active = config.active ?? true; + + // Current solution + config.name = typeof config.name === "string" ? config.name : "(no name)"; + config.items = typeof config.items === "number" ? config.items : -1; + config.active = typeof config.active === "boolean" ? config.active : true; + + // Using || operator which could give bad data + config.name = config.name || "(no name)"; // does not allow for "" input + config.items = config.items || -1; // does not allow for 0 input + config.active = config.active || true; // really bad, always true +} + +// You can read more about nullish coalescing in the 3.7 blog post: +// https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/ diff --git a/packages/examples/en/3-7/Syntax and Messaging/Optional Chaining.ts b/packages/examples/en/3-7/Syntax and Messaging/Optional Chaining.ts new file mode 100644 index 000000000000..3d1287cb8e0f --- /dev/null +++ b/packages/examples/en/3-7/Syntax and Messaging/Optional Chaining.ts @@ -0,0 +1,69 @@ +//// { compiler: { ts: "3.7-Beta" }, order: 1 } + +// Optional chaining reached TC39 Stage 3 consensus during +// 3.7's development. Optional Chaining allows you to write +// code which can immediately stop running expressions when +// it hits a null or undefined. + +// Property Access + +// Lets imagine we have an album where the artist, and the +// artists bio might not be present in the data. For example +// a compilation may not have a single artist. + +type AlbumAPIResponse = { + title: string + artist?: { + name: string + bio?: string + previousAlbums?: string[] + } +}; + +declare const album: AlbumAPIResponse; + +// With optional chaining, you can write +// code like this: + +const artistBio = album?.artist?.bio; + +// Instead of: + +const maybeArtistBio = album.artist && album.artist.bio; + +// In this case ?. acts differently than the &&s since && +// will act different on "falsy" values (e.g. ab empty string, +// 0, NaN, and, well, false). + +// Optional chaining will only take use null or undefined as +// a signal to stop and return an undefined. + +// Optional Element Access + +// Property access is via the . operator, the optional chaining +// also works with the [] operators when accessing elements. + +const maybeArtistBioElement = album?.["artist"]?.["bio"]; + +const maybeFirstPreviousAlbum = album?.artist?.previousAlbums?.[0]; + +// Optional Calls + +// When dealing with functions which may or may not exist at +// runtime, optional chaining supports only calling a function +// if it exists. This can replace code where you would traditionally +// write something like: if (func) func() + +// For example here's an optional call to the callback from +// an API request: + +const callUpdateMetadata = (metadata: any) => Promise.resolve(metadata); // Fake API call + +const updateAlbumMetadata = async (metadata: any, callback?: () => void) => { + await callUpdateMetadata(metadata); + + callback?.(); +}; + +// You can read more about optional chaining in the 3.7 blog post: +// https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/ diff --git a/packages/examples/en/3-7/Types and Code Flow/Assertion Functions.ts b/packages/examples/en/3-7/Types and Code Flow/Assertion Functions.ts new file mode 100644 index 000000000000..f7da56d3b2a8 --- /dev/null +++ b/packages/examples/en/3-7/Types and Code Flow/Assertion Functions.ts @@ -0,0 +1,69 @@ +//// { compiler: { ts: "3.7-Beta" }, order: 1 } + +// Given JavaScripts flexibility, it can be a good idea to add +// runtime checks to your code to validate your assumptions. + +// These are typically called assertions (or invariants) and +// they are small functions which raise errors early when +// your variables doesn't match up to what you expect. + +// Node comes with a function for doing this out of the box, +// it's called assert and it's available without an import. + +// We're going to define our own though, this declares a +// function which asserts that the expression called +// value is true: +declare function assert(value: unknown): asserts value; + +// Now we're use it to validate the type of an enum +declare const maybeStringOrNumber: string | number +assert(typeof maybeStringOrNumber === "string") + +// With TypeScript 3.7, the code flow analysis can use these +// types of functions to figure out what the code is. So, +// when you hover over the variable below - you can see that +// it has been narrowed from a string or number to +// just a string. + +maybeStringOrNumber + +// You can use assertion functions to make guarantees of +// your types throughout your inferred code, for example +// TypeScript knows that this function will return a +// number without the need to add types to the parameter +// via the above assert declaration + +function multiply(x: any, y: any) { + assert(typeof x === "number"); + assert(typeof y === "number"); + + return x * y; +} + +// Assertion functions are siblings to Type Guards +// example:type-guards except they affect the control flow +// when it continues through the function. + +// For example, we can use assertion functions to narrow +// an enum down over time: + +const oneOfFirstFiveNumbers = 1 | 2 | 3 | 4 | 5 + +declare function isOdd(param: unknown): asserts param is 1 | 3 | 5 +declare function isBelowFour(param: unknown): asserts param is 1 | 2 | 3 | 4 + +// This should cut down the enum to 1 | 3 | 5 + +isOdd(oneOfFirstFiveNumbers) +oneOfFirstFiveNumbers + +// This will then cut the enum's possible states to: 1 | 3 + +isBelowFour(oneOfFirstFiveNumbers) +oneOfFirstFiveNumbers + +// This is a primer on some of the features of assertion functions +// in TypeScript 3.7 - you can find out more by reading the +// beta release notes: + +// https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/ diff --git a/packages/examples/en/3-7/Types and Code Flow/Recursive Type References.ts b/packages/examples/en/3-7/Types and Code Flow/Recursive Type References.ts new file mode 100644 index 000000000000..4a4e7ac8d977 --- /dev/null +++ b/packages/examples/en/3-7/Types and Code Flow/Recursive Type References.ts @@ -0,0 +1,39 @@ +//// { compiler: { ts: "3.7-Beta" }, order: 2 } + +// Choosing between using type vs interface is about the +// constraints in the features for each. With 3.7, one of +// the constrains on type but not in interface was removed. + +// You can find out more about this in example:types-vs-interfaces + +// It used to be that you could not refer to the type you +// are defining inside the type itself. This was a limit +// which didn't exist inside an interface, and could be worked +// around with a little work. + +// For example, this is not feasible in 3.6: +type ValueOrArray = T | Array>; + +// An implementation would have looked like this, by mixing +// the type with an interface. +type ValueOrArray2 = T | ArrayOfValueOrArray; +interface ArrayOfValueOrArray extends Array> {} + +// This allows for a comprehensive definition of JSON, +// which works by referring to itself. + +type Json = string | number | boolean | null | Json[] | { [key: string]: Json }; + +const exampleStatusJSON: Json = { + available: true, + username: "Jean-loup", + room: { + name: "Highcrest", + // Cannot add functions into the Json type + // update: () => {} + } +} + +// There's more to learn from the 3.7 beta release notes and its PR: +// https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/ +// https://github.com/microsoft/TypeScript/pull/33050 diff --git a/packages/examples/en/3-7/Types and Code Flow/Uncalled Function Checks.ts b/packages/examples/en/3-7/Types and Code Flow/Uncalled Function Checks.ts new file mode 100644 index 000000000000..6b5efd9a119b --- /dev/null +++ b/packages/examples/en/3-7/Types and Code Flow/Uncalled Function Checks.ts @@ -0,0 +1,31 @@ +//// { compiler: { ts: "3.7-Beta" }, order: 1 } + +// New to 3.7 is a check inside if statements for +// when you accidentally use a function instead +// of the return value of a function. + +// This only applies when the function is known +// to exist making the if statement always be true + +// Here is an example plugin interface, where there +// are optional and non-optional callbacks. + +interface PluginSettings { + pluginShouldLoad?: () => void + pluginActivate: () => void +} + +declare const plugin: PluginSettings + +// Because pluginShouldLoad could not exist, then +// the check is legitimate + +if (plugin.pluginShouldLoad) { + plugin.pluginShouldLoad() +} + +// In 3.6 and below, this was not an error + +if (plugin.pluginActivate) { + plugin.pluginActivate() +} diff --git a/packages/examples/en/JavaScript/External APIs/TypeScript with Deno.ts b/packages/examples/en/JavaScript/External APIs/TypeScript with Deno.ts new file mode 100644 index 000000000000..5bbbf33a410a --- /dev/null +++ b/packages/examples/en/JavaScript/External APIs/TypeScript with Deno.ts @@ -0,0 +1,36 @@ +//// { order: 3 } + +// Deno is a work-in-progress JavaScript and TypeScript +// runtime based on v8 with a focus on security. + +// https://deno.land + +// Deno has a sandbox-based permissions system which reduces the +// access JavaScript has to the file-system or the network and uses +// http based imports which are downloaded and cached locally. + +// Here is an example of using deno for scripting: + +import compose from "https://deno.land/x/denofun/lib/compose.ts"; + +function greet(name: string) { + return `Hello, ${name}!`; +} + +function makeLoud(x: string) { + return x.toUpperCase(); +} + +const greetLoudly = compose( + makeLoud, + greet +); + +// Echos "HELLO, WORLD!." +greetLoudly("world"); + + +import concat from "https://deno.land/x/denofun/lib/concat.ts"; + +// Returns "helloworld" +concat("hello", "world"); diff --git a/packages/examples/en/JavaScript/External APIs/TypeScript with Node.js b/packages/examples/en/JavaScript/External APIs/TypeScript with Node.js new file mode 100644 index 000000000000..b67a6ce20c0f --- /dev/null +++ b/packages/examples/en/JavaScript/External APIs/TypeScript with Node.js @@ -0,0 +1,52 @@ +//// { order: 3, isJavaScript: true } + +// Node.js is a very popular JavaScript runtime built on v8, +// the JavaScript engine which powers Chrome. You can use it +// to build servers, front-end clients and anything in-between. + +// https://nodejs.org/en/ + +// Node comes with a set of core libraries which extend the +// JavaScript runtime, they range from path handling: + +import { join } from "path"; +const myPath = join("~", "downloads", "todo_list.json"); + +// To file manipulation: + +import { readFileSync } from "fs"; +const todoListText = readFileSync(myPath, "utf8"); + +// You can incrementally add types to your JavaScript projects +// using JSDoc-style type. We'll make one for our TODO list item +// based on the JSON structure: + +/** + * @typedef {Object} TODO a TODO item + * @property {string} title The display name for the TODO item + * @property {string} body The name used to show the user + * @property {boolean} done The name used to show the user + */ + +// Now assign that to the return value of JSON.parse +// to learn more about this, see: example:jsdoc-support + +/** @type {TODO[]} a list of TODOs */ +const todoList = JSON.parse(todoListText); + +// And process handling: +import { spawnSync } from "child_process"; +todoList + .filter(todo => !todo.done) + .forEach(todo => { + // Use the ghi client to create an issue for every todo + // list item which hasn't been completed yet. + + // Note that you get correct auto-complete and + // docs in JS when you highlight 'todo.title' below + spawnSync(`ghi open --message "${todo.title}\n${todo.body}" `); + }); + +// TypeScript has up-to-date type definitions for all of the +// built in modules via DefinitelyTyped - which means you +// can write node programs with strong type coverage. diff --git a/packages/examples/en/JavaScript/External APIs/TypeScript with React.tsx b/packages/examples/en/JavaScript/External APIs/TypeScript with React.tsx new file mode 100644 index 000000000000..3a8ac5ed8f4b --- /dev/null +++ b/packages/examples/en/JavaScript/External APIs/TypeScript with React.tsx @@ -0,0 +1,135 @@ +//// { order: 2, compiler: { jsx: 2, esModuleInterop: true } } + +// React is a popular library for creating user interfaces. +// It provides a JavaScript abstraction for creating view +// components using a JavaScript language extension called +// JSX. + +// TypeScript supports JSX, and provides a rich set of +// type tools to richly model how components connect. + +// To understand how TypeScript works with React components +// you may want a primer on generics: + +// - example:generic-functions +// - example:generic-classes + +// First we'll look at how generic interfaces are used to map +// React components. This is a faux-React functional component: + +type FauxactFunctionComponent = + (props: Props, context?: any) => FauxactFunctionComponent | null | JSX.Element + + +// Roughly: +// +// FauxactFunctionComponent is a generic function which relies on +// another type Props. Props has to be an object (to make sure +// you don't pass a primitive) and the Props type will be +// re-used as the first argument in the function. + +// To use it, you need a props type: + +interface DateProps { iso8601Date: string, message: string } + +// We can then create a DateComponent which uses the +// DateProp interface, and renders the date. + +const DateComponent: FauxactFunctionComponent = + (props) => + +// This creates a function which is generic with a Props +// variable which has to be an object. The component function +// returns either another component function or null. + + +// The other component API is a class-based one, here's a +// simplified version of that API: + +interface FauxactClassComponent { + props: Props + state: State + + setState: (prevState: State, props: Props) => Props + callback?: () => void + render(): FauxactClassComponent | null + +} + +// Because this class can have both Props and State - it has +// two generic arguments which are used throughout the class. + +// The React library comes with it's own type definitions +// like these but are much more comprehensive. Let's bring +// those into our playground and explore a few components. + +import React from 'react'; + +// Your props are your public API, so it's worth taking the +// time to use JSDoc to explain how it works: + +export interface Props { + /** The user's name */ + name: string; + /** Should the name be rendered in bold */ + priority?: boolean +} + +const PrintName: React.FC = (props) => { + return ( +
+

OK

+
+ ) +} + +// You can play with the new component's usage below: + +const ShowUser: React.FC = (props) => { + return +} + +// TypeScript supports providing intellisense inside +// the {} in an attribute + +let username = "Cersei" +const ShowStoredUser: React.FC = (props) => { + return +} + +// TypeScript works with modern React code too, here you can +// see that count and setCount have correctly been inferred +// to use numbers based on the initial value passed into +// useState. + +import { useState, useEffect } from 'react'; + +const CounterExample = () => { + const [count, setCount] = useState(0); + + useEffect(() => { + document.title = `You clicked ${count} times`; + }); + + return ( +
+

You clicked {count} times

+ +
+ ); +} + +// React and TypeScript is a really, really big topic +// but the fundamentals are pretty small: TypeScript +// supports JSX, and the rest is handled by the React +// typings from definitely typed. + +// You can learn more about using React with TypeScript +// from these sites: + +// https://github.com/typescript-cheatsheets/react-typescript-cheatsheet +// https://egghead.io/courses/use-typescript-to-develop-react-applications +// https://levelup.gitconnected.com/ultimate-react-component-patterns-with-typescript-2-8-82990c516935 + diff --git a/packages/examples/en/JavaScript/External APIs/TypeScript with Web.js b/packages/examples/en/JavaScript/External APIs/TypeScript with Web.js new file mode 100644 index 000000000000..3e1571fefb7f --- /dev/null +++ b/packages/examples/en/JavaScript/External APIs/TypeScript with Web.js @@ -0,0 +1,79 @@ +//// { order: 0, isJavaScript: true } + +// The DOM (Document Object Model) is the underlying API for +// working with a webpage, and TypeScript has great support +// for that API. + +// Let's create a popover to show when you press run in +// the toolbar above. + +const popover = document.createElement("div"); +popover.id = "example-popover"; + +// Note that popover is correctly typed to be a HTMLDivElement +// specifically because we passed in "div". + +// To make it possible to re-run this code, we'll first +// add a function to remove the popover it it was already there. + +const removePopover = () => { + const existingPopover = document.getElementById(popover.id); + if (existingPopover && existingPopover.parentElement) { + existingPopover.parentElement.removeChild(existingPopover); + } +}; + +// Then call it right away + +removePopover(); + +// We can set the inline styles on the element via the +// .style property on a HTMLElement - this is fully typed + +popover.style.backgroundColor = "#0078D4"; +popover.style.color = "white"; +popover.style.border = "1px solid black"; +popover.style.position = "fixed"; +popover.style.bottom = "10px"; +popover.style.right = "20px"; +popover.style.width = "200px"; +popover.style.height = "100px"; +popover.style.padding = "10px"; + +// Including more obscure, or deprecated CSS attributes +popover.style.webkitBorderRadius = "4px"; + +// To add content to the popover, we'll need to add +// a paragraph element and use it to add some text + +const message = document.createElement("p"); +message.textContent = "Here is an example popover"; + +// And we'll also add a close button + +const closeButton = document.createElement("a"); +closeButton.textContent = "X"; +closeButton.style.position = "absolute"; +closeButton.style.top = "3px"; +closeButton.style.right = "8px"; +closeButton.style.color = "white"; + +closeButton.onclick = () => { + removePopover(); +}; + +// Then add all of these elements on to the page +popover.appendChild(message); +popover.appendChild(closeButton); +document.body.appendChild(popover); + +// If you hit "Run" above, then a popup should appear +// in the bottom left, which you can close by clicking +// on the x in the top right of the popup. + +// This example shows how you can work with the DOM API +// in JavaScript - but using TypeScript to provide great +// tooling support. + +// There is an extended example for TypeScript tooling with +// WebGL available here: example:typescript---webgl diff --git a/packages/examples/en/JavaScript/External APIs/TypeScript with WebGL.js b/packages/examples/en/JavaScript/External APIs/TypeScript with WebGL.js new file mode 100644 index 000000000000..0e83d35645a3 --- /dev/null +++ b/packages/examples/en/JavaScript/External APIs/TypeScript with WebGL.js @@ -0,0 +1,281 @@ +//// { order: 5, isJavaScript: true } + +// This example create a HTML canvas which uses WebGL to +// render spinning confetti using JavaScript. We're going +// to walk through the code to understand how it works, and +// see how TypeScript's tooling provides useful insight. + +// This example builds off: example:working-with-the-dom + +// First up, we need to create a HTML canvas element, which +// we do via the DOM API and set some inline style attributes + +const canvas = document.createElement("canvas") +canvas.id = "spinning-canvas" +canvas.style.backgroundColor = "#0078D4" +canvas.style.position = "fixed" +canvas.style.bottom = "10px" +canvas.style.right = "20px" +canvas.style.width = "500px" +canvas.style.height = "400px" + +// Next to make it easy to make changes, we remove any older +// versions of the canvas when hitting run - now you can make +// changes and see them reflected when you press run or +// (cmd + enter) + +const existingCanvas = document.getElementById(canvas.id) +if (existingCanvas && existingCanvas.parentElement) { + existingCanvas.parentElement.removeChild(existingCanvas) +} + +// Tell the canvas element that we will use webgl to draw +// inside the element (and not the default raster engine) + +const gl = canvas.getContext("webgl") + +// Next we need to create vertex shaders - these roughly are +// small programs that apply maths to a set of incoming +// array of vertices (numbers) + +// You can see the large set of attributes at the top of the shader, +// these are passed into the compiled shader further down the example. + +// There's a great overview on how they work here: +// https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html + +const vertexShader = gl.createShader(gl.VERTEX_SHADER) +gl.shaderSource( + vertexShader, + ` +precision lowp float; + +attribute vec2 a_position; // Flat square on XY plane +attribute float a_startAngle; +attribute float a_angularVelocity; +attribute float a_rotationAxisAngle; +attribute float a_particleDistance; +attribute float a_particleAngle; +attribute float a_particleY; +uniform float u_time; // Global state + +varying vec2 v_position; +varying vec3 v_color; +varying float v_overlight; + +void main() { + float angle = a_startAngle + a_angularVelocity * u_time; + float vertPosition = 1.1 - mod(u_time * .25 + a_particleY, 2.2); + float viewAngle = a_particleAngle + mod(u_time * .25, 6.28); + + mat4 vMatrix = mat4( + 1.3, 0.0, 0.0, 0.0, + 0.0, 1.3, 0.0, 0.0, + 0.0, 0.0, 1.0, 1.0, + 0.0, 0.0, 0.0, 1.0 + ); + + mat4 shiftMatrix = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + a_particleDistance * sin(viewAngle), vertPosition, a_particleDistance * cos(viewAngle), 1.0 + ); + + mat4 pMatrix = mat4( + cos(a_rotationAxisAngle), sin(a_rotationAxisAngle), 0.0, 0.0, + -sin(a_rotationAxisAngle), cos(a_rotationAxisAngle), 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ) * mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, cos(angle), sin(angle), 0.0, + 0.0, -sin(angle), cos(angle), 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + + gl_Position = vMatrix * shiftMatrix * pMatrix * vec4(a_position * 0.03, 0.0, 1.0); + vec4 normal = vec4(0.0, 0.0, 1.0, 0.0); + vec4 transformedNormal = normalize(pMatrix * normal); + + float dotNormal = abs(dot(normal.xyz, transformedNormal.xyz)); + float regularLighting = dotNormal / 2.0 + 0.5; + float glanceLighting = smoothstep(0.92, 0.98, dotNormal); + v_color = vec3( + mix((0.5 - transformedNormal.z / 2.0) * regularLighting, 1.0, glanceLighting), + mix(0.5 * regularLighting, 1.0, glanceLighting), + mix((0.5 + transformedNormal.z / 2.0) * regularLighting, 1.0, glanceLighting) + ); + + v_position = a_position; + v_overlight = 0.9 + glanceLighting * 0.1; +} +` +) +gl.compileShader(vertexShader) + +// This example also uses fragment shaders - a fragment +// shader is another small program that runs through every +// pixel in the canvas and set its color. + +// In this case, if you play around with the numbers you can see how +// this affects the lighting in the scene, as well as the border +// radius on the confetti: + +const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) +gl.shaderSource( + fragmentShader, + ` +precision lowp float; +varying vec2 v_position; +varying vec3 v_color; +varying float v_overlight; + +void main() { + gl_FragColor = vec4(v_color, 1.0 - smoothstep(0.8, v_overlight, length(v_position))); +} +` +) +gl.compileShader(fragmentShader) + +// Takes the compiled shaders and adds them to the canvas's +// WebGL context so that can be used. + +const shaderProgram = gl.createProgram() +gl.attachShader(shaderProgram, vertexShader) +gl.attachShader(shaderProgram, fragmentShader) +gl.linkProgram(shaderProgram) +gl.useProgram(shaderProgram) + +gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()) + +// We need to get/set the input variables into the shader in a +// memory-safe way, so the order and the length of their +// values needs to be stored. + +const attrs = [ + { name: "a_position", length: 2, offset: 0 }, // e.g. x and y represent 2 spaces in memory + { name: "a_startAngle", length: 1, offset: 2 }, // but angle is just 1 value + { name: "a_angularVelocity", length: 1, offset: 3 }, + { name: "a_rotationAxisAngle", length: 1, offset: 4 }, + { name: "a_particleDistance", length: 1, offset: 5 }, + { name: "a_particleAngle", length: 1, offset: 6 }, + { name: "a_particleY", length: 1, offset: 7 } +] + +const STRIDE = Object.keys(attrs).length + 1 + +// Loop through our known attributes and create pointers in memory for the JS side +// to be able to fill into the shader. + +// To understand this API a little bit: WebGL is based on OpenGL +// which is a state-machine styled API. You pass in commands in a +// particular order to render things to the screen. + +// So, the intended usage is often not passing objects to every WebGL +// API call, but instead passing one thing to one function, then passing +// another to the next. So, here we prime webGL to create an array of +// vertex pointers + +for (var i = 0; i < attrs.length; i++) { + const name = attrs[i].name + const length = attrs[i].length + const offset = attrs[i].offset + const attribLocation = gl.getAttribLocation(shaderProgram, name) + gl.vertexAttribPointer(attribLocation, length, gl.FLOAT, false, STRIDE * 4, offset * 4) + gl.enableVertexAttribArray(attribLocation) +} + +// Then on this line they are bound to an array in memory. + +gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()) + +// Set up some constants for rendering + +const NUM_PARTICLES = 200 +const NUM_VERTICES = 4 + +// Try reducing this one and hitting run again, +// it represents how many points should exist on +// each confetti and having an odd number sends +// it way out of whack + +const NUM_INDICES = 6 + +// Create the arrays of inputs for the vertex shaders +const vertices = new Float32Array(NUM_PARTICLES * STRIDE * NUM_VERTICES) +const indices = new Uint16Array(NUM_PARTICLES * NUM_INDICES) + +for (let i = 0; i < NUM_PARTICLES; i++) { + const axisAngle = Math.random() * Math.PI * 2 + const startAngle = Math.random() * Math.PI * 2 + const groupPtr = i * STRIDE * NUM_VERTICES + + const particleDistance = Math.sqrt(Math.random()) + const particleAngle = Math.random() * Math.PI * 2 + const particleY = Math.random() * 2.2 + const angularVelocity = Math.random() * 2 + 1 + + for (let j = 0; j < 4; j++) { + const vertexPtr = groupPtr + j * STRIDE + vertices[vertexPtr + 2] = startAngle // Start angle + vertices[vertexPtr + 3] = angularVelocity // Angular velocity + vertices[vertexPtr + 4] = axisAngle // Angle diff + vertices[vertexPtr + 5] = particleDistance // Distance of the particle from the (0,0,0) + vertices[vertexPtr + 6] = particleAngle // Angle around Y axis + vertices[vertexPtr + 7] = particleY // Angle around Y axis + } + + // Coordinates + vertices[groupPtr] = vertices[groupPtr + STRIDE * 2] = -1 + vertices[groupPtr + STRIDE] = vertices[groupPtr + STRIDE * 3] = +1 + vertices[groupPtr + 1] = vertices[groupPtr + STRIDE + 1] = -1 + vertices[groupPtr + STRIDE * 2 + 1] = vertices[groupPtr + STRIDE * 3 + 1] = +1 + + const indicesPtr = i * NUM_INDICES + const vertexPtr = i * NUM_VERTICES + indices[indicesPtr] = vertexPtr + indices[indicesPtr + 4] = indices[indicesPtr + 1] = vertexPtr + 1 + indices[indicesPtr + 3] = indices[indicesPtr + 2] = vertexPtr + 2 + indices[indicesPtr + 5] = vertexPtr + 3 +} + +// Pass in the data to the WebGL context +gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) +gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) + + +const timeUniformLocation = gl.getUniformLocation(shaderProgram, "u_time") +const startTime = (window.performance || Date).now() + +// Start the background colour as black +gl.clearColor(0, 0, 0, 1) + +// Allow alpha channels on in the vertex shader +gl.enable(gl.BLEND) +gl.blendFunc(gl.SRC_ALPHA, gl.ONE) + +// Set the WebGL context to be the full size of the canvas +gl.viewport(0, 0, canvas.width, canvas.height) + +// Create a run-loop to draw all of the confetti +;(function frame() { + gl.uniform1f(timeUniformLocation, ((window.performance || Date).now() - startTime) / 1000) + + gl.clear(gl.COLOR_BUFFER_BIT) + gl.drawElements( + gl.TRIANGLES, + NUM_INDICES * NUM_PARTICLES, + gl.UNSIGNED_SHORT, + 0 + ) + requestAnimationFrame(frame) +})() + +// Add the new canvas element into the bottom left +// of the playground +document.body.appendChild(canvas) + +// Credit: based on this JSFiddle by Subzey +// https://jsfiddle.net/subzey/52sowezj/ + diff --git a/packages/examples/en/JavaScript/Functions with JavaScript/Function Chaining.ts b/packages/examples/en/JavaScript/Functions with JavaScript/Function Chaining.ts new file mode 100644 index 000000000000..f76b6dee4e22 --- /dev/null +++ b/packages/examples/en/JavaScript/Functions with JavaScript/Function Chaining.ts @@ -0,0 +1,89 @@ +//// { order: 2, compiler: { esModuleInterop: true } } + + +// Function chaining APIs are a common pattern in +// JavaScript, which can make your code focused +// with less intermediary values and easier to read +// because of their nesting qualities. + +// A really common API which works via chaining +// is jQuery. Here is an example of jQuery +// being used with the types from DefinitelyTyped + +import $ from "jquery"; + +// Here's an example use of the jQuery API: + +$("#navigation") + .css("background", "red") + .height(300) + .fadeIn(200); + +// If you add a dot on the line above, you'll see +// a long list of functions. This pattern is easy to +// reproduce in JavaScript. The key is to make sure +// you always return the same object. + +// Here is an example API which creates a chaining +// API. The key is to have an outer function which +// keeps track of internal state, and an object which +// exposes the API that is always returned. + +const addTwoNumbers = (start = 1) => { + let n = start; + + const api = { + // Implement each function in your API + add(inc: number = 1) { + n += inc; + return api; + }, + + print() { + console.log(n); + return api; + } + }; + return api; +}; + +// Which allows the same style of API as we +// saw in jQuery: + +addTwoNumbers(1) + .add(3) + .add() + .print() + .add(1); + +// Here's a similar example which uses a class: + +class AddNumbers { + private n = 0; + + constructor(start = 0) { + this.n = start; + } + + public add(inc = 1) { + this.n = this.n + inc; + return this; + } + + public print() { + console.log(this.n); + return this; + } +} + +// Here it is in action: + +new AddNumbers(2).add(3).add(3); + +// This example as used the TypeScript +// type inference to provide a way to +// provide tooling to JavaScript patterns. + +// For more examples on this: +// +// - example:code-flow diff --git a/packages/examples/en/JavaScript/Functions with JavaScript/Generic Functions.ts b/packages/examples/en/JavaScript/Functions with JavaScript/Generic Functions.ts new file mode 100644 index 000000000000..645b071b908c --- /dev/null +++ b/packages/examples/en/JavaScript/Functions with JavaScript/Generic Functions.ts @@ -0,0 +1,100 @@ +// Generics provide a way to use Types as variables in other +// types. Meta. + +// We'll be trying to keep this example light, you can do +// a lot with generics and it's likely you will see some very +// complicated code using generics at some point - but that +// does not mean that generics are complicated. + +// Let's start with an example where we wrap an input object +// in an array. We will only care about one variable in this +// case, the type which was passed in: + +function wrapInArray(input: Type): Type[] { + return [input]; +} + +// Note: it's common to see Type refereed to as T. This is +// culturally similar to how people use i in a for loop to +// represent index. T normally represents Types, so we'll +// be using the full name for clarity. + +// Our function will use inference to always keep the type +// passed in the same as the type passed out (though +// it will be wrapped in an array.) + +const stringArray = wrapInArray("hello generics"); +const numberArray = wrapInArray(123); + +// We can verify this works as expected by checking +// if we can assign a string array to a function which +// should be an object array +const notStringArray: string[] = wrapInArray({}); + +// You can also skip the generic inference by adding the +// type yourself also: +const stringArray2 = wrapInArray(""); + +// wrapInArray allows any type to be used, however there +// are cases when you need to only allow a subset of types. +// In these cases you can say the type has to extend a +// particular type. + +interface Drawable { + draw: () => void; +} + +// This function takes a set of objects which have a function +// for drawing to the screen +function renderToScreen(input: Type[]) { + input.forEach(i => i.draw()); +} + +const objectsWithDraw = [{ draw: () => {} }, { draw: () => {} }]; +renderToScreen(objectsWithDraw); + +// It will fail if draw is missing: + +renderToScreen([{}, { draw: () => {} }]); + +// Generics can start to look complicated when you have +// multiple variables. Here an example of a caching function +// that lets you have different sets of input types and caches. + +interface CacheHost { + save: (a: any) => void; +} + +function addObjectToCache(obj: Type, cache: Cache): Cache { + cache.save(obj); + return cache; +} + +// This is the same as above, but with an extra parameter. +// Note: to make this work though, we had to use an any. This +// can be worked out by using a generic interface. + +interface CacheHostGeneric { + save: (a: ContentType) => void; +} + +// Now when the CacheHostGeneric is ued, you need to tell it +// what ContentType is. + +function addTypedObjectToCache>(obj: Type, cache: Cache): Cache { + cache.save(obj); + return cache; +} + +// That escalated pretty quickly in terms of syntax. However, +// this provides more safety. These are trade-offs, that you +// have more knowledge to make now. When providing APIs for +// others, generics offer a flexible way to let others use +// their own types with full code inference. + +// For more examples of generics with classes and interfaces +// +// example:advanced-classes +// example:typescript---react +// https://www.typescriptlang.org/docs/handbook/generics.html + diff --git a/packages/examples/en/JavaScript/Functions with JavaScript/Typing Functions.ts b/packages/examples/en/JavaScript/Functions with JavaScript/Typing Functions.ts new file mode 100644 index 000000000000..9385a551961e --- /dev/null +++ b/packages/examples/en/JavaScript/Functions with JavaScript/Typing Functions.ts @@ -0,0 +1,132 @@ +// TypeScript's inference can get you very far, but there +// are lots of extra ways to provide a richer way to document +// the shape of your functions. + +// A good first place is to look at optional params, which +// is a way of letting others know you can skip params. + +let i = 0 +const incrementIndex = (value?: number) => { + i += (value === undefined ? 1 : value) +} + +// This function can be called like: + +incrementIndex() +incrementIndex(0) +incrementIndex(3) + +// You can type parameters as functions, which provides +// type inference when you write the functions + +const callbackWithIndex = (callback: (i: number) => void) => { + callback(i) +} + +// Embedding function interfaces can get a bit hard to read +// with all the arrows. Using a type alias will let you name +// the function param. + +type NumberCallback = (i: number) => void +const callbackWithIndex2 = (callback: NumberCallback) => { + callback(i) +} + +// These can be called like: + +callbackWithIndex((index) => { console.log(index) }) + +// By hovering on index above, you can see how TypeScript +// has inferred the index to be a number correctly. + +// TypeScript inference can work when passing a function +// as an instance reference too. To show this, we'll use +// a function which changed a number into string: + +const numberToString = (n: number) => { return n.toString() } + +// This can be used in a function like map on an array +// to convert all numbers into a string, if you hover +// on stringedNumbers below you can see the expected types. +const stringedNumbers = [1,4,6,10].map((i) => numberToString(i)) + +// We can use shorthand to have the function passed directly +// and get the same results with more focused code: +const stringedNumbersTerse = [1,4,6,10].map(numberToString) + +// You may have functions which could accept a lot of types +// but you are only interested in a few properties. This is +// a useful case for indexed signatures in types. The +// following type declares that this function is OK to use +// any object so long as it includes the property name: + +interface AnyObjectButMustHaveName { + name: string + [key: string]: any +} + +const printFormattedName = (input: AnyObjectButMustHaveName) => { } + +printFormattedName({name: "joey"}) +printFormattedName({name: "joey", age: 23}) + +// If you'd like to learn more about index-signatures +// we recommend: +// +// https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks +// https://basarat.gitbooks.io/typescript/docs/types/index-signatures.html + +// You can also allow this kind of behavior everywhere +// via the tsconfig flag suppressExcessPropertyErrors - +// however, you can't know if others using your API have +// this set to off. + +// Functions in JavaScript can accept different sets of params. +// There are two common patterns for describing these: union +// types for parameters/return, and function overloads. + +// Using union types in your parameters makes sense if there +// is only one or two changes and documentation does not need +// to change between functions. + +const boolOrNumberFunction = (input: boolean | number) => {} + +boolOrNumberFunction(true) +boolOrNumberFunction(23) + +// Function overloads on the other hand offer a much richer +// syntax for the parameters and return types + +interface BoolOrNumberOrStringFunction { + /** Takes a bool, returns a bool */ + (input: boolean): boolean + /** Takes a number, returns a number */ + (input: number): number + /** Takes a string, returns a bool */ + (input: string): boolean +} + +// If this is your first time seeing declare, it allows you +// to tell TypeScript something exists even if it doesn't +// exist in the runtime in this file. Useful for mapping +// code with side-effects but extremely useful for demos +// where making the implementation would be a lot of code. + +declare const boolOrNumberOrStringFunction: BoolOrNumberOrStringFunction + +const boolValue = boolOrNumberOrStringFunction(true) +const numberValue = boolOrNumberOrStringFunction(12) +const boolValue2 = boolOrNumberOrStringFunction("string") + +// If you hover over the above values and functions you +// can see the right documentation and return values. + +// Using function overloads can get you very far, however +// theres another tool for dealing with different types of +// inputs and return values and that is generics. + +// These provide a way for you to have types as placeholder +// variables in type definitions. + +// example:generic-functions +// example:function-chaining diff --git a/packages/examples/en/JavaScript/Helping with JavaScript/Errors.ts b/packages/examples/en/JavaScript/Helping with JavaScript/Errors.ts new file mode 100644 index 000000000000..57c3ec5e8b3c --- /dev/null +++ b/packages/examples/en/JavaScript/Helping with JavaScript/Errors.ts @@ -0,0 +1,38 @@ +//// { order: 3, isJavaScript: true } + +// By default TypeScript doesn't provide error messaging +// inside JavaScript, instead the tooling is focused on +// providing rich support for editors. + +// Turning on errors however, is pretty easy. In a +// typical JS file, all that's required to turn on TypeScript +// error messages is adding the following comment: + +// @ts-check + +let myString = "123"; +myString = {}; + +// This may start to add a lot of red squiggles inside your +// JS file. While still working inside JavaScript, you have +// a few tools to fix these errors. + +// For some of the trickier errors, which you don't feel +// code changes should happen, you can use JSDoc annotations +// to tell TypeScript what the types should be: + +/** @type {string | {}} */ +let myStringOrObject = "123"; +myStringOrObject = {}; + +// Which you can read more on here: example:jsdoc-support + +// You could declare the failure unimportant, by telling +// TypeScript to ignore the next error: + +let myIgnoredError = "123"; +// @ts-ignore +myStringOrObject = {}; + +// You can use type inference via the flow of code to make +// changes to your JavaScript: example:code-flow diff --git a/packages/examples/en/JavaScript/Helping with JavaScript/Quick Fixes.ts b/packages/examples/en/JavaScript/Helping with JavaScript/Quick Fixes.ts new file mode 100644 index 000000000000..f7e7281a858e --- /dev/null +++ b/packages/examples/en/JavaScript/Helping with JavaScript/Quick Fixes.ts @@ -0,0 +1,22 @@ +// Work in Progress - there is a PR which adds quick +// fix support for the tool which powers the playground +// which we need to have merged before this is usable +// with the light-bulb + +// TypeScript provides quick-fix recommendations for +// common accidents, they show up in your editor based +// on recommendations + +// For example TypeScript can provide quick-fixes +// for typoes in your types: + +const eulersNumber = 2.7182818284 +eulersNumber.toStrang(); +// ^_______^ - select this to see the light build + + +class ExampleClass { + method() { + this.notDeclared = 10; + } +} \ No newline at end of file diff --git a/packages/examples/en/JavaScript/JavaScript Essentials/Code Flow.ts b/packages/examples/en/JavaScript/JavaScript Essentials/Code Flow.ts new file mode 100644 index 000000000000..a1529344993c --- /dev/null +++ b/packages/examples/en/JavaScript/JavaScript Essentials/Code Flow.ts @@ -0,0 +1,64 @@ +//// { order: 3, compiler: { strictNullChecks: true } } + +// How code flows inside our JavaScript files can affect +// the types throughout our programs. + +const users = [{ name: "Ahmed" }, { name: "Gemma" }, { name: "Jon" }]; + +// We're going to look to see if we can find a user named "jon". +const jon = users.find(u => u.name === "jon"); + +// In the above case, 'find' could fail. In that case we +// don't have an object. This creates the type +// +// { name: string } | undefined +// +// If you hover your mouse over the three following uses of 'jon' below, +// you'll see how the types change depending on where the word is located: + +if (jon) { + jon; +} else { + jon; +} + +// The type '{ name: string } | undefined' uses a TypeScript +// feature called union types. A union type is a way to +// declare that an object could be one of many things. +// +// The pipe acts as the separator between different types. +// JavaScript's dynamic nature means that lots of functions +// receive and return objects of unrelated types and we need +// to be able to express which ones we might be dealing with. + +// We can use this in a few ways. Let's start by looking at +// an array where the values have different types. + +const identifiers = ["Hello", "World", 24, 19]; + +// We can use the JavaScript 'typeof x === y' syntax to +// check for the type of the first element. You can hover on +// 'randomIdentifier' below to see how it changes between +// different locations + +const randomIdentifier = identifiers[0]; +if (typeof randomIdentifier === "number") { + randomIdentifier; +} else { + randomIdentifier; +} + +// This control flow analysis means that we can write vanilla +// JavaScript and TypeScript will try to understand how the +// code types will change in different locations. + +// To learn more about code flow analysis +// - example:type-guards + +// To continue reading through examples you could jump to a few different +// places now. +// +// - Modern JavaScript: example:immutability +// - Type Guards: example:type-guards +// - Functional Programming with JavaScript example:function-chaining +// diff --git a/packages/examples/en/JavaScript/JavaScript Essentials/Functions.ts b/packages/examples/en/JavaScript/JavaScript Essentials/Functions.ts new file mode 100644 index 000000000000..a657a6e09b00 --- /dev/null +++ b/packages/examples/en/JavaScript/JavaScript Essentials/Functions.ts @@ -0,0 +1,90 @@ +//// { order: 2, compiler: { noImplicitAny: false } } + +// There are quite a few ways to declare a function in +// JavaScript. Let's look at a function which adds two +// numbers together: + +// Creates a function in global scope called addOldSchool +function addOldSchool(x, y) { + return x + y; +} + +// You can move the name of the function to a variable +// name also +const anonymousOldSchoolFunction = function(x, y) { + return x + y; +}; + +// You can also use fat-arrow shorthand for a function +const addFunction = (x, y) => { + return x + y; +}; + +// We're going to focus on the last one, but everything +// applies to all three formats. + +// TypeScript provides additional syntax which adds to a +// function definition and offers hints on what the +// types are expected by this function. +// +// Up next is the most open version of the add function, it +// says that add takes two inputs of any type: this could +// be strings, numbers or objects which you've made. + +const add1 = (x: any, y: any) => { + return x + y; +}; +add1("Hello", 23); + +// This is legitimate JavaScript (strings can be added +// like this for example) but isn't optimal for our function +// which we know is for numbers, so we'll convert the x and +// y to only be numbers + +const add2 = (x: number, y: number) => { + return x + y; +}; +add2(16, 23); +add2("Hello", 23); + +// Great. We get an error when anything other than a number +// is passed in. If you hover over the word add2 above, +// you'll see that TypeScript describes it as: +// +// const add2: (x: number, y: number) => number +// +// Where it has inferred that when the two inputs are +// numbers the only possible return type is a number. +// This is great, you don't have to write extra syntax. +// Let's look at what it takes to do that + +const add3 = (x: number, y: number): string => { + return x + y; +}; + +// This function fails because we told TypeScript that it +// should expect a string to be returned but the function +// didn't live up to that promise. + +const add4 = (x: number, y: number): number => { + return x + y; +}; + +// This is a very explicit version of add2 - there are +// cases when you want to use the explicit return type +// syntax to give yourself a space to work within before +// you get started. A bit like how test-driven development +// recommends starting with a failing test, but in this case +// it's with a failing shape of a function instead. + +// This example is only a primer, you can learn a lot more +// about functions work in TypeScript in the handbook and +// inside the Functional JavaScript section of the examples +// +// https://www.typescriptlang.org/docs/handbook/functions.html +// example:function-chaining + +// And to continue our tour of JavaScript essentials, +// we'll look at how code flow affects the TypeScript types + +// example:code-flow diff --git a/packages/examples/en/JavaScript/JavaScript Essentials/Hello World.ts b/packages/examples/en/JavaScript/JavaScript Essentials/Hello World.ts new file mode 100644 index 000000000000..69c8a295f808 --- /dev/null +++ b/packages/examples/en/JavaScript/JavaScript Essentials/Hello World.ts @@ -0,0 +1,35 @@ +//// { order: 0, compiler: { target: 1 } } + +// Welcome to the TypeScript playground. This site is a lot +// like running a TypeScript project inside a web browser. + +// The playground makes it easy for you to safely experiment +// with ideas in TypeScript by making it trivial to share +// these projects. The URL for this page is everything +// required to load the project for someone else. + +const hello = "Hello" + +// You can see on the right the result of the TypeScript +// compiler: this is vanilla JavaScript which can run on +// browsers, servers or anywhere really. + +const world = "World" + +// You can see how it makes tiny changes to the code, by +// converting a "const" to a "var". This is one of the many +// things TypeScript does to make it possible to run +// anywhere JavaScript runs. + +console.log(hello + " " + world) + +// Now you have an idea of how the playground works, let's +// look at how TypeScript makes working with JavaScript more +// fun. During this section we'll be trying to keep as +// close to vanilla JavaScript as possible to show how you +// can re-use existing knowledge. +// +// Click below to continue: +// +// example:objects-and-arrays + diff --git a/packages/examples/en/JavaScript/JavaScript Essentials/Objects and Arrays.ts b/packages/examples/en/JavaScript/JavaScript Essentials/Objects and Arrays.ts new file mode 100644 index 000000000000..c574ab5e9f82 --- /dev/null +++ b/packages/examples/en/JavaScript/JavaScript Essentials/Objects and Arrays.ts @@ -0,0 +1,113 @@ +//// { order: 1, compiler: { strict: false } } + +// JavaScript objects are collections of values wrapped up +// with named keys. + +const userAccount = { + name: "Kieron", + id: 0 +} + +// You can combine these to make larger, more complex +// data-models. + +const pie = { + type: "Apple" +} + +const purchaseOrder = { + owner: userAccount, + item: pie +} + +// If you use your mouse to hover over some of these words +// (try purchaseOrder above) you can see how TypeScript is +// interpreting your JavaScript into labeled types. + +// Values can be accessed via the ".", so to get a +// username for a purchase order +console.log(purchaseOrder.item.type) + +// If you hover your mouse over each part of the code +// between the ()s, you can see TypeScript offering more +// information about each part. Try re-writing this below: + +// Copy this in the next line, character by character: +// +// purchaseOrder.item.type + + + +// TypeScript provides feedback to the Playground +// about what JavaScript objects are available in this +// file and lets you avoid typoes and see additional +// information without having to look it up in another place. + +// TypeScript also offers these same features to arrays. +// Here's an array with just our purchase order above in it. + +const allOrders = [purchaseOrder] + +// If you hover on allOrders, you can tell it's an array +// because the hover info ends with []. You can access the +// first order by using square brackets with an index +// (starting from zero) + +const firstOrder = allOrders[0] +console.log(firstOrder.item.type) + +// An alternative was to get an object is via pop-ing the +// array to remove objects. Doing this removes the object +// from the array, and returns the object. This is called +// mutating the array, because it changes the underlying +// data inside it. + +const poppedFirstOrder = allOrders.pop() + +// Now allOrders is empty. Mutating data can be useful for +// many things, but one way to reduce the complexity in your +// codebases is to avoid mutation. TypeScript offers a way +// to declare an array readonly instead: + +// Creates a type based on the shape of a purchase order +type PurchaseOrder = typeof purchaseOrder + +// Creates a readonly array of purchase orders +const readonlyOrders: readonly PurchaseOrder[] = [purchaseOrder] + +// Yep! That's a bit more code for sure. There's four +// new things here: +// +// type PurchaseOrder - Declares a new type to TypeScript +// +// typeof - Use the type inference system to set the type +// based on the const which is passed in next. +// +// purchaseOrder - Get the variable purchaseOrder and tell +// TypeScript this is the shape of all +// objects in the orders array. +// +// readonly - This object does not support mutation, once +// it is created then the contents of the array +// will always stay the same. +// +// Now if you try to pop from the readonlyOrders, TypeScript +// will raise an error. + +readonlyOrders.pop() + +// You can use readonly in all sorts of places, it's a +// little bit of extra syntax here and there, but it +// provides a lot of extra safety. + +// You can find out more about readonly: +// - [handbook link] +// - https://basarat.gitbooks.io/typescript/content/docs/types/readonly.html + +// And you can carry on learning about JavaScript and +// TypeScript in the example on functions. +// +// example:functions +// +// or if you want to know more about immutability +// example:immutability diff --git a/packages/examples/en/JavaScript/Modern JavaScript/Async Await.ts b/packages/examples/en/JavaScript/Modern JavaScript/Async Await.ts new file mode 100644 index 000000000000..dc70d536bbc5 --- /dev/null +++ b/packages/examples/en/JavaScript/Modern JavaScript/Async Await.ts @@ -0,0 +1,109 @@ +//// { order: 1, target: "es5" } + +// Modern JavaScript added a way to handle callbacks in an +// elegant way by adding a Promise based API which has special +// syntax which lets you treat asynchronous code as though it +// acts synchronous. + +// Like all language features, this is a trade-off in +// complexity: making a function async means your return +// values are wrapped in Promises. What used to return a +// string, now returns a Promise + +const func = () => ":wave:" +const asyncFunc = async () => ":wave:" + +const myString = func() +const myPromiseString = asyncFunc() + +myString.length + +// myPromiseString is a Promise, not the string: + +myPromiseString.length + + +// You can use the await keyword to convert a promise +// into its value. Today, these only work inside an async +// function. + +const myWrapperFunction = async () => { + const myString = func() + const myResolvedPromiseString = await asyncFunc() + + // Via the await keyword, now myResolvedPromiseString + // is a string + myString.length + myResolvedPromiseString.length +} + +// Code which is running via an await can throw errors, +// and it's important to catch those errors somewhere. + +const myThrowingFunction = async () => { + throw new Error("Do not call this") +} + +// We can wrap calling an async function in a try catch to +// handle cases where the function acts unexpectedly. + +const asyncFunctionCatching = async () => { + const myReturnValue = "Hello world" + try { + await myThrowingFunction() + } catch (error) { + console.error("myThrowingFunction failed", error) + } + return myReturnValue +} + +// Due to the ergonomics of this API being either returning +// a single value, or throwing. You should consider offering +// information about the result inside the returned value and +// use throw only when something truly exceptional has +// occurred. + +const exampleSquareRootFunction = async (input: any) => { + if (isNaN(input)) { + throw new Error("Only numbers are accepted") + } + + if (input < 0) { + return { success: false, message: "Cannot square root negative number"} + } else { + return { success: true, value: Math.sqrt(input) } + } +} + +// Then the function consumers can check in the response and +// figure out what to do with your return value. While this +// is a trivial example, once you have started working with +// networking code these APIs become worth the extra syntax. + +const checkSquareRoot = async (value: number) => { + const response = await exampleSquareRootFunction(value) + if (response.success) { + response.value + } +} + +// Async/Await took code which looked like this: + +// getResponse(url, (response) => { +// getResponse(response.url, (secondResponse) => { +// const responseData = secondResponse.data +// getResponse(responseData.url, (thirdRespones) => { +// ... +// }) +// }) +// }) + +// And let it become linear like: + +// const response = await getResponse(url) +// const secondResponse = await getResponse(response.url) +// const responseData = secondResponse.data +// const thirdResponse = await getResponse(responseData.url) + +// Which can make the code sit closer to left edge, and +// be read with a consistent rhythm. diff --git a/packages/examples/en/JavaScript/Modern JavaScript/Immutability.ts b/packages/examples/en/JavaScript/Modern JavaScript/Immutability.ts new file mode 100644 index 000000000000..79d98725abc2 --- /dev/null +++ b/packages/examples/en/JavaScript/Modern JavaScript/Immutability.ts @@ -0,0 +1,57 @@ +// JavaScript is a language with a few ways to declare +// some of your objects don't change. The most prominent is +// const - which says that the value won't change. + +const helloWorld = "Hello World"; + +// You cannot change helloWorld now, TypeScript will give +// you an error about this because you would get one at +// runtime instead. + +helloWorld = "Hi world"; + +// Why care about immutability? A lot of this is about +// reducing complexity in your code. If you can reduce the +// number of things which can change, then there are less +// things to keep track of. + +// Using const is a great first step, however this fails +// down a bit when using objects. + +const myConstantObject = { + msg: "Hello World" +}; + +// myConstantObject is not quite a constant though, because +// we can still make changes to parts of the object, for +// example we can change msg: + +myConstantObject.msg = "Hi World"; + +// const means the value at that point stays the same, but +// that the object itself may change internally. This can +// be changed using Object.freeze + +const myDefinitelyConstantObject = Object.freeze({ + msg: "Hello World" +}); + +// When an object is frozen, then you cannot change the +// internals. TypeScript will offer errors in these cases: + +myDefinitelyConstantObject.msg = "Hi World"; + +// This works the same for arrays too: + +const myFrozenArray = Object.freeze(["Hi"]); +myFrozenArray.push("World"); + +// Using freeze means you can trust that the object is +// staying the same under the hood. + +// TypeScript has a few extra syntax hooks to improve working +// with immutable data which you can find in the TypeScript +// section of the examples + +// example:literals +// example:type-widening-narrowing diff --git a/packages/examples/en/JavaScript/Modern JavaScript/Import Export.ts b/packages/examples/en/JavaScript/Modern JavaScript/Import Export.ts new file mode 100644 index 000000000000..1f2d12de5e25 --- /dev/null +++ b/packages/examples/en/JavaScript/Modern JavaScript/Import Export.ts @@ -0,0 +1,109 @@ +//// { order: 1, target: "ES5" } + +// JavaScript added import/export to the language back in 2016 +// and TypeScript has complete support for this style of +// linking between files and to external modules. TypeScript +// expands on this syntax by also allowing types to be passed +// with code. + +// Let's look at importing code from a module + +import { danger, message, warn, DangerDSLType } from "danger"; + +// This takes a set of named imports from a node module +// called danger. While there are more than four imports, +// these are the only ones that we have chosen to import. + +// Specifically naming which imports you are importing +// gives tools the ability to remove unused code in your +// apps, and helps you understand what is being used in +// a particular file. + +// In this case: danger, message and warn are JavaScript +// imports - where as DangerDSLType is an interface type. + +// TypeScript lets engineers document their code using +// JSDoc, and docs are imported also. For example if +// you hover on the different parts below, you see +// explanations of what they are. + +danger.git.modified_files; + +// If you want to know how to provide these documentation +// annotations read example:jsdoc-support + +// Another way to import code is by using the default export +// of a module. An example of this is the debug module, which +// exposes a function that creates a logging function. + +import debug from "debug"; +const log = debug("playground"); +log("Started running code"); + +// Because of the nature of default exports having no true +// name, they can be tricky when applied with static analysis +// tools like the refactoring support in TypeScript but they +// have their uses. + +// Because there is a long history in import/exporting code +// in JavaScript, there is a confusing part of default exports: +// Some exports have documentation that implies you can write +// an import like this: + +import req from "request"; + +// However that fails, and then you find a stack overflow +// which recommends the import as: + +import * as request from "request"; + +// And this works. Why? We'll get back to that at the end of +// our section on exporting. + +// In order to import, you must be able to export. The modern +// way to write exports is using the export keyword. + +/** The current stickers left on the roll */ +export const numberOfStickers = 11; + +// This could be imported into another file by: +// +// import { numberOfStickers } from "./path/to/file" + +// You can have as many of those in a file as you like. Then +// a default export add is close to the same thing. + +/** Generates a sticker for you */ +const stickerGenerator = () => {}; +export default stickerGenerator; + +// This could be imported into another file by: +// +// import getStickers from "./path/to/file" +// +// the naming is up to the module consumer. + +// These aren't the only types of imports, just the most common +// in modern code. Covering all of the ways code can cross +// module boundaries is a very long topic in the handbook. +// +// https://www.typescriptlang.org/docs/handbook/modules.html + +// However, to try cover that last question. If you look at +// the JavaScript code for this example - you'll see this: + +// var stickerGenerator = function () { }; +// exports.default = stickerGenerator; + +// This sets the default property on the exports object +// to be stickerGenerator. There is code out there which +// sets exports to be a function, instead of an object. +// +// TypeScript opted to stick with the ECMAScript specification +// about how to handle those cases, which is to raise an +// error. However, there is a compiler setting which will +// automatically handle those cases for you which is +// esModuleInterop. +// +// If you turn that on for this example, you will see that +// error go away. diff --git a/packages/examples/en/JavaScript/Modern JavaScript/JSDoc Support.js b/packages/examples/en/JavaScript/Modern JavaScript/JSDoc Support.js new file mode 100644 index 000000000000..421e145a2210 --- /dev/null +++ b/packages/examples/en/JavaScript/Modern JavaScript/JSDoc Support.js @@ -0,0 +1,92 @@ +//// { order: 3, isJavaScript: true } + +// TypeScript has very rich JSDoc support, for a lot of cases +// you can even skip making your files .ts and just use JSDoc +// annotations to create a rich development environment. +// +// A JSDoc comment is a multi-line comment which starts with +// two stars instead of one. + +/* This is a normal comment */ +/** This is a JSDoc comment */ + +// JSDoc comments become attached to the closest JavaScript +// code below it. + +const myVariable = "Hi"; + +// If you hover over myVariable, you can see that it has the +// text from inside the JSDoc comment attached. + +// JSDoc comments are a way to provide type information to +// TypeScript and your editors. Let's start with an easy one +// setting a variable's type to a built-in type + +// For all of these examples, you can hover over the name, +// and on the next line try write [example]. to see the +// auto-complete options. + +/** @type {number} */ +var myNumber; + +// You can see all of the supported tags inside the handbook +// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#supported-jsdoc +// +// However, we'll try go through some of the more common examples +// in here, you can also copy & paste any examples from the handbook +// into here. + +// Importing the types for JavaScript configuration files: + +/** @type { import("webpack").Config } */ +const config = {}; + +// Creating a complex type to re-use in many places: + +/** + * @typedef {Object} User - a User account + * @property {string} displayName - the name used to show the user + * @property {number} id - a unique id + */ + +// Then use it by referencing the typedef's name: + +/** @type { User } */ +const user = {}; + +// There's TypeScript compatible inline type shorthand, which +// you can use for both type and typedef + +/** @type {{ owner: User, name: string }} */ +const resource; + +/** @typedef {{owner: User, name: string} Resource */ + +/** @type {Resource} */ +const otherResource; + +// Declaring a typed function + +/** + * Adds two numbers together + * @param a {number} The first number + * @param b {number} The second number + * @returns {number} + */ +function addTwoNumbers(a, b) { + return a + b; +} + +// You can use most of TypeScript's type tools, like unions + +/** @type {(string | boolean)} */ +let stringOrBoolean = ""; +stringOrBoolean = false; + +// Extending globals in JSDoc is a more involved process +// which you can see in the VS Code docs +// https://code.visualstudio.com/docs/nodejs/working-with-javascript#_global-variables-and-type-checking + +// Adding JSDoc comments to your functions is a win-win +// situation, you get better tooling and so do all your +// API consumers. diff --git a/packages/examples/en/JavaScript/README.md b/packages/examples/en/JavaScript/README.md new file mode 100644 index 000000000000..7daf372802ae --- /dev/null +++ b/packages/examples/en/JavaScript/README.md @@ -0,0 +1,5 @@ +## JavaScript Examples + +These examples are to cover the how TypeScript handles JavaScript. They +can use TypeScript features lightly, but the focus should be to show people +how they can re-use their JavaScript knowledge in TypeScript. diff --git a/packages/examples/en/JavaScript/Working With Classes/Classes 101.ts b/packages/examples/en/JavaScript/Working With Classes/Classes 101.ts new file mode 100644 index 000000000000..97dab6e24caf --- /dev/null +++ b/packages/examples/en/JavaScript/Working With Classes/Classes 101.ts @@ -0,0 +1,55 @@ +//// { order: 0 } + +// A class is a special type of JavaScript object which +// is always created via a constructor. These classes +// act a lot like objects, and have an inheritance structure +// similar to language like Java/C#/Swift. + +// Here's an example class: + +class Vendor { + name: string; + + constructor(name: string) { + this.name = name; + } + + greet() { + return "Hello, welcome to " + this.name; + } +} + +// An instance can be created via the new keyword, and +// you can call functions and access properties from the +// object. + +const shop = new Vendor("Ye Olde Shop"); +console.log(shop.greet()); + +// You can subclass an object, here's a food cart which +// has a variety as well as a name: + +class FoodTruck extends Vendor { + cuisine: string; + + constructor(name: string, cuisine: string) { + super(name); + this.cuisine = cuisine; + } + + greet() { + return "Hi, welcome to food truck " + this.name + " we serve " + this.cuisine + " food."; + } +} + +// Because we indicated that there needs to be two arguments +// to create a new truck, TypeScript will provide errors +// when you only use one + +const nameOnlyTruck = new FoodTruck("Salome's Adobo"); + +// Correctly passing in two arguments will let you create a +// new instance of the FoodTruck: + +const truck = new FoodTruck("Dave's Doritos", "junk"); +console.log(truck.greet()); diff --git a/packages/examples/en/JavaScript/Working With Classes/Generic Classes.ts b/packages/examples/en/JavaScript/Working With Classes/Generic Classes.ts new file mode 100644 index 000000000000..1b5049b1604f --- /dev/null +++ b/packages/examples/en/JavaScript/Working With Classes/Generic Classes.ts @@ -0,0 +1,94 @@ +//// { order: 3 } + +// This example is mostly in TypeScript, because it is much +// easier to understand this way first. At the end we'll +// cover how to create the same class but using JSDoc instead. + +// Generic Classes are a way to say that a particular type +// depends on another type. For example, here is a drawer +// which can hold any sort of object, but only one type: + +class Drawer { + contents: ClothingType[] = []; + + add(object: ClothingType) { + this.contents.push(object); + } + + remove() { + return this.contents.pop(); + } +} + +// In order to use a Drawer, you will need another +// type to work with: + +interface Sock { + color: string; +} + +interface TShirt { + size: "s" | "m" | "l"; +} + +// We can create a Drawer just for socks by passing in the +// type Sock when we create a new Drawer: +const sockDrawer = new Drawer(); + +// Now we can add or remove socks to the drawer: +sockDrawer.add({ color: "white" }); +const mySock = sockDrawer.remove(); + +// As well as creating a drawer for Tshirts +const tshirtDrawer = new Drawer(); +tshirtDrawer.add({ size: "m" }); + +// If you're a bit eccentric, you could even create a drawer +// which mixes Socks and TShirts by using a union: + +const mixedDrawer = new Drawer(); + +// Creating a class like Drawer without the extra TypeScript +// syntax requires using the template tag in JSDoc. In this +// example we define the template variable, then provide +// the properties on the class: + +// To have this work in the playground, you'll need to change +// the settings to be a JavaScript file, and delete the +// TypeScript code above + +/** + * + * @template {{}} ClothingType + * @param {ClothingType[]} contents + */ +class Dresser { + contents = []; + + /** @param {ClothingType} object */ + add(object) { + this.contents.push(object); + } + + /** @return {ClothingType} */ + remove() { + return this.contents.pop(); + } +} + +// Then we create a new type via JSDoc: + +/** + * @typedef {Object} Coat An item of clothing + * @property {string} color The colour for coat + */ + +// Then when we create a new instance of that class +// we use @type to assign the variable as a Dresser +// which handles Coats. + +/** @type {Dresser} */ +const coatDresser = new Dresser(); + +coatDresser.add({ color: "green" }); +const sock = coatDresser.remove(); diff --git a/packages/examples/en/JavaScript/Working With Classes/Mixins.ts b/packages/examples/en/JavaScript/Working With Classes/Mixins.ts new file mode 100644 index 000000000000..2605703f848d --- /dev/null +++ b/packages/examples/en/JavaScript/Working With Classes/Mixins.ts @@ -0,0 +1,89 @@ +//// { order: 4 } + +// Mixins are a faux-multiple inheritance pattern for classed +// in JavaScript which TypeScript has support for. The pattern +// allows you to create a class which creates class which are +// a merge of many classes. + +// To get started, we need a type which is the which we'll use +// to extend other classes from (it's main responsibility is +// to declare that the type being passed in is a class) (Maybe?!) +// +type Constructor = new (...args: any[]) => T; + +// Then we can create a series of classes which exist to extend +// the final class by wrapping it. This pattern works well +// when similar objects have different capabilities. + +// This mixin adds a scale property, with getters and setters +// for changing it with an encapsulated private property + +function Scale(Base: TBase) { + return class extends Base { + private _scale = 1; + + setScale(scale: number) { + this._scale = scale; + } + + get scale(): number { + return this._scale; + } + }; +} + +// This mixin adds extra methods around alpha composition +// something which modern computers use to create depth + +function Alpha(Base: TBase) { + return class extends Base { + private alpha = 1; + + setHidden() { + this.alpha = 0; + } + + setVisible() { + this.alpha = 1; + } + + setAlpha(alpha: number) { + this.alpha = alpha; + } + }; +} + +// A simple sprite base class which will then be extended: + +class Sprite { + name = ""; + x = 0; + y = 0; + + constructor(name: string) { + this.name = name; + } +} + +// Here we create two different types of sprites +// which have different capabilities: + +const ModernDisplaySprite = Alpha(Scale(Sprite)); +const EightBitSprite = Scale(Sprite); + +// Creating an instance with these classes +// shows that the objects have different sets of +// functions due to their mixins: + +const flappySprite = new ModernDisplaySprite("Bird"); +flappySprite.setVisible(); +flappySprite.setScale(0.8); +flappySprite.x = 10; +flappySprite.y = 20; + + +const gameBoySprite = new EightBitSprite("L block"); +gameBoySprite.setScale(0.3); +// Fails because an EightBitSprite does not have +// the mixin for changing alphas +gameBoySprite.setAlpha(0.5); diff --git a/packages/examples/en/JavaScript/Working With Classes/This.ts b/packages/examples/en/JavaScript/Working With Classes/This.ts new file mode 100644 index 000000000000..67c118f8fcb2 --- /dev/null +++ b/packages/examples/en/JavaScript/Working With Classes/This.ts @@ -0,0 +1,90 @@ +//// { order: 2 } + +///When calling a function inside a class. this generally +// refers to the current instance of a your class. + +class Safe { + contents: string; + + constructor(contents: string) { + this.contents = contents; + } + + printContents() { + console.log(this.contents); + } +} + +const safe = new Safe("Crown Jewels"); +safe.printContents(); + +// If you come form an objected oriented language where the +// this/self variable is easily predictable, then you may +// find you need to read up on how confusing 'this' can be: + +// https://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/ +// https://aka.ms/AA5ugm2 + +// TLDR: this can change. The reference to which this refers +// to can be different depending on how you call the function + +// For example, if you use a reference to the func in another +// object, and then call it through that - the this variable +// has moved refer to the hosting object: + +const customObjectCapturingThis = { contents: "http://gph.is/VxeHsW", print: safe.printContents }; +customObjectCapturingThis.print(); // Prints "http://gph.is/VxeHsW" - not "Crown Jewels" + +// This is tricky, because when dealing with callback APIs - +// it can be very tempting to pass the function reference +// directly. This can be worked around by creating a new +// function at the call site. + +const objectNotCapturingThis = { contents: "N/A", print: () => safe.printContents() }; +objectNotCapturingThis.print(); + +// There are a few are ways to work around this problem. One +// route is to force the binding of this, to be the object +// you originally intended via bind. + +const customObjectCapturingThisAgain = { contents: "N/A", print: safe.printContents.bind(a) }; +customObjectCapturingThisAgain.print(); + +// To work around an unexpected this context, you can also +// change how you create functions in your class. By +// creating a property which uses a fat arrow function, the +// binding of this is done at a different time. Which makes +// it more predictable for those less experienced with the +// JavaScript runtime. + +class SafelyBoundSafe { + contents: string; + + constructor(contents: string) { + this.contents = contents; + } + + printContents = () => { + console.log(this.contents); + }; +} + +// Now passing the function to another object +// to run does not accidentally change this + +const saferSafe = new SafelyBoundSafe("Golden Skull"); +saferSafe.printContents(); + +const customObjectTryingToChangeThis = { + contents: "http://gph.is/XLof62", + print: saferSafe.printContents +}; + +customObjectTryingToChangeThis.print(); + +// If you have a TypeScript project, you can use the compiler +// flag noImplicitThis to highlight cases where TypeScript +// cannot determine what type "this" is for a function. +// +// You can learn more about that in the handbook +// https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypet diff --git a/packages/examples/en/README.md b/packages/examples/en/README.md new file mode 100644 index 000000000000..aa66846bb552 --- /dev/null +++ b/packages/examples/en/README.md @@ -0,0 +1,25 @@ +# TypeScript Example Code + +These samples are built for hyperlinking between each-other +in a sandboxed environment like the TypeScript Playground. + +Each example aims to cover one or two specific features to +either how JavaScript works in TypeScript, or features which +TypeScript has added to the language. + +An example should make assumptions that the reader is in a +monaco/IDE-like environment which has a TSServer running for +to provide extra analysis. As well as a minor fluency in +JavaScript. + +These examples are not set in stone, and we're open to new +ideas. If you'd like to help out and speak more than one +language, we'd love to see translations. + +## Adding a new example section + +Create a folder in this repo, then sub-folders per section. Next, +edit `generateTOC.js` with at the set of folders it should grab +at around line 30, then edit the `const toc` further down to +add a new section. If you need custom ordering then use the +`sortedSubSections` array to set your order. diff --git a/packages/examples/en/TypeScript/Language Extensions/Enums.ts b/packages/examples/en/TypeScript/Language Extensions/Enums.ts new file mode 100644 index 000000000000..74b9fe8a96cd --- /dev/null +++ b/packages/examples/en/TypeScript/Language Extensions/Enums.ts @@ -0,0 +1,81 @@ +// Enums are a feature added to JavaScript in TypeScript +// which makes it easier to handle named sets of constants. + +// By default an enum is number based, starting at zero, +// and each option is assigned an increment by one. This is +// useful when the value is not important. + +enum CompassDirection { + North, + East, + South, + West +} + +// By annotating an enum option, you set the value; +// increments continue from that value: + +enum StatusCodes { + OK = 200, + BadRequest = 400, + Unauthorized, + PaymentRequired, + Forbidden, + NotFound +} + +// You reference an enum by using EnumName.Value + +const startingDirection = CompassDirection.East; +const currentStatus = StatusCodes.OK; + +// Enums support accessing data in both directions from key +// to value, and value to key. + +const okNumber = StatusCodes.OK; +const okNumberIndex = StatusCodes["OK"]; +const stringBadRequest = StatusCodes[400]; + +// Enums can be different types, a string type is common. +// Using a string can make it easier to debug, because the +// value at runtime does not require you to look up the number. + +enum GamePadInput { + Up = "UP", + Down = "DOWN", + Left = "LEFT", + Right = "RIGHT" +} + +// If you want to reduce the number of objects in your +// JavaScript runtime, you can create a const enum. + +// A const enum's value is replaced by TypeScript during +// transpilation of your code, instead of being looked up +// via an object at runtime. + +const enum MouseAction { + MouseDown, + MouseUpOutside, + MouseUpInside +} + +const handleMouseAction = (action: MouseAction) => { + switch (action) { + case MouseAction.MouseDown: + console.log("Mouse Down"); + break; + } +}; + +// If you look at the transpiled JavaScript, you can see +// how the other enums exist as objects and functions, +// however MouseAction is not there. + +// This is also true for the check against MouseAction.MouseDown +// inside the switch statement inside handleMouseAction. + +// Enums can do more than this, you can read more in the +// TypeScript handbook + +// https://www.typescriptlang.org/docs/handbook/enums.html diff --git a/packages/examples/en/TypeScript/Language Extensions/Nominal Typing.ts b/packages/examples/en/TypeScript/Language Extensions/Nominal Typing.ts new file mode 100644 index 000000000000..a068f69fdc91 --- /dev/null +++ b/packages/examples/en/TypeScript/Language Extensions/Nominal Typing.ts @@ -0,0 +1,65 @@ +// A nominal type system means that each type is unique +// and even if types have the same data you cannot assign +// across types. + +// TypeScript's type system is structural, which means +// if the type is shaped like a duck, it's a duck. If a +// goose has all the same attributes as a duck, then it also +// is a duck. You can learn more here: example:structural-typing + +// This can have drawbacks, for example there are cases +// where a string or number can have special context and you +// don't want to ever make the values transferrable. For +// example: +// +// - User Input Strings (unsafe) +// - Translation Strings +// - User Identification Numbers +// - Access Tokens + +// We can get most of the value from a nominal type +// system with a little bit of extra code. + +// We're going to use an intersectional type, with a unique +// constraint in the form of a property called __brand (this +// is convention) which makes it impossible to assign a +// normal string to a ValidatedInputString. + +type ValidatedInputString = string & { __brand: "User Input Post Validation" }; + +// We will will use a function to transform a string to +// a ValidatedInputString - but the point worth noting +// is that we're just _telling_ TypeScript that it's true. + +const validateUserInput = (input: string) => { + const simpleValidatedInput = input.replace(/\ { + console.log(name); +}; + +// For example, here's some unsafe input from a user, going +// through the validator and then being able print: + +const input = "\n"; +const validatedInput = validateUserInput(input); +printName(validatedInput); + +// On the other hand, passing the un-validated string to +// printName will raise a compiler error: + +printName(input); + +// You can read a comprehensive overview of the +// different ways to create nominal types, and their +// trade-offs in this 400 comment long GitHub issue +// +// https://github.com/Microsoft/TypeScript/issues/202 +// +// and this post is a great summary +// https://michalzalecki.com/nominal-typing-in-typescript/ diff --git a/packages/examples/en/TypeScript/Language Extensions/Types vs Interfaces.ts b/packages/examples/en/TypeScript/Language Extensions/Types vs Interfaces.ts new file mode 100644 index 000000000000..31898b0d8cc3 --- /dev/null +++ b/packages/examples/en/TypeScript/Language Extensions/Types vs Interfaces.ts @@ -0,0 +1,83 @@ +// There are two main tools to declare the shape of an +// object: interfaces and type aliases. +// +// They are very similar, and for the most common cases +// act the same. + +type BirdType = { + wings: 2; +}; + +interface BirdInterface { + wings: 2; +} + +const bird1: BirdType = { wings: 2 }; +const bird2: BirdInterface = { wings: 2 }; + +// Because TypeScript is a structural type system, +// it's possible to intermix their use too. + +const bird3: BirdInterface = bird1; + +// They both support extending other interfaces and types. +// Type aliases do this via intersection types, while +// interfaces have a keyword. + +type Owl = { nocturnal: true } & BirdType; +type Robin = { nocturnal: false } & BirdInterface; + +interface Peacock extends BirdType { + colourful: true; + flies: false; +} +interface Chicken extends BirdInterface { + colourful: false; + flies: false; +} + +let owl: Owl = { wings: 2, nocturnal: true }; +let chicken: Chicken = { wings: 2, colourful: false, flies: false }; + +// That said, we recommend you use interfaces over type +// aliases. Specifically, because you will get better error +// messages. If you hover over the following errors, you can +// see how TypeScript can provide terser and more focused +// messages when working with interfaces like Chicken. + +owl = chicken; +chicken = owl; + +// One major difference between type aliases vs interfaces +// are that interfaces are open and type aliases are closed. +// This means you can extend an interface by declaring it +// a second time. + +interface Kitten { + purrs: boolean; +} + +interface Kitten { + colour: string; +} + +// In the other case a type cannot be changed outside of +// it's declaration. + +type Puppy = { + color: string; +}; + +type Puppy = { + toys: number; +}; + +// Depending on your goals, this difference could be a +// positive or a negative. However for publicly exposed +// types, it's a better call to make them an interface. + +// One of the best resources for seeing all of the edge +// cases around types vs interfaces, this stack overflow +// thread is a good place to start + +// https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types/52682220#52682220 diff --git a/packages/examples/en/TypeScript/Language/Soundness.ts b/packages/examples/en/TypeScript/Language/Soundness.ts new file mode 100644 index 000000000000..bba96f581be1 --- /dev/null +++ b/packages/examples/en/TypeScript/Language/Soundness.ts @@ -0,0 +1,116 @@ +// Without a background in type theory, you're unlikely +// to be familiar with the idea of a type system being "sound". + +// Soundness is the idea that the compiler can make guarantees +// about the type a value has a runtime, and not just +// during compilation. This is normal for most programming +// languages that are built with types from day one. + +// Building a type system which models a language which has +// existed for a few decades however becomes about making +// decisions with trade-offs on three qualities: Simplicity, +// Usability and Soundness. + +// With TypeScript's goal of being able to support all JavaScript +// code, the language tends towards simplicity and usability +// when presented with ways to add types to JavaScript. + +// Let's look at a few cases where TypeScript is provably +// not sound, to understand what those trade-offs would look +// like otherwise. + +// Type Assertions + +const usersAge = ("23" as any) as number; + +// TypeScript will let you use type assertions to override +// the inference to something which is is quite wrong. Using +// type assertions is a way of telling TypeScript you know +// best, and TypeScript will try to let you get on with it. + +// Languages which are sound would occasionally use runtime checks +// to ensure that the data matches what your types say - but +// TypeScript aims to have no type-aware runtime impact on +// your transpiled code. + +// Function Parameter Bi-variance + +// Params for a function support redefining the parameter +// to be a subtype of the original declaration. + +interface InputEvent { + timestamp: number; +} +interface MouseInputEvent extends InputEvent { + x: number; + y: number; +} +interface KeyboardInputEvent extends InputEvent { + keyCode: number; +} + +function listenForEvent(eventType: "keyboard" | "mouse", + handler: (event: InputEvent) => void) {} + +// You can re-declare the parameter type to be a subtype of +// the declaration. Above, handler expected a type InputEvent +// but in the below usage examples - TypeScript accepts +// a type which has additional properties. + +listenForEvent("keyboard", (event: KeyboardInputEvent) => {}); +listenForEvent("mouse", (event: MouseInputEvent) => {}); + +// This can go all the way back to the smallest common type: + +listenForEvent("mouse", (event: {}) => {}); + +// But no further: + +listenForEvent("mouse", (event: string) => {}); + +// This covers the real-world pattern of event listener +// in JavaScript, at the expense of having being sound. + +// TypeScript can raise an error when this happens via +// `strictFunctionTypes`. Or, you could work around this +// particular case with function overloads, +// see: example:typing-functions + +// Void special casing + +// Parameter Discarding + +// To learn about special cases with function parameters +// see example:structural-typing + +// Rest Parameters + +// Rest parameters are assumed to all be optional, this means +// TypeScript will not have a way to enforce the number of +// parameters available to a callback. + +function getRandomNumbers(count: number, + callback: (...args: number[]) => void) {} + +getRandomNumbers(2, (first, second) => console.log([first, second])); +getRandomNumbers(400, first => console.log(first)); + +// Void Functions Can Match to a Function With a Return Value + +// A function which returns a void function, can accept a +// function which takes any other type. + +const getPI = () => 3.14; + +function runFunction (func: () => void) { + func(); +} + +runFunction(getPI); + +// For more information on the places where soundness of the +// type system is compromised, see: + +// https://github.com/Microsoft/TypeScript/wiki/FAQ#type-system-behavior +// https://github.com/Microsoft/TypeScript/issues/9825 +// https://www.typescriptlang.org/docs/handbook/type-compatibility.html diff --git a/packages/examples/en/TypeScript/Language/Structural Typing.ts b/packages/examples/en/TypeScript/Language/Structural Typing.ts new file mode 100644 index 000000000000..89cf5492a9d9 --- /dev/null +++ b/packages/examples/en/TypeScript/Language/Structural Typing.ts @@ -0,0 +1,81 @@ +// TypeScript is a Structural Type System. A structural type +// system means that when comparing types, TypeScript only +// takes into account the members on the type. + +// This is in contrast to nominal type systems, where you +// could create two types but could not assign them to each +// other. See example:nominal-typing + +// For example, these two interfaces are completely +// transferrable in a structural type system: + +interface Ball { diameter: number; } +interface Sphere { diameter: number; } + +let ball: Ball = { diameter: 10 }; +let sphere: Sphere = { diameter: 20 }; + +sphere = ball; +ball = sphere; + +// If we add in a type which structurally contains all of +// the members of Ball and Sphere, then it also can be +// set to be a ball or sphere. + +interface Tube { + diameter: number; + length: number; +} + +let tube: Tube = { diameter: 12, length: 3 }; + +tube = ball; +ball = tube; + +// Because a ball does not have a length, then it cannot be +// assigned to the tube variable. However, all of the members +// of Ball are inside tube, and so it can be assigned. + +// TypeScript is comparing each member in the type against +// each other to verify their equality. + +// A function is an object in JavaScript and it is compared +// in a similar fashion. With one useful extra trick around +// the params: + +let createBall = (diameter: number) => ({ diameter }); +let createSphere = (diameter: number, useInches: boolean) => { + return { diameter: useInches ? diameter * 0.39 : diameter }; +}; + +createSphere = createBall; +createBall = createSphere; + +// TypeScript will allow (number) to equal (number, boolean) +// in the parameters, but not (number, boolean) -> (number) + +// TypeScript will discard the boolean in the first assignment +// because it's very common for JavaScript code to skip passing +// params when they're not needed. + +// For example the array's forEach's callback has three params, +// value, index and the full array - if TypeScript didn't +// support discarding parameters, then you would have to +// include every option to make the functions match up: + +[createBall(1), createBall(2)].forEach((ball, _index, _balls) => { + console.log(ball); +}); + +// No one needs that. + +// Return types are treated like objects, and any differences +// are compared with the same object equality rules above. + +let createRedBall = (diameter: number) => ({ diameter, color: "red" }); + +createBall = createRedBall +createRedBall = createBall + +// Where the first assignment works (they both have diameter) +// but the second doesn't (the ball doesn't have a color.) diff --git a/packages/examples/en/TypeScript/Language/Type Guards.ts b/packages/examples/en/TypeScript/Language/Type Guards.ts new file mode 100644 index 000000000000..7843cc84cac5 --- /dev/null +++ b/packages/examples/en/TypeScript/Language/Type Guards.ts @@ -0,0 +1,85 @@ +// Type Guarding is the term where you influence the code +// flow analysis via code. TypeScript uses existing JavaScript +// behavior which validates your objects at runtime to influence +// the code flow. This example assumes you've read example:code-flow + +// To run through these examples, we'll create some classes, +// here's a system for handling internet or telephone orders. + +interface Order { address: string } +interface TelephoneOrder extends Order { callerNumber: string } +interface InternetOrder extends Order { email: string } + +// Then a type which could be one of the two Order subtypes or undefined +type PossibleOrders = TelephoneOrder | InternetOrder | undefined; + +// And a function which returns a Possible order +declare function getOrder(): PossibleOrders; +const possibleOrder = getOrder(); + +// We can use the "in" operator to check whether a particular +// key is on the object to narrow the union. ("in" is a JavaScript +// operator for testing object keys.) + +if ("email" in possibleOrder) { + const mustBeInternetOrder = possibleOrder; +} + +// You can use the JavaScript "instanceof" operator if you +// have a class which conforms to the interface: + +class TelephoneOrderClass { + address: string; + callerNumber: string; +} + +if (possibleOrder instanceof TelephoneOrderClass) { + possibleOrder; +} + +// You can use the JavaScript "typeof" operator to +// narrow your union, this only works with primitives +// inside JavaScript (like strings, objects, numbers) + +if (typeof possibleOrder === "undefined") { + const definitelyNotAnOder = possibleOrder; +} + +// You can see a full list of possible typeof values +// here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof + +// Using JavaScript operators can only get you so far, when +// you want to check your own object types you can use +// type predicate functions. + +// A type predicate function is a function where the return +// type offers information to the code flow analysis when +// the function returns true + +// Using the possible order, we can use two type guards +// to declare which type the possibleOrder is: + +function isAnInternetOrder(order: PossibleOrders): order is InternetOrder { + return order && "email" in order +} + +function isATelephoneOrder(order: PossibleOrders): order is TelephoneOrder { + return order && "calledNumber" in order +} + +// Now we can use these functions in if statements to narrow +// down the type which possibleOrder is inside the if: + +if (isAnInternetOrder(possibleOrder)) { + console.log("Order received via email:", possibleOrder.email) +} + +if (isATelephoneOrder(possibleOrder)) { + console.log("Order received via phone:", possibleOrder.callerNumber) +} + +// You can read more on how code flow analysis here: +// +// - example:code-flow +// - example:type-guards +// - example:discriminate-types diff --git a/packages/examples/en/TypeScript/Language/Type Widening and Narrowing.ts b/packages/examples/en/TypeScript/Language/Type Widening and Narrowing.ts new file mode 100644 index 000000000000..6eb18d3e1b12 --- /dev/null +++ b/packages/examples/en/TypeScript/Language/Type Widening and Narrowing.ts @@ -0,0 +1,66 @@ +// It might be easiest to start of the discussion of +// widening and narrowing with an example: + +const welcomeString = "Hello There"; +let replyString = "Hey"; + +// Aside from the text differences of the strings, welcomeString +// is a const (which means the value will never change) +// and replyString is a let (which means it can change) + +// If you hover over both variables, you get very different +// type information from TypeScript: +// +// const welcomeString: "Hello There" +// +// let replyString: string + +// TypeScript has inferred the type of welcomeString to be +// the literal string "Hello There", whereas replyString +// is general string. + +// This is because a let needs to have a wider type, you +// could set replyString to be any other string - which means +// it has a wider set of possibilities. + +replyString = "Hi :wave:"; + +// If replyString had the string literal type "Hey" - then +// you could never change the value because it could only +// change to "Hey" again. + +// Widening and Narrowing types is about expanding and reducing +// the possibilities which a type could represent. + +// An example of type narrowing is working with unions, the +// example on code flow analysis is almost entirely based on +// narrowing: example:code-flow + +// Type narrowing is what powers the strict mode of TypeScript +// via the nullability checks. With strict mode turned off, +// markers for nullability like undefined and null are ignored +// in a union. + +declare const quantumString: string | undefined; +// This will fail in strict mode only +quantumString.length; + +// In strict mode the onus is on the code author to ensure +// that the type has been narrowed to the non-null type. +// Usually this is as simple as an if check + +if (quantumString) { + quantumString.length; +} + +// In strict mode the type quantumString has two representations. +// Inside the if, the type was narrowed to just string. + +// You can see more examples of narrowing in +// +// example:union-and-intersection-types +// example:discriminate-types + +// And even more resources on the web: +// https://mariusschulz.com/blog/literal-type-widening-in-typescript +// https://sandersn.github.io/manual/Widening-and-Narrowing-in-Typescript.html diff --git a/packages/examples/en/TypeScript/Meta-Types/Conditional Types.ts b/packages/examples/en/TypeScript/Meta-Types/Conditional Types.ts new file mode 100644 index 000000000000..de6f5d6b3407 --- /dev/null +++ b/packages/examples/en/TypeScript/Meta-Types/Conditional Types.ts @@ -0,0 +1,111 @@ +// Conditionals Types provide a way to do simple logic in the +// TypeScript type system. This is definitely an advanced +// feature, and it's quite feasible that you won't need to +// use this in your normal day to day code. + +// A conditional type looks like: +// +// A extends B ? C : D +// +// Where the condition is whether a type extends an +// expression, and if so what type should be returned. + +// Let's go through some examples, for brevity we're +// going to use single letters for generics. This is optional +// but restricting ourselves to 60 characters makes it +// hard to fit on screen. + +type Cat = { meows: true }; +type Dog = { barks: true }; +type Cheetah = { meow: true; fast: true }; +type Wolf = { barks: true; howls: true }; + +// We can create a conditional type which lets use extract +// types which only conform to something which barks. + +type ExtractDogish = A extends { barks: true } ? A : never; + +// Then we can create types which ExtractDogish wraps: + +// A cat doesn't bark, so it will return never +type NeverCat = ExtractDogish; +// A wolf will bark, so it returns the wolf shape +type Wolfish = ExtractDogish; + +// This becomes useful when you want to work with a +// union of many types and reduce the number of potential +// options in a union: + +type Animals = Cat | Dog | Cheetah | Wolf; + +// When you apply ExtractDogish to a union type, it is the +// same as running the conditional against each member of +// the type: + +type Dogish = ExtractDogish; + +// = ExtractDogish | ExtractDogish | +// ExtractDogish | ExtractDogish +// +// = never | Dog | never | Wolf +// +// = Dog | Wolf (see example:unknown-and-never) + +// This is called a distributive conditional type because +// the type distributes over each member of the union. + +// Deferred Conditional Types + +// Conditional types can be used to tighten your APIs which +// can return different types depending on the inputs. + +// For example this function which could return either a +// string or number depending on the boolean passed in. + +declare function getID(fancy: T): + T extends true ? string : number; + +// Then depending on how much the type-system knows about +// the boolean, you will get different return types: + +let stringReturnValue = getID(true); +let numberReturnValue = getID(false); +let stringOrID = getID(Math.random() < 0.5); + +// In this case above TypeScript can know the return value +// instantly. However, you can use conditional types in functions +// where the type isn't known yet. This is called a deferred +// conditional type. + +// Same as our Dogish above, but as a function instead +declare function isCatish(x: T): T extends { meows: true } ? T : undefined; + +// There is an extra useful tool within conditional types, which +// is being able to specifically tell TypeScript that it should +// infer the type when deferring. That is the 'infer' keyword. + +// infer is typically used to create meta-types which inspect +// the existing types in your code, think of it as creating +// a new variable inside the type + +type GetReturnValue = + T extends (...args: any[]) => infer R ? R : T; + +// Roughly: +// +// - this is a conditional generic type called GetReturnValue +// which takes a type in its first parameter +// +// - the conditional checks if the type is a function, and +// if so create a new type called Return based on the return +// value for that function +// +// - If the pass checks, the type value is the inferred +// return value, otherwise it is the original Type +// + +type getIDReturn = GetReturnValue + +// This fails the check for being a function, and would +// just return the type passed into it. +type getCat = GetReturnValue diff --git a/packages/examples/en/TypeScript/Meta-Types/Discriminate Types.ts b/packages/examples/en/TypeScript/Meta-Types/Discriminate Types.ts new file mode 100644 index 000000000000..3cc75e640254 --- /dev/null +++ b/packages/examples/en/TypeScript/Meta-Types/Discriminate Types.ts @@ -0,0 +1,69 @@ +// A discriminated type union is where you use code flow +// analysis to reduce a set of potential objects down to one +// specific object. +// +// This pattern works really well for sets of similar +// objects with a different string or number constant +// for example: a list of named events, or versioned +// sets of objects. + +type TimingEvent = { name: "start"; userStarted: boolean } + | { name: "closed"; duration: number }; + +// When event comes into this function, it could be any +// of the two potential types. + +const handleEvent = (event: TimingEvent) => { + // By using a switch against event.name TypeScript's code + // flow analysis can determine that an object can only + // be represented by one type in the union. + + switch (event.name) { + case "start": + // This means you can safely access userStarted + // because it's the only type inside TimingEvent + // where name is "start" + const initiatedByUser = event.userStarted; + break; + + case "closed": + const timespan = event.duration; + break; + } +}; + +// This pattern is the same with numbers which we can use +// version as the discriminate for. + +// In this example, we have a discriminate union and an +// additional error state to handle. + +type APIResponses = { version: 0; msg: string } + | { version: 1; message: string; status: number } + | { error: string }; + +const handleResponse = (response: APIResponses) => { + // Handle the error case, and then return + if ("error" in response) { + console.error(response.error); + return; + } + + // TypeScript now knows that APIResponse cannot be + // the error type. If it were the error, the function + // would have returned. You can verify this by + // hovering over response below. + + if (response.version === 0) { + console.log(response.msg); + } else if (response.version === 1) { + console.log(response.status, response.message); + } +}; + +// You're better off using a switch statement instead of +// if statements because you can make assurances that all +// parts of the union are checked. There is a good pattern +// for this using the never type in the handbook + +// https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions diff --git a/packages/examples/en/TypeScript/Meta-Types/Indexed Types.ts b/packages/examples/en/TypeScript/Meta-Types/Indexed Types.ts new file mode 100644 index 000000000000..a3a8e4897b38 --- /dev/null +++ b/packages/examples/en/TypeScript/Meta-Types/Indexed Types.ts @@ -0,0 +1,42 @@ +// There are times when you find yourself duplicating types. +// A common example is nested resources in an auto-generated +// API response. + +interface ArtworkSearchResponse { + artists: [ + { + name: string; + artworks: [ + { + name: string; + deathdate: string | null; + bio: string; + } + ]; + } + ]; +} + +// If this interface were hand-crafted, it's pretty easy to +// imagine pulling out the artworks into an interface like: + +interface Artwork { + name: string; + deathdate: string | null; + bio: string; +} + +// However, in this case we don't control the API, and if +// we hand-created the interface then it's possible that +// the artworks part of ArtworkSearchResponse and +// Artwork could get out of sync when the response changes. + +// The fix for this is indexed types, which replicate how +// JavaScript allows accessing properties via strings. + +type InferredArtwork = + ArtworkSearchResponse["artists"]["0"]["artworks"]["0"]; + +// The InferredArtwork is generated by looking through the +// type's properties and giving a new name to the subset which +// you have indexed. diff --git a/packages/examples/en/TypeScript/Meta-Types/Mapped Types.ts b/packages/examples/en/TypeScript/Meta-Types/Mapped Types.ts new file mode 100644 index 000000000000..b4c0cc60d4e0 --- /dev/null +++ b/packages/examples/en/TypeScript/Meta-Types/Mapped Types.ts @@ -0,0 +1,57 @@ +// Mapped types are a way to creates new types based +// on another type. Effectively a transformational type. + +// Common cases for using a mapped type is dealing with +// partial subsets of an existing type. For example +// an API may return an Artist: + +interface Artist { + id: number; + name: string; + bio: string; +} + +// However, if you were to send an update to the API which +// only changes a subset of the Artist then you would +// typically have to create an additional type: + +interface ArtistForEdit { + id: number; + name?: string; + bio?: string; +} + +// It's very likely that this would get out of sync with +// the Artist above. Mapped types let you create a change +// in an existing type. + +type MyPartialType = { + // For every existing property inside the type of Type + // convert it to be a ?: version + [Property in keyof Type]?: Type[Property]; +}; + +// Now we can use the mapped type instead to create +// our edit interface: +type MappedArtistForEdit = MyPartialType; + +// This is close to perfect, but it does allow id to be null +// which should never happen. So, let's make one quick +// improvement by using an intersection type (see: +// example:union-and-intersection-types ) + +type MyPartialTypeForEdit = { + [Property in keyof Type]?: Type[Property]; +} & { id: number }; + +// This takes the partial result of the mapped type, and +// merges it with an object which has id: number set. +// Effectively forcing id to be in the type. + +type CorrectMappedArtistForEdit = MyPartialTypeForEdit; + +// This is a pretty simple example of how mapped type +// work, but covers most of the basics. If you'd like to +// dive in with more depth, check out the handbook +// +// https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types diff --git a/packages/examples/en/TypeScript/Primitives/Any.ts b/packages/examples/en/TypeScript/Primitives/Any.ts new file mode 100644 index 000000000000..639dd45449e1 --- /dev/null +++ b/packages/examples/en/TypeScript/Primitives/Any.ts @@ -0,0 +1,50 @@ +// Any is the TypeScript escape clause. You can use any to +// either declare a section of your code to be dynamic and +// JavaScript like, or to work around limitations in the +// type system. + +// A good case for any is JSON parsing: + +const myObject = JSON.parse("{}"); + +// Any declares to TypeScript to trust your code as being +// safe because you know more about it. Even if that is +// not strictly true. For example, this code would crash: + +myObject.x.y.z; + +// Using an any gives you the ability to write code closer to +// original JavaScript with the trade off of type safety. + +// any is much like a 'type wildcard' which you can replace +// with any type (except never) to make one type assignable +// to the other. + +declare function debug(value: any); + +debug("a string"); +debug(23); +debug({ color: "blue" }); + +// Each call to debug is allowed because you could replace the +// any with the type of the argument to match. + +// TypeScript will take into account the position of the +// anys in different forms, for example with these tuples +// for the function argument + +declare function swap(x: [number, string]): [string, number] + +declare const pair: [any, any]; +swap(pair) + +// The call to swap is allowed because the argument can be +// matched by replacing the first any in pair with number +// and the second `any` with string. + +// If tuples are new to you, see: example:tuples + +// Unknown is a sibling type to any, if any is about saying +// "I know what's best", then unknown is a way to say "I'm +// not sure what is best, so you need to tell TS the type" +// example:unknown-and-never diff --git a/packages/examples/en/TypeScript/Primitives/Literals.ts b/packages/examples/en/TypeScript/Primitives/Literals.ts new file mode 100644 index 000000000000..3a3cd80741e4 --- /dev/null +++ b/packages/examples/en/TypeScript/Primitives/Literals.ts @@ -0,0 +1,65 @@ +// TypeScript has some fun special cases for literals in +// source code. + +// In part, a lot of the support is covered in type widening +// and narrowing ( example:type-widening-narrowing ) and it's +// worth covering that first. + +// A literal is a more concrete subtype of a collective type. +// What this means is that "Hello World" is a string, but a +// string is not "Hello World" inside the type system + +const helloWorld = "Hello World"; +let hiWorld = "Hi World"; // this is a string because it is let + +// This function takes all strings +declare function allowsAnyString(arg: string); +allowsAnyString(helloWorld); +allowsAnyString(hiWorld); + +// This function only accepts the string literal "Hello World" +declare function allowsOnlyHello(arg: "Hello World"); +allowsOnlyHello(helloWorld); +allowsOnlyHello(hiWorld); + +// This lets you declare APIs which use unions to say it +// only accepts a particular literal: + +declare function allowsFirstFiveNumbers(arg: 1 | 2 | 3 | 4 | 5); +allowsFirstFiveNumbers(1); +allowsFirstFiveNumbers(10); + +let potentiallyAnyNumber = 3; +allowsFirstFiveNumbers(potentiallyAnyNumber); + +// At first glance, this rule isn't applied to complex objects + +const myUser = { + name: "Sabrina" +}; + +// See how it transforms name: "Sabrina" to name: string? +// even though it is defined as a constant. This is because +// the name can still change any time: + +myUser.name = "Cynthia"; + +// Because myUser's name property can change, TypeScript +// cannot use the literal version in the type system. There +// is a feature which will allow you to do this however. + +const myUnchangingUser = { + name: "Fatma" +} as const; + +// When "as const" is applied to the object, then it becomes +// a object literal which doesn't change instead of a +// mutable object which can. + +myUnchangingUser.name = "Raîssa"; + +// "as const" is a great tool for fixtured data, and places +// where you treat code as literals inline. As const also +// works with arrays + +const exampleUsers = [{ name: "Brian" }, { name: "Fahrooq" }] as const; diff --git a/packages/examples/en/TypeScript/Primitives/Union and Intersection Types.ts b/packages/examples/en/TypeScript/Primitives/Union and Intersection Types.ts new file mode 100644 index 000000000000..38aaec4cae71 --- /dev/null +++ b/packages/examples/en/TypeScript/Primitives/Union and Intersection Types.ts @@ -0,0 +1,83 @@ +// Type unions are a way of declaring that an object +// could be more than one type. + +type StringOrNumber = string | number; +type ProcessStates = "open" | "closed"; +type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9; +type AMessyUnion = "hello" | 156 | { error: true }; + +// If the use of "open" and "closed" vs string is +// new to you, check out: example:literals + +// We can mix different types into a union, and +// what we're saying is that the value is one of those types. + +// TypeScript will then leave you to figure out how to +// determine which value it could be at runtime. + +// Unions can sometimes be undermined by type widening, +// for example: + +type WindowStates = "open" | "closed" | "minimized" | string; + +// If you hover above, you can see that WindowStates +// becomes a string - not the union. This is covered in +// example:type-widening-narrowing + +// If a union is an OR, then an intersection is an AND. +// Intersection types are when two types intersect to create +// a new type. This allows for type composition. + +interface ErrorHandling { + success: boolean; + error?: { message: string }; +} + +interface ArtworksData { + artworks: [{ title: string }]; +} + +interface ArtistsData { + artists: [{ name: string }]; +} + +// These interfaces can be composed in responses which have +// both consistent error handling, and their own data. + +type ArtworksResponse = ArtworksData & ErrorHandling; +type ArtistsResponse = ArtistsData & ErrorHandling; + +// For example: + +const handleArtistsResponse = (response: ArtistsResponse) => { + if (response.error) { + console.error(response.error.message); + return; + } + + console.log(response.artists); +}; + +// A mix of Intersection and Union types become really +// useful when you have cases where an object has to +// include one of two values: + +interface CreateArtistBioBase { + artistID: string + thirdParty?: boolean +} + +type CreateArtistBioRequest + = CreateArtistBioBase & { html: string } | { markdown: string } + +// Now you can only create a request when you include +// artistID and either html or markdown + +const workingRequest: CreateArtistBioRequest = { + artistID: "banksy", + markdown: "Banksy is an anonymous England-based graffiti artist..." +} + +const badRequest: CreateArtistBioRequest = { + artistID: "banksy", +} diff --git a/packages/examples/en/TypeScript/Primitives/Unknown and Never.ts b/packages/examples/en/TypeScript/Primitives/Unknown and Never.ts new file mode 100644 index 000000000000..77b43fe1f3f6 --- /dev/null +++ b/packages/examples/en/TypeScript/Primitives/Unknown and Never.ts @@ -0,0 +1,128 @@ +// Unknown + +// Unknown is one of those types that once it clicks, you +// can find quite a lot of uses for it. It acts like a sibling +// to the any type. Where any allows for ambiguity - unknown +// requires specifics. + +// A good example would be in wrapping a JSON parser, JSON +// data can come in many different forms and the creator +// of the json parsing function won't know the shape of the +// data - the person calling that function should. + +const jsonParser = (jsonString: string) => JSON.parse(jsonString); + +const myAccount = jsonParser(`{ name: "Dorothea" }`); + +myAccount.name; +myAccount.email; + +// If you hover on jsonParser, you can see that it has the +// return type of any, so then does myAccount. It's possible +// to fix this with Generics - but it's also possible to fix +// this with unknown. + +const jsonParserUnknown = (jsonString: string): unknown => JSON.parse(jsonString); + +const myOtherAccount = jsonParserUnknown(`{ name: "Samuel" }`); + +myOtherAccount.name; + +// The object myOtherAccount cannot be used until the type has +// been declared to TypeScript. This can be used to ensure +// that API consumers think about their typing up-front: + +type User = { name: string }; +const myUserAccount = jsonParserUnknown(`{ name: "Samuel" }`) as User; +myUserAccount.name; + +// Unknown is a great tool, to understand it more read these: +// https://mariusschulz.com/blog/the-unknown-type-in-typescript +// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type + +// Never + +// Because TypeScript supports code flow analysis, the language +// needs to be able to represent when code logically cannot +// happen. For example, this function cannot return: + +const neverReturns = () => { + // If it throws on the first line + throw new Error("Always throws, never returns"); +}; + +// If you hover on the type, you see it is a () => never +// which means it should never happen. These can still be +// passed around like other values: + +const myValue = neverReturns(); + +// Having a function never return can be useful when dealing +// with the unpredictability of the JavaScript runtime and +// API consumers that might not be using types: + +const validateUser = (user: User) => { + if (user) { + return user.name !== "NaN"; + } + + // According to the type system, this code path can never + // happen, which matches thereturn type of neverReturns. + + return neverReturns(); +}; + +// The type definitions state that a user has to be passed in +// but there are enough escape valves in JavaScript whereby +// you can't guarantee that. + +// Using a function which returns never allows you to add +// additional code in places which should not be possible. +// This is useful for presenting better error messages, +// or closing resources like files or loops. + +// A very popular use for never, is to ensure that a +// switch is exhaustive. E.g., that every path is covered. + +// Here's an enum and an exhaustive switch, try adding +// a new option to the enum (maybe Tulip?) + +enum Flower { + Rose, + Rhododendron, + Violet, + Daisy +} + +const flowerLatinName = (flower: Flower) => { + switch (flower) { + case Flower.Rose: + return "Rosa rubiginosa"; + case Flower.Rhododendron: + return "Rhododendron ferrugineum"; + case Flower.Violet: + return "Viola reichenbachiana"; + case Flower.Daisy: + return "Bellis perennis"; + + default: + const _exhaustiveCheck: never = flower; + return _exhaustiveCheck; + } +}; + +// You will get a compiler error saying that your new +// flower type cannot be converted into never. + +// Never in Unions + +// A never is something which is automatically removed from +// a type union. + +type NeverIsRemoved = string | never | number; + +// If you look at the type for NeverIsRemoved, you see that +// it is string | number. This is because it should never +// happen at runtime because you cannot assign to it. + +// This feature is used a lot in example:conditional-types diff --git a/packages/examples/en/TypeScript/README.md b/packages/examples/en/TypeScript/README.md new file mode 100644 index 000000000000..0e3421d98c05 --- /dev/null +++ b/packages/examples/en/TypeScript/README.md @@ -0,0 +1,6 @@ +## TypeScript Examples + +These examples are to cover the how TypeScript extends JavaScript. + +An example should be based on a single feature or concept in TypeScript, +and provide a few angles in which you can look and play with that feature. diff --git a/packages/examples/en/TypeScript/Type Primitives/Built-in Utility Types.ts b/packages/examples/en/TypeScript/Type Primitives/Built-in Utility Types.ts new file mode 100644 index 000000000000..cd693f418eae --- /dev/null +++ b/packages/examples/en/TypeScript/Type Primitives/Built-in Utility Types.ts @@ -0,0 +1,131 @@ +//// { order: 3, compiler: { strictNullChecks: true } } + +// When a particular type feels like it's useful in most +// codebases, they are added into TypeScript and become +// available for anyone which means you can consistently +// rely on their availability + +// Partial + +// Takes a type and converts all of its properties +// to optional ones. + +interface Sticker { + id: number + name: string + createdAt: string + updatedAt: string + submitter: undefined | string +} + +type StickerUpdateParam = Partial + + +// Readonly + +// Takes an object and makes its properties read-only + +type StickerFromAPI = Readonly + + +// Record + +// Creates a type which uses the list of properties from +// KeysFrom and gives them the value of Type + +// List which keys come from: +type NavigationPages = 'home' | 'stickers' | 'about' | 'contact' + +// The shape of the data for which each of ^ is needed: +interface PageInfo { + title: string + url: string + axTitle?: string +} + +const navigationInfo: Record = { + home: { title: "Home", url: "/" }, + about: { title: "About" , url: "/about"}, + contact: { title: "Contact", url: "/contact" }, + stickers: { title: "Stickers", url: "/stickers/all" } +} + +// Pick + +// Creates a type by picking the set of properties Keys +// from Type. Essentially an allow-list for extracting type +// information from a type. + +type StickerSortPreview = Pick + + +// Omit + +// Creates a type by removing the set of properties Keys +// from Type. Essentially a block-list for extracting type +// information from a type. + +type StickerTimeMetadata = Omit + + +// Exclude + +// Creates a type where any property in Type's properties +// which don't overlap with RemoveUnion. + +type HomeNavigationPages = Exclude + + +// Extract + +// Creates a type where any property in Type's properties +// are included if they overlap with MatchUnion. + +type DynamicPages = Extract + + +// NonNullable + +// Creates a type by excluding null and undefined from a set +// of properties. Useful when you have a validation check. + +type StickerLookupResult = Sticker | undefined | null +type ValidatedResult = NonNullable + + +// ReturnType + +// Extracts the return value from a Type. + +declare function getStickerByID(id: number): Promise +type StickerResponse = ReturnType + + +// InstanceType + +// Creates a type which is an instance of a class, or object +// with a constructor function. + +class StickerCollection { + stickers: Sticker[] +} + +type CollectionItem = InstanceType + + +// Required + +// Creates a type which converts all optional properties +// to required ones. + +type AccessiblePageInfo = Required + + +// ThisType + +// Unlike other types, ThisType does not return a new +// type but instead manipulates the definition of this +// inside a function. You can only use ThisType when you +// have noImplicitThis turned on in your TSConfig + +// https://www.typescriptlang.org/docs/handbook/utility-types.html diff --git a/packages/examples/en/TypeScript/Type Primitives/Nullable Types.ts b/packages/examples/en/TypeScript/Type Primitives/Nullable Types.ts new file mode 100644 index 000000000000..16ac81bcda3c --- /dev/null +++ b/packages/examples/en/TypeScript/Type Primitives/Nullable Types.ts @@ -0,0 +1,85 @@ +//// { order: 3, compiler: { strictNullChecks: false } } + +// JavaScript has two ways to declare values which don't +// exist, and TypeScript adds extra syntax which allow even +// more ways to declare something as optional or nullable. + +// First up, the difference between the two JavaScript +// primitives: undefined and null + +// Undefined is when something cannot be found or set + +const emptyObj = {}; +const anUndefinedProperty: undefined = emptyObj["anything"]; + +// Null is meant to be used when there is a conscious lack +// of a value. + +const searchResults = { + video: { name: "LEGO Movie" }, + text: null, + audio: { name: "LEGO Movie Soundtrack" } +}; + +// Why not use undefined? Mainly, because now you can verify +// that text was correctly included. If text returned as +// undefined then the result is the same as though it was +// not there. + +// This might feel a bit superficial, but when converted into +// a JSON string, if text was an undefined, it would not be +// included in the string equivalent. + +// Strict Null Types + +// Before TypeScript 2.0 undefined and null were effectively +// ignored in the type system. This let TypeScript provide a +// coding environment closer to un-typed JavaScript. + +// Version 2.0 added a compiler flag called "strictNullTypes" +// and this flag required people to treat undefined and null +// as types which needs to be handled via code-flow analysis +// ( see more at example:code-flow ) + +// For an example of the difference in turning on strict null +// types to TypeScript, hover over "Potential String" below: + +type PotentialString = string | undefined | null; + +// The PotentialString discards the undefined and null. If +// you go up to the settings and turn on strict mode and come +// back, you'll see that hovering on PotentialString now shows +// the full union. + +declare function getID(): PotentialString; + +const userID = getID(); +console.log("User Logged in: ", userID.toUpperCase()); + +// Only in strict mode the above will fail ^ + +// There are ways to tell TypeScript you know more, such as +// a type assertion or via a non-null assertion operator (!) + +const definitelyString1 = getID() as string; +const definitelyString2 = getID()!; + +// Or you safely can check for the existence via an if: + +if (userID) { + console.log(userID); +} + +// Optional Properties + +// Void + +// Void is the return type of a function which does not +// return a value. + +const voidFunction = () => {}; +const resultOfVoidFunction = voidFunction(); + +// This is usually an accident, and TypeScript keeps the void +// type around to let you get compiler errors - even though at +// runtime it would be an undefined. diff --git a/packages/examples/en/TypeScript/Type Primitives/Tuples.ts b/packages/examples/en/TypeScript/Type Primitives/Tuples.ts new file mode 100644 index 000000000000..ed601dca80e9 --- /dev/null +++ b/packages/examples/en/TypeScript/Type Primitives/Tuples.ts @@ -0,0 +1,70 @@ +// Typically an array contains zero to many objects of a +// single type. TypeScript has special analysis around +// arrays which contain multiple types, and where the order +// in which they are indexed is important. + +// These are called tuples. Think of them as a way to +// connect some data, but with less syntax than keyed objects. + +// You can create a tuple using JavaScript's array syntax: + +const failingResponse = ["Not Found", 404]; + +// but you will need to declare its type as a tuple. + +const passingResponse: [string, number] = ["{}", 200]; + +// If you hover over the two variable names you can see the +// difference between an array ( (string | number)[] ) and +// the tuple ( [string, number] ). + +// As an array, the order is not important so an item at +// any index could be either a string or a number. In the +// tuple the order and length are guaranteed. + +if (passingResponse[1] === 200) { + const localInfo = JSON.parse(passingResponse[0]); + console.log(localInfo); +} + +// This means TypeScript will provide the correct types at +// the right index, and even raise an error if you try to +// access an object at an un-declared index. + +passingResponse[2]; + +// A tuple can feel like a good pattern for short bits of +// connected data or for fixtures. + +type StaffAccount = [number, string, string, string?]; + +const staff: StaffAccount[] = [ + [0, "Adankwo", "adankwo.e@"], + [1, "Kanokwan", "kanokwan.s@"], + [2, "Aneurin", "aneurin.s@", "Supervisor"] +]; + +// When you have a set of known types at the beginning of a +// tuple and then an unknown length, you can use the spread +// operator to indicate that it can have any length and the +// extra indexes will be of a particular type: + +type PayStubs = [StaffAccount, ...number[]]; + +const payStubs: PayStubs[] = [[staff[0], 250], [staff[1], 250, 260], [staff[0], 300, 300, 300]]; + +const monthOnePayments = payStubs[0][1] + payStubs[1][1] + payStubs[2][1]; +const monthTwoPayments = payStubs[1][2] + payStubs[2][2]; +const monthThreePayments = payStubs[2][2]; + +// You can use tuples to describe functions which take +// an undefined number of parameters with types: + +declare function calculatePayForEmployee(id: number, ...args: [...number[]]): number; + +calculatePayForEmployee(staff[0][0], payStubs[0][1]); +calculatePayForEmployee(staff[1][0], payStubs[1][1], payStubs[1][2]); + +// +// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#tuples-in-rest-parameters-and-spread-expressions +// https://auth0.com/blog/typescript-3-exploring-tuples-the-unknown-type/ diff --git a/packages/examples/scripts/formatExamples.js b/packages/examples/scripts/formatExamples.js new file mode 100644 index 000000000000..9d8691e4bfad --- /dev/null +++ b/packages/examples/scripts/formatExamples.js @@ -0,0 +1,2 @@ +const ts = require("typescript") + diff --git a/packages/examples/scripts/generateTOC.js b/packages/examples/scripts/generateTOC.js new file mode 100644 index 000000000000..df18582c1d23 --- /dev/null +++ b/packages/examples/scripts/generateTOC.js @@ -0,0 +1,160 @@ +// @ts-check + +const { existsSync } = require("fs"); +const { join, dirname, basename} = require("path"); +const { writeFileSync } = require("fs"); +const fs = require("fs"); +const path = require("path"); +const crypto = require("crypto"); + +/** Retrieve file paths from a given folder and its subfolders. */ +// https://gist.github.com/kethinov/6658166#gistcomment-2936675 +const getFilePaths = folderPath => { + const entryPaths = fs.readdirSync(folderPath).map(entry => path.join(folderPath, entry)); + const filePaths = entryPaths.filter(entryPath => fs.statSync(entryPath).isFile()); + const dirPaths = entryPaths.filter(entryPath => !filePaths.includes(entryPath)); + const dirFiles = dirPaths.reduce((prev, curr) => prev.concat(getFilePaths(curr)), []); + return [...filePaths, ...dirFiles]; +}; + +/** + * @typedef {Object} Item - an item in the TOC + * @property {string[]} path - the path to get to this file + * @property {string} name - the filename + * @property {string} id - an id for the slug + * @property {string} title - name + * @property {number} sortIndex - when listing the objects + * @property {string} hash - the md5 of the content + * @property {any} compilerSettings - name + */ + +// * @property {string} body - the text for the example + +const root = join(__dirname, "..", "en"); +const allJS = getFilePaths(join(root, "JavaScript")); +const allTS = getFilePaths(join(root, "TypeScript")); + +const all37Examples = getFilePaths(join(root, "3-7")); + +/** @type {string[]} */ +const all = [...allJS, ...allTS, ...all37Examples].filter(p => p.endsWith(".ts") || p.endsWith(".tsx") || p.endsWith(".js")); + +const examples = all.map(m => { + let contents = fs.readFileSync(m, "utf8"); + const relative = path.relative(root, m); + const title = path + .basename(m) + .split(".") + .slice(0, -1) + .join("."); + let compiler = {}; + let index = 1; + + if (contents.startsWith("//// {")) { + const preJSON = contents.split("//// {")[1].split("}\n")[0]; + contents = contents + .split("\n") + .slice(1) + .join("\n"); + const code = "({" + preJSON + "})"; + + try { + const obj = eval(code); + if (obj.order) { + index = obj.order; + delete obj.order; + } + compiler = obj.compiler; + } catch (err) { + console.error(">>>> " + m); + console.error("Issue with: ", code); + throw err; + } + } + + /** @type Item */ + const item = { + path: dirname(relative).split("/"), + title: title, + name: basename(relative), + id: title + .toLowerCase() + .replace(/[^\x00-\x7F]/g, "-") + .replace(/ /g, "-") + .replace(/\//g, "-") + .replace(/\+/g, "-"), + + // body: contents, + sortIndex: index, + hash: crypto + .createHash("md5") + .update(contents) + .digest("hex"), + + compilerSettings: compiler + }; + + return item; +}); + +const toc = { + sections: [{ + name: "JavaScript", + subtitle: "See how TypeScript improves day to day working with JavaScript with minimal additional syntax." + }, + { + name: "TypeScript", + subtitle: "Explore how TypeScript extends JavaScript to add more safety and tooling." + }, + { + name: "3.7", + subtitle: "See the Beta Release notes.", + whatisnew: true + }], + sortedSubSections: [ + // JS + "JavaScript Essentials", + "Functions with JavaScript", + "Working With Classes", + "Modern JavaScript", + "External APIs", + "Helping with JavaScript", + // TS + "Primitives", + "Type Primitives", + "Meta-Types", + "Language", + "Language Extensions", + // Examples + "Syntax and Messaging", + "Types and Code Flow", + "Fixits" + ], + examples +} + +validateTOC(toc) + +const prodTableOfContentsFile = join(__dirname, "..", "..", "site/examplesTOC.json"); +const devTableOfContentsFile = join(__dirname, "..", "..", "serve/examplesTOC.json"); +if (existsSync( join(__dirname, "..", "..", "site"))) { + writeFileSync(prodTableOfContentsFile, JSON.stringify(toc)); +} +if (existsSync( join(__dirname, "..", "..", "serve"))) { + writeFileSync(devTableOfContentsFile, JSON.stringify(toc)); +} + + +function validateTOC(toc) { + // Ensure all subfolders are in the sorted section + const allSubFolders = [] + all.forEach(path => { + const subPath = dirname(path).split("/").pop() + if (!allSubFolders.includes(subPath)){ allSubFolders.push(subPath) } + }); + allSubFolders.forEach(s => { + if(!toc.sortedSubSections.includes(s)) { + throw new Error("Expected '" + s + "' in " + toc.sortedSubSections) + } + }) +} diff --git a/packages/handbook-v1/en/Advanced Types.md b/packages/handbook-v1/en/Advanced Types.md new file mode 100644 index 000000000000..0f4ea7e02dc2 --- /dev/null +++ b/packages/handbook-v1/en/Advanced Types.md @@ -0,0 +1,1317 @@ +--- +title: Advanced Types +layout: docs +permalink: /docs/handbook/advanced-types.html +--- +{% raw %}# Table of contents + +[Intersection Types](#intersection-types) + +[Union Types](#union-types) + +[Type Guards and Differentiating Types](#type-guards-and-differentiating-types) +* [User-Defined Type Guards](#user-defined-type-guards) + * [Using type predicates](#using-type-predicates) + * [Using the `in` operator](#using-the-in-operator) +* [`typeof` type guards](#typeof-type-guards) +* [`instanceof` type guards](#instanceof-type-guards) + +[Nullable types](#nullable-types) +* [Optional parameters and properties](#optional-parameters-and-properties) +* [Type guards and type assertions](#type-guards-and-type-assertions) + +[Type Aliases](#type-aliases) +* [Interfaces vs. Type Aliases](#interfaces-vs-type-aliases) + +[String Literal Types](#string-literal-types) + +[Numeric Literal Types](#numeric-literal-types) + +[Enum Member Types](#enum-member-types) + +[Discriminated Unions](#discriminated-unions) +* [Exhaustiveness checking](#exhaustiveness-checking) + +[Polymorphic `this` types](#polymorphic-this-types) + +[Index types](#index-types) +* [Index types and index signatures](#index-types-and-index-signatures) + +[Mapped types](#mapped-types) +* [Inference from mapped types](#inference-from-mapped-types) + +[Conditional Types](#conditional-types) +* [Distributive conditional types](#distributive-conditional-types) +* [Type inference in conditional types](#type-inference-in-conditional-types) +* [Predefined conditional types](#predefined-conditional-types) + +# Intersection Types + +An intersection type combines multiple types into one. +This allows you to add together existing types to get a single type that has all the features you need. +For example, `Person & Serializable & Loggable` is a `Person` *and* `Serializable` *and* `Loggable`. +That means an object of this type will have all members of all three types. + +You will mostly see intersection types used for mixins and other concepts that don't fit in the classic object-oriented mold. +(There are a lot of these in JavaScript!) +Here's a simple example that shows how to create a mixin: + +```ts +function extend(first: First, second: Second): First & Second { + const result: Partial = {}; + for (const prop in first) { + if (first.hasOwnProperty(prop)) { + (result as First)[prop] = first[prop]; + } + } + for (const prop in second) { + if (second.hasOwnProperty(prop)) { + (result as Second)[prop] = second[prop]; + } + } + return result as First & Second; +} + +class Person { + constructor(public name: string) { } +} + +interface Loggable { + log(name: string): void; +} + +class ConsoleLogger implements Loggable { + log(name) { + console.log(`Hello, I'm ${name}.`); + } +} + +const jim = extend(new Person('Jim'), ConsoleLogger.prototype); +jim.log(jim.name); +``` + +# Union Types + +Union types are closely related to intersection types, but they are used very differently. +Occasionally, you'll run into a library that expects a parameter to be either a `number` or a `string`. +For instance, take the following function: + +```ts +/** + * Takes a string and adds "padding" to the left. + * If 'padding' is a string, then 'padding' is appended to the left side. + * If 'padding' is a number, then that number of spaces is added to the left side. + */ +function padLeft(value: string, padding: any) { + if (typeof padding === "number") { + return Array(padding + 1).join(" ") + value; + } + if (typeof padding === "string") { + return padding + value; + } + throw new Error(`Expected string or number, got '${padding}'.`); +} + +padLeft("Hello world", 4); // returns " Hello world" +``` + +The problem with `padLeft` is that its `padding` parameter is typed as `any`. +That means that we can call it with an argument that's neither a `number` nor a `string`, but TypeScript will be okay with it. + +```ts +let indentedString = padLeft("Hello world", true); // passes at compile time, fails at runtime. +``` + +In traditional object-oriented code, we might abstract over the two types by creating a hierarchy of types. +While this is much more explicit, it's also a little bit overkill. +One of the nice things about the original version of `padLeft` was that we were able to just pass in primitives. +That meant that usage was simple and concise. +This new approach also wouldn't help if we were just trying to use a function that already exists elsewhere. + +Instead of `any`, we can use a *union type* for the `padding` parameter: + +```ts +/** + * Takes a string and adds "padding" to the left. + * If 'padding' is a string, then 'padding' is appended to the left side. + * If 'padding' is a number, then that number of spaces is added to the left side. + */ +function padLeft(value: string, padding: string | number) { + // ... +} + +let indentedString = padLeft("Hello world", true); // errors during compilation +``` + +A union type describes a value that can be one of several types. +We use the vertical bar (`|`) to separate each type, so `number | string | boolean` is the type of a value that can be a `number`, a `string`, or a `boolean`. + +If we have a value that has a union type, we can only access members that are common to all types in the union. + +```ts +interface Bird { + fly(); + layEggs(); +} + +interface Fish { + swim(); + layEggs(); +} + +function getSmallPet(): Fish | Bird { + // ... +} + +let pet = getSmallPet(); +pet.layEggs(); // okay +pet.swim(); // errors +``` + +Union types can be a bit tricky here, but it just takes a bit of intuition to get used to. +If a value has the type `A | B`, we only know for *certain* that it has members that both `A` *and* `B` have. +In this example, `Bird` has a member named `fly`. +We can't be sure whether a variable typed as `Bird | Fish` has a `fly` method. +If the variable is really a `Fish` at runtime, then calling `pet.fly()` will fail. + +# Type Guards and Differentiating Types + +Union types are useful for modeling situations when values can overlap in the types they can take on. +What happens when we need to know specifically whether we have a `Fish`? +A common idiom in JavaScript to differentiate between two possible values is to check for the presence of a member. +As we mentioned, you can only access members that are guaranteed to be in all the constituents of a union type. + +```ts +let pet = getSmallPet(); + +// Each of these property accesses will cause an error +if (pet.swim) { + pet.swim(); +} +else if (pet.fly) { + pet.fly(); +} +``` + +To get the same code working, we'll need to use a type assertion: + +```ts +let pet = getSmallPet(); + +if ((pet as Fish).swim) { + (pet as Fish).swim(); +} else if ((pet as Bird).fly) { + (pet as Bird).fly(); +} +``` + +## User-Defined Type Guards + +Notice that we had to use type assertions several times. +It would be much better if once we performed the check, we could know the type of `pet` within each branch. + +It just so happens that TypeScript has something called a *type guard*. +A type guard is some expression that performs a runtime check that guarantees the type in some scope. + +### Using type predicates + +To define a type guard, we simply need to define a function whose return type is a *type predicate*: + +```ts +function isFish(pet: Fish | Bird): pet is Fish { + return (pet as Fish).swim !== undefined; +} +``` + +`pet is Fish` is our type predicate in this example. +A predicate takes the form `parameterName is Type`, where `parameterName` must be the name of a parameter from the current function signature. + +Any time `isFish` is called with some variable, TypeScript will *narrow* that variable to that specific type if the original type is compatible. + +```ts +// Both calls to 'swim' and 'fly' are now okay. + +if (isFish(pet)) { + pet.swim(); +} +else { + pet.fly(); +} +``` + +Notice that TypeScript not only knows that `pet` is a `Fish` in the `if` branch; +it also knows that in the `else` branch, you *don't* have a `Fish`, so you must have a `Bird`. + +### Using the `in` operator + +The `in` operator now acts as a narrowing expression for types. + +For a `n in x` expression, where `n` is a string literal or string literal type and `x` is a union type, the "true" branch narrows to types which have an optional or required property `n`, and the "false" branch narrows to types which have an optional or missing property `n`. + +```ts +function move(pet: Fish | Bird) { + if ("swim" in pet) { + return pet.swim(); + } + return pet.fly(); +} +``` + +## `typeof` type guards + +Let's go back and write the code for the version of `padLeft` that uses union types. +We could write it with type predicates as follows: + +```ts +function isNumber(x: any): x is number { + return typeof x === "number"; +} + +function isString(x: any): x is string { + return typeof x === "string"; +} + +function padLeft(value: string, padding: string | number) { + if (isNumber(padding)) { + return Array(padding + 1).join(" ") + value; + } + if (isString(padding)) { + return padding + value; + } + throw new Error(`Expected string or number, got '${padding}'.`); +} +``` + +However, having to define a function to figure out if a type is a primitive is kind of a pain. +Luckily, you don't need to abstract `typeof x === "number"` into its own function because TypeScript will recognize it as a type guard on its own. +That means we could just write these checks inline. + +```ts +function padLeft(value: string, padding: string | number) { + if (typeof padding === "number") { + return Array(padding + 1).join(" ") + value; + } + if (typeof padding === "string") { + return padding + value; + } + throw new Error(`Expected string or number, got '${padding}'.`); +} +``` + +These *`typeof` type guards* are recognized in two different forms: `typeof v === "typename"` and `typeof v !== "typename"`, where `"typename"` must be `"number"`, `"string"`, `"boolean"`, or `"symbol"`. +While TypeScript won't stop you from comparing to other strings, the language won't recognize those expressions as type guards. + +## `instanceof` type guards + +If you've read about `typeof` type guards and are familiar with the `instanceof` operator in JavaScript, you probably have some idea of what this section is about. + +*`instanceof` type guards* are a way of narrowing types using their constructor function. +For instance, let's borrow our industrial string-padder example from earlier: + +```ts +interface Padder { + getPaddingString(): string +} + +class SpaceRepeatingPadder implements Padder { + constructor(private numSpaces: number) { } + getPaddingString() { + return Array(this.numSpaces + 1).join(" "); + } +} + +class StringPadder implements Padder { + constructor(private value: string) { } + getPaddingString() { + return this.value; + } +} + +function getRandomPadder() { + return Math.random() < 0.5 ? + new SpaceRepeatingPadder(4) : + new StringPadder(" "); +} + +// Type is 'SpaceRepeatingPadder | StringPadder' +let padder: Padder = getRandomPadder(); + +if (padder instanceof SpaceRepeatingPadder) { + padder; // type narrowed to 'SpaceRepeatingPadder' +} +if (padder instanceof StringPadder) { + padder; // type narrowed to 'StringPadder' +} +``` + +The right side of the `instanceof` needs to be a constructor function, and TypeScript will narrow down to: + +1. the type of the function's `prototype` property if its type is not `any` +2. the union of types returned by that type's construct signatures + +in that order. + +# Nullable types + +TypeScript has two special types, `null` and `undefined`, that have the values null and undefined respectively. +We mentioned these briefly in [the Basic Types section](./Basic%20Types.md). +By default, the type checker considers `null` and `undefined` assignable to anything. +Effectively, `null` and `undefined` are valid values of every type. +That means it's not possible to *stop* them from being assigned to any type, even when you would like to prevent it. +The inventor of `null`, Tony Hoare, calls this his ["billion dollar mistake"](https://en.wikipedia.org/wiki/Null_pointer#History). + +The `--strictNullChecks` flag fixes this: when you declare a variable, it doesn't automatically include `null` or `undefined`. +You can include them explicitly using a union type: + +```ts +let s = "foo"; +s = null; // error, 'null' is not assignable to 'string' +let sn: string | null = "bar"; +sn = null; // ok + +sn = undefined; // error, 'undefined' is not assignable to 'string | null' +``` + +Note that TypeScript treats `null` and `undefined` differently in order to match JavaScript semantics. +`string | null` is a different type than `string | undefined` and `string | undefined | null`. + +## Optional parameters and properties + +With `--strictNullChecks`, an optional parameter automatically adds `| undefined`: + +```ts +function f(x: number, y?: number) { + return x + (y || 0); +} +f(1, 2); +f(1); +f(1, undefined); +f(1, null); // error, 'null' is not assignable to 'number | undefined' +``` + +The same is true for optional properties: + +```ts +class C { + a: number; + b?: number; +} +let c = new C(); +c.a = 12; +c.a = undefined; // error, 'undefined' is not assignable to 'number' +c.b = 13; +c.b = undefined; // ok +c.b = null; // error, 'null' is not assignable to 'number | undefined' +``` + +## Type guards and type assertions + +Since nullable types are implemented with a union, you need to use a type guard to get rid of the `null`. +Fortunately, this is the same code you'd write in JavaScript: + +```ts +function f(sn: string | null): string { + if (sn == null) { + return "default"; + } + else { + return sn; + } +} +``` + +The `null` elimination is pretty obvious here, but you can use terser operators too: + +```ts +function f(sn: string | null): string { + return sn || "default"; +} +``` + +In cases where the compiler can't eliminate `null` or `undefined`, you can use the type assertion operator to manually remove them. +The syntax is postfix `!`: `identifier!` removes `null` and `undefined` from the type of `identifier`: + +```ts +function broken(name: string | null): string { + function postfix(epithet: string) { + return name.charAt(0) + '. the ' + epithet; // error, 'name' is possibly null + } + name = name || "Bob"; + return postfix("great"); +} + +function fixed(name: string | null): string { + function postfix(epithet: string) { + return name!.charAt(0) + '. the ' + epithet; // ok + } + name = name || "Bob"; + return postfix("great"); +} +``` + +The example uses a nested function here because the compiler can't eliminate nulls inside a nested function (except immediately-invoked function expressions). +That's because it can't track all calls to the nested function, especially if you return it from the outer function. +Without knowing where the function is called, it can't know what the type of `name` will be at the time the body executes. + +# Type Aliases + +Type aliases create a new name for a type. +Type aliases are sometimes similar to interfaces, but can name primitives, unions, tuples, and any other types that you'd otherwise have to write by hand. + +```ts +type Name = string; +type NameResolver = () => string; +type NameOrResolver = Name | NameResolver; +function getName(n: NameOrResolver): Name { + if (typeof n === "string") { + return n; + } + else { + return n(); + } +} +``` + +Aliasing doesn't actually create a new type - it creates a new *name* to refer to that type. +Aliasing a primitive is not terribly useful, though it can be used as a form of documentation. + +Just like interfaces, type aliases can also be generic - we can just add type parameters and use them on the right side of the alias declaration: + +```ts +type Container = { value: T }; +``` + +We can also have a type alias refer to itself in a property: + +```ts +type Tree = { + value: T; + left: Tree; + right: Tree; +} +``` + +Together with intersection types, we can make some pretty mind-bending types: + +```ts +type LinkedList = T & { next: LinkedList }; + +interface Person { + name: string; +} + +var people: LinkedList; +var s = people.name; +var s = people.next.name; +var s = people.next.next.name; +var s = people.next.next.next.name; +``` + +However, it's not possible for a type alias to appear anywhere else on the right side of the declaration: + +```ts +type Yikes = Array; // error +``` + +## Interfaces vs. Type Aliases + +As we mentioned, type aliases can act sort of like interfaces; however, there are some subtle differences. + +One difference is that interfaces create a new name that is used everywhere. +Type aliases don't create a new name — for instance, error messages won't use the alias name. +In the code below, hovering over `interfaced` in an editor will show that it returns an `Interface`, but will show that `aliased` returns object literal type. + +```ts +type Alias = { num: number } +interface Interface { + num: number; +} +declare function aliased(arg: Alias): Alias; +declare function interfaced(arg: Interface): Interface; +``` + +in older versions of TypeScript, type aliases couldn't be extended or implemented from (nor could they extend/implement other types). As of version 2.7, type aliases can be extended by creating a new intersection type e.g. `type Cat = Animal & { purrs: true }`. + +Because [an ideal property of software is being open to extension](https://en.wikipedia.org/wiki/Open/closed_principle), you should always use an interface over a type alias if possible. + +On the other hand, if you can't express some shape with an interface and you need to use a union or tuple type, type aliases are usually the way to go. + +# String Literal Types + +String literal types allow you to specify the exact value a string must have. +In practice string literal types combine nicely with union types, type guards, and type aliases. +You can use these features together to get enum-like behavior with strings. + +```ts +type Easing = "ease-in" | "ease-out" | "ease-in-out"; +class UIElement { + animate(dx: number, dy: number, easing: Easing) { + if (easing === "ease-in") { + // ... + } + else if (easing === "ease-out") { + } + else if (easing === "ease-in-out") { + } + else { + // error! should not pass null or undefined. + } + } +} + +let button = new UIElement(); +button.animate(0, 0, "ease-in"); +button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here +``` + +You can pass any of the three allowed strings, but any other string will give the error + +```text +Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" | "ease-out" | "ease-in-out"' +``` + +String literal types can be used in the same way to distinguish overloads: + +```ts +function createElement(tagName: "img"): HTMLImageElement; +function createElement(tagName: "input"): HTMLInputElement; +// ... more overloads ... +function createElement(tagName: string): Element { + // ... code goes here ... +} +``` + +# Numeric Literal Types + +TypeScript also has numeric literal types. + +```ts +function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 { + // ... +} +``` + +These are seldom written explicitly, they can be useful when narrowing can catch bugs: + +```ts +function foo(x: number) { + if (x !== 1 || x !== 2) { + // ~~~~~~~ + // Operator '!==' cannot be applied to types '1' and '2'. + } +} +``` + +In other words, `x` must be `1` when it gets compared to `2`, meaning that the above check is making an invalid comparison. + +# Enum Member Types + +As mentioned in [our section on enums](./Enums.md#union-enums-and-enum-member-types), enum members have types when every member is literal-initialized. + +Much of the time when we talk about "singleton types", we're referring to both enum member types as well as numeric/string literal types, though many users will use "singleton types" and "literal types" interchangeably. + +# Discriminated Unions + +You can combine singleton types, union types, type guards, and type aliases to build an advanced pattern called *discriminated unions*, also known as *tagged unions* or *algebraic data types*. +Discriminated unions are useful in functional programming. +Some languages automatically discriminate unions for you; TypeScript instead builds on JavaScript patterns as they exist today. +There are three ingredients: + +1. Types that have a common, singleton type property — the *discriminant*. +2. A type alias that takes the union of those types — the *union*. +3. Type guards on the common property. + +```ts +interface Square { + kind: "square"; + size: number; +} +interface Rectangle { + kind: "rectangle"; + width: number; + height: number; +} +interface Circle { + kind: "circle"; + radius: number; +} +``` + +First we declare the interfaces we will union. +Each interface has a `kind` property with a different string literal type. +The `kind` property is called the *discriminant* or *tag*. +The other properties are specific to each interface. +Notice that the interfaces are currently unrelated. +Let's put them into a union: + +```ts +type Shape = Square | Rectangle | Circle; +``` + +Now let's use the discriminated union: + +```ts +function area(s: Shape) { + switch (s.kind) { + case "square": return s.size * s.size; + case "rectangle": return s.height * s.width; + case "circle": return Math.PI * s.radius ** 2; + } +} +``` + +## Exhaustiveness checking + +We would like the compiler to tell us when we don't cover all variants of the discriminated union. +For example, if we add `Triangle` to `Shape`, we need to update `area` as well: + +```ts +type Shape = Square | Rectangle | Circle | Triangle; +function area(s: Shape) { + switch (s.kind) { + case "square": return s.size * s.size; + case "rectangle": return s.height * s.width; + case "circle": return Math.PI * s.radius ** 2; + } + // should error here - we didn't handle case "triangle" +} +``` + +There are two ways to do this. +The first is to turn on `--strictNullChecks` and specify a return type: + +```ts +function area(s: Shape): number { // error: returns number | undefined + switch (s.kind) { + case "square": return s.size * s.size; + case "rectangle": return s.height * s.width; + case "circle": return Math.PI * s.radius ** 2; + } +} +``` + +Because the `switch` is no longer exhaustive, TypeScript is aware that the function could sometimes return `undefined`. +If you have an explicit return type `number`, then you will get an error that the return type is actually `number | undefined`. +However, this method is quite subtle and, besides, `--strictNullChecks` does not always work with old code. + +The second method uses the `never` type that the compiler uses to check for exhaustiveness: + +```ts +function assertNever(x: never): never { + throw new Error("Unexpected object: " + x); +} +function area(s: Shape) { + switch (s.kind) { + case "square": return s.size * s.size; + case "rectangle": return s.height * s.width; + case "circle": return Math.PI * s.radius ** 2; + default: return assertNever(s); // error here if there are missing cases + } +} +``` + +Here, `assertNever` checks that `s` is of type `never` — the type that's left after all other cases have been removed. +If you forget a case, then `s` will have a real type and you will get a type error. +This method requires you to define an extra function, but it's much more obvious when you forget it. + +# Polymorphic `this` types + +A polymorphic `this` type represents a type that is the *subtype* of the containing class or interface. +This is called *F*-bounded polymorphism. +This makes hierarchical fluent interfaces much easier to express, for example. +Take a simple calculator that returns `this` after each operation: + +```ts +class BasicCalculator { + public constructor(protected value: number = 0) { } + public currentValue(): number { + return this.value; + } + public add(operand: number): this { + this.value += operand; + return this; + } + public multiply(operand: number): this { + this.value *= operand; + return this; + } + // ... other operations go here ... +} + +let v = new BasicCalculator(2) + .multiply(5) + .add(1) + .currentValue(); +``` + +Since the class uses `this` types, you can extend it and the new class can use the old methods with no changes. + +```ts +class ScientificCalculator extends BasicCalculator { + public constructor(value = 0) { + super(value); + } + public sin() { + this.value = Math.sin(this.value); + return this; + } + // ... other operations go here ... +} + +let v = new ScientificCalculator(2) + .multiply(5) + .sin() + .add(1) + .currentValue(); +``` + +Without `this` types, `ScientificCalculator` would not have been able to extend `BasicCalculator` and keep the fluent interface. +`multiply` would have returned `BasicCalculator`, which doesn't have the `sin` method. +However, with `this` types, `multiply` returns `this`, which is `ScientificCalculator` here. + +# Index types + +With index types, you can get the compiler to check code that uses dynamic property names. +For example, a common JavaScript pattern is to pick a subset of properties from an object: + +```js +function pluck(o, propertyNames) { + return propertyNames.map(n => o[n]); +} +``` + +Here's how you would write and use this function in TypeScript, using the **index type query** and **indexed access** operators: + +```ts +function pluck(o: T, propertyNames: K[]): T[K][] { + return propertyNames.map(n => o[n]); +} + +interface Car { + manufacturer: string; + model: string; + year: number; +} +let taxi: Car = { + manufacturer: 'Toyota', + model: 'Camry', + year: 2014 +}; + +// Manufacturer and model are both of type string, +// so we can pluck them both into a typed string array +let makeAndModel: string[] = pluck(taxi, ['manufacturer', 'model']); + +// If we try to pluck model and year, we get an +// array of a union type: (string | number)[] +let modelYear = pluck(taxi, ['model', 'year']) +``` + +The compiler checks that `manufacturer` and `model` are actually properties on `Car`. +The example introduces a couple of new type operators. +First is `keyof T`, the **index type query operator**. +For any type `T`, `keyof T` is the union of known, public property names of `T`. +For example: + +```ts +let carProps: keyof Car; // the union of ('manufacturer' | 'model' | 'year') +``` + +`keyof Car` is completely interchangeable with `'manufacturer' | 'model' | 'year'`. +The difference is that if you add another property to `Car`, say `ownersAddress: string`, then `keyof Car` will automatically update to be `'manufacturer' | 'model' | 'year' | 'ownersAddress'`. +And you can use `keyof` in generic contexts like `pluck`, where you can't possibly know the property names ahead of time. +That means the compiler will check that you pass the right set of property names to `pluck`: + +```ts +// error, 'unknown' is not in 'manufacturer' | 'model' | 'year' +pluck(taxi, ['year', 'unknown']); / +``` + +The second operator is `T[K]`, the **indexed access operator**. +Here, the type syntax reflects the expression syntax. +That means that `person['name']` has the type `Person['name']` — which in our example is just `string`. +However, just like index type queries, you can use `T[K]` in a generic context, which is where its real power comes to life. +You just have to make sure that the type variable `K extends keyof T`. +Here's another example with a function named `getProperty`. + +```ts +function getProperty(o: T, propertyName: K): T[K] { + return o[propertyName]; // o[propertyName] is of type T[K] +} +``` + +In `getProperty`, `o: T` and `propertyName: K`, so that means `o[propertyName]: T[K]`. +Once you return the `T[K]` result, the compiler will instantiate the actual type of the key, so the return type of `getProperty` will vary according to which property you request. + +```ts +let name: string = getProperty(taxi, 'manufacturer'); +let year: number = getProperty(taxi, 'year'); + +// error, 'unknown' is not in 'manufacturer' | 'model' | 'year' +let unknown = getProperty(taxi, 'unknown'); +``` + +## Index types and index signatures + +`keyof` and `T[K]` interact with index signatures. An index signature parameter type must be 'string' or 'number'. +If you have a type with a string index signature, `keyof T` will be `string | number` +(and not just `string`, since in JavaScript you can access an object property either +by using strings (`object['42'`]) or numbers (`object[42]`)). +And `T[string]` is just the type of the index signature: + +```ts +interface Dictionary { + [key: string]: T; +} +let keys: keyof Dictionary; // string | number +let value: Dictionary['foo']; // number +``` + +If you have a type with a number index signature, `keyof T` will just be `number`. + +```ts +interface Dictionary { + [key: number]: T; +} +let keys: keyof Dictionary; // number +let value: Dictionary['foo']; // Error, Property 'foo' does not exist on type 'Dictionary'. +let value: Dictionary[42]; // number +``` + +# Mapped types + +A common task is to take an existing type and make each of its properties optional: + +```ts +interface PersonPartial { + name?: string; + age?: number; +} +``` + +Or we might want a readonly version: + +```ts +interface PersonReadonly { + readonly name: string; + readonly age: number; +} +``` + +This happens often enough in JavaScript that TypeScript provides a way to create new types based on old types — **mapped types**. +In a mapped type, the new type transforms each property in the old type in the same way. +For example, you can make all properties of a type `readonly` or optional. +Here are a couple of examples: + +```ts +type Readonly = { + readonly [P in keyof T]: T[P]; +} +type Partial = { + [P in keyof T]?: T[P]; +} +``` + +And to use it: + +```ts +type PersonPartial = Partial; +type ReadonlyPerson = Readonly; +``` + +Note that this syntax describes a type rather than a member. +If you want to add members, you can use an intersection type: + +```ts +// Use this: +type PartialWithNewMember = { + [P in keyof T]?: T[P]; +} & { newMember: boolean } + +// **Do not** use the following! +// This is an error! +type PartialWithNewMember = { + [P in keyof T]?: T[P]; + newMember: boolean; +} +``` + +Let's take a look at the simplest mapped type and its parts: + +```ts +type Keys = 'option1' | 'option2'; +type Flags = { [K in Keys]: boolean }; +``` + +The syntax resembles the syntax for index signatures with a `for .. in` inside. +There are three parts: + +1. The type variable `K`, which gets bound to each property in turn. +2. The string literal union `Keys`, which contains the names of properties to iterate over. +3. The resulting type of the property. + +In this simple example, `Keys` is a hard-coded list of property names and the property type is always `boolean`, so this mapped type is equivalent to writing: + +```ts +type Flags = { + option1: boolean; + option2: boolean; +} +``` + +Real applications, however, look like `Readonly` or `Partial` above. +They're based on some existing type, and they transform the properties in some way. +That's where `keyof` and indexed access types come in: + +```ts +type NullablePerson = { [P in keyof Person]: Person[P] | null } +type PartialPerson = { [P in keyof Person]?: Person[P] } +``` + +But it's more useful to have a general version. + +```ts +type Nullable = { [P in keyof T]: T[P] | null } +type Partial = { [P in keyof T]?: T[P] } +``` + +In these examples, the properties list is `keyof T` and the resulting type is some variant of `T[P]`. +This is a good template for any general use of mapped types. +That's because this kind of transformation is [homomorphic](https://en.wikipedia.org/wiki/Homomorphism), which means that the mapping applies only to properties of `T` and no others. +The compiler knows that it can copy all the existing property modifiers before adding any new ones. +For example, if `Person.name` was readonly, `Partial.name` would be readonly and optional. + +Here's one more example, in which `T[P]` is wrapped in a `Proxy` class: + +```ts +type Proxy = { + get(): T; + set(value: T): void; +} +type Proxify = { + [P in keyof T]: Proxy; +} +function proxify(o: T): Proxify { + // ... wrap proxies ... +} +let proxyProps = proxify(props); +``` + +Note that `Readonly` and `Partial` are so useful, they are included in TypeScript's standard library along with `Pick` and `Record`: + +```ts +type Pick = { + [P in K]: T[P]; +} +type Record = { + [P in K]: T; +} +``` + +`Readonly`, `Partial` and `Pick` are homomorphic whereas `Record` is not. +One clue that `Record` is not homomorphic is that it doesn't take an input type to copy properties from: + +```ts +type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string> +``` + +Non-homomorphic types are essentially creating new properties, so they can't copy property modifiers from anywhere. + +## Inference from mapped types + +Now that you know how to wrap the properties of a type, the next thing you'll want to do is unwrap them. +Fortunately, that's pretty easy: + +```ts +function unproxify(t: Proxify): T { + let result = {} as T; + for (const k in t) { + result[k] = t[k].get(); + } + return result; +} + +let originalProps = unproxify(proxyProps); +``` + +Note that this unwrapping inference only works on homomorphic mapped types. +If the mapped type is not homomorphic you'll have to give an explicit type parameter to your unwrapping function. + +# Conditional Types + +TypeScript 2.8 introduces *conditional types* which add the ability to express non-uniform type mappings. +A conditional type selects one of two possible types based on a condition expressed as a type relationship test: + +```ts +T extends U ? X : Y +``` + +The type above means when `T` is assignable to `U` the type is `X`, otherwise the type is `Y`. + +A conditional type `T extends U ? X : Y` is either *resolved* to `X` or `Y`, or *deferred* because the condition depends on one or more type variables. +When `T` or `U` contains type variables, whether to resolve to `X` or `Y`, or to defer, is determined by whether or not the type system has enough information to conclude that `T` is always assignable to `U`. + +As an example of some types that are immediately resolved, we can take a look at the following example: + +```ts +declare function f(x: T): T extends true ? string : number; + +// Type is 'string | number +let x = f(Math.random() < 0.5) + +``` + +Another example would be the `TypeName` type alias, which uses nested conditional types: + +```ts +type TypeName = + T extends string ? "string" : + T extends number ? "number" : + T extends boolean ? "boolean" : + T extends undefined ? "undefined" : + T extends Function ? "function" : + "object"; + +type T0 = TypeName; // "string" +type T1 = TypeName<"a">; // "string" +type T2 = TypeName; // "boolean" +type T3 = TypeName<() => void>; // "function" +type T4 = TypeName; // "object" +``` + +But as an example of a place where conditional types are deferred - where they stick around instead of picking a branch - would be in the following: + +```ts +interface Foo { + propA: boolean; + propB: boolean; +} + +declare function f(x: T): T extends Foo ? string : number; + +function foo(x: U) { + // Has type 'U extends Foo ? string : number' + let a = f(x); + + // This assignment is allowed though! + let b: string | number = a; +} +``` + +In the above, the variable `a` has a conditional type that hasn't yet chosen a branch. +When another piece of code ends up calling `foo`, it will substitute in `U` with some other type, and TypeScript will re-evaluate the conditional type, deciding whether it can actually pick a branch. + +In the meantime, we can assign a conditional type to any other target type as long as each branch of the conditional is assignable to that target. +So in our example above we were able to assign `U extends Foo ? string : number` to `string | number` since no matter what the conditional evaluates to, it's known to be either `string` or `number`. + +## Distributive conditional types + +Conditional types in which the checked type is a naked type parameter are called *distributive conditional types*. +Distributive conditional types are automatically distributed over union types during instantiation. +For example, an instantiation of `T extends U ? X : Y` with the type argument `A | B | C` for `T` is resolved as `(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)`. + +### Example + +```ts +type T10 = TypeName void)>; // "string" | "function" +type T12 = TypeName; // "string" | "object" | "undefined" +type T11 = TypeName; // "object" +``` + +In instantiations of a distributive conditional type `T extends U ? X : Y`, references to `T` within the conditional type are resolved to individual constituents of the union type (i.e. `T` refers to the individual constituents *after* the conditional type is distributed over the union type). +Furthermore, references to `T` within `X` have an additional type parameter constraint `U` (i.e. `T` is considered assignable to `U` within `X`). + +### Example + +```ts +type BoxedValue = { value: T }; +type BoxedArray = { array: T[] }; +type Boxed = T extends any[] ? BoxedArray : BoxedValue; + +type T20 = Boxed; // BoxedValue; +type T21 = Boxed; // BoxedArray; +type T22 = Boxed; // BoxedValue | BoxedArray; +``` + +Notice that `T` has the additional constraint `any[]` within the true branch of `Boxed` and it is therefore possible to refer to the element type of the array as `T[number]`. Also, notice how the conditional type is distributed over the union type in the last example. + +The distributive property of conditional types can conveniently be used to *filter* union types: + +```ts +type Diff = T extends U ? never : T; // Remove types from T that are assignable to U +type Filter = T extends U ? T : never; // Remove types from T that are not assignable to U + +type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d" +type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c" +type T32 = Diff void), Function>; // string | number +type T33 = Filter void), Function>; // () => void + +type NonNullable = Diff; // Remove null and undefined from T + +type T34 = NonNullable; // string | number +type T35 = NonNullable; // string | string[] + +function f1(x: T, y: NonNullable) { + x = y; // Ok + y = x; // Error +} + +function f2(x: T, y: NonNullable) { + x = y; // Ok + y = x; // Error + let s1: string = x; // Error + let s2: string = y; // Ok +} +``` + +Conditional types are particularly useful when combined with mapped types: + +```ts +type FunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]; +type FunctionProperties = Pick>; + +type NonFunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]; +type NonFunctionProperties = Pick>; + +interface Part { + id: number; + name: string; + subparts: Part[]; + updatePart(newName: string): void; +} + +type T40 = FunctionPropertyNames; // "updatePart" +type T41 = NonFunctionPropertyNames; // "id" | "name" | "subparts" +type T42 = FunctionProperties; // { updatePart(newName: string): void } +type T43 = NonFunctionProperties; // { id: number, name: string, subparts: Part[] } +``` + +Similar to union and intersection types, conditional types are not permitted to reference themselves recursively. +For example the following is an error. + +### Example + +```ts +type ElementType = T extends any[] ? ElementType : T; // Error +``` + +## Type inference in conditional types + +Within the `extends` clause of a conditional type, it is now possible to have `infer` declarations that introduce a type variable to be inferred. +Such inferred type variables may be referenced in the true branch of the conditional type. +It is possible to have multiple `infer` locations for the same type variable. + +For example, the following extracts the return type of a function type: + +```ts +type ReturnType = T extends (...args: any[]) => infer R ? R : any; +``` + +Conditional types can be nested to form a sequence of pattern matches that are evaluated in order: + +```ts +type Unpacked = + T extends (infer U)[] ? U : + T extends (...args: any[]) => infer U ? U : + T extends Promise ? U : + T; + +type T0 = Unpacked; // string +type T1 = Unpacked; // string +type T2 = Unpacked<() => string>; // string +type T3 = Unpacked>; // string +type T4 = Unpacked[]>; // Promise +type T5 = Unpacked[]>>; // string +``` + +The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred: + +```ts +type Foo = T extends { a: infer U, b: infer U } ? U : never; +type T10 = Foo<{ a: string, b: string }>; // string +type T11 = Foo<{ a: string, b: number }>; // string | number +``` + +Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred: + +```ts +type Bar = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never; +type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string +type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number +``` + +When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the *last* signature (which, presumably, is the most permissive catch-all case). +It is not possible to perform overload resolution based on a list of argument types. + +```ts +declare function foo(x: string): number; +declare function foo(x: number): string; +declare function foo(x: string | number): string | number; +type T30 = ReturnType; // string | number +``` + +It is not possible to use `infer` declarations in constraint clauses for regular type parameters: + +```ts +type ReturnType infer R> = R; // Error, not supported +``` + +However, much the same effect can be obtained by erasing the type variables in the constraint and instead specifying a conditional type: + +```ts +type AnyFunction = (...args: any[]) => any; +type ReturnType = T extends (...args: any[]) => infer R ? R : any; +``` + +## Predefined conditional types + +TypeScript 2.8 adds several predefined conditional types to `lib.d.ts`: + +* `Exclude` -- Exclude from `T` those types that are assignable to `U`. +* `Extract` -- Extract from `T` those types that are assignable to `U`. +* `NonNullable` -- Exclude `null` and `undefined` from `T`. +* `ReturnType` -- Obtain the return type of a function type. +* `InstanceType` -- Obtain the instance type of a constructor function type. + +### Example + +```ts +type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d" +type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c" + +type T02 = Exclude void), Function>; // string | number +type T03 = Extract void), Function>; // () => void + +type T04 = NonNullable; // string | number +type T05 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[] + +function f1(s: string) { + return { a: 1, b: s }; +} + +class C { + x = 0; + y = 0; +} + +type T10 = ReturnType<() => string>; // string +type T11 = ReturnType<(s: string) => void>; // void +type T12 = ReturnType<(() => T)>; // {} +type T13 = ReturnType<(() => T)>; // number[] +type T14 = ReturnType; // { a: number, b: string } +type T15 = ReturnType; // any +type T16 = ReturnType; // never +type T17 = ReturnType; // Error +type T18 = ReturnType; // Error + +type T20 = InstanceType; // C +type T21 = InstanceType; // any +type T22 = InstanceType; // never +type T23 = InstanceType; // Error +type T24 = InstanceType; // Error +``` + +> Note: The `Exclude` type is a proper implementation of the `Diff` type suggested [here](https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458). We've used the name `Exclude` to avoid breaking existing code that defines a `Diff`, plus we feel that name better conveys the semantics of the type. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Basic Types.md b/packages/handbook-v1/en/Basic Types.md new file mode 100644 index 000000000000..be4cbb52029d --- /dev/null +++ b/packages/handbook-v1/en/Basic Types.md @@ -0,0 +1,300 @@ +--- +title: Basic Types +layout: docs +permalink: /docs/handbook/basic-types.html +--- +{% raw %}# Introduction + +For programs to be useful, we need to be able to work with some of the simplest units of data: numbers, strings, structures, boolean values, and the like. +In TypeScript, we support much the same types as you would expect in JavaScript, with a convenient enumeration type thrown in to help things along. + +# Boolean + +The most basic datatype is the simple true/false value, which JavaScript and TypeScript call a `boolean` value. + +```ts +let isDone: boolean = false; +``` + +# Number + +As in JavaScript, all numbers in TypeScript are floating point values. +These floating point numbers get the type `number`. +In addition to hexadecimal and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015. + +```ts +let decimal: number = 6; +let hex: number = 0xf00d; +let binary: number = 0b1010; +let octal: number = 0o744; +``` + +# String + +Another fundamental part of creating programs in JavaScript for webpages and servers alike is working with textual data. +As in other languages, we use the type `string` to refer to these textual datatypes. +Just like JavaScript, TypeScript also uses double quotes (`"`) or single quotes (`'`) to surround string data. + +```ts +let color: string = "blue"; +color = 'red'; +``` + +You can also use *template strings*, which can span multiple lines and have embedded expressions. +These strings are surrounded by the backtick/backquote (`` ` ``) character, and embedded expressions are of the form `${ expr }`. + +```ts +let fullName: string = `Bob Bobbington`; +let age: number = 37; +let sentence: string = `Hello, my name is ${ fullName }. + +I'll be ${ age + 1 } years old next month.`; +``` + +This is equivalent to declaring `sentence` like so: + +```ts +let sentence: string = "Hello, my name is " + fullName + ".\n\n" + + "I'll be " + (age + 1) + " years old next month."; +``` + +# Array + +TypeScript, like JavaScript, allows you to work with arrays of values. +Array types can be written in one of two ways. +In the first, you use the type of the elements followed by `[]` to denote an array of that element type: + +```ts +let list: number[] = [1, 2, 3]; +``` + +The second way uses a generic array type, `Array`: + +```ts +let list: Array = [1, 2, 3]; +``` + +# Tuple + +Tuple types allow you to express an array with a fixed number of elements whose types are known, but need not be the same. For example, you may want to represent a value as a pair of a `string` and a `number`: + +```ts +// Declare a tuple type +let x: [string, number]; +// Initialize it +x = ["hello", 10]; // OK +// Initialize it incorrectly +x = [10, "hello"]; // Error +``` + +When accessing an element with a known index, the correct type is retrieved: + +```ts +console.log(x[0].substring(1)); // OK +console.log(x[1].substring(1)); // Error, 'number' does not have 'substring' +``` + +Accessing an element outside the set of known indices fails with an error: + +```ts +x[3] = "world"; // Error, Property '3' does not exist on type '[string, number]'. + +console.log(x[5].toString()); // Error, Property '5' does not exist on type '[string, number]'. +``` + +# Enum + +A helpful addition to the standard set of datatypes from JavaScript is the `enum`. +As in languages like C#, an enum is a way of giving more friendly names to sets of numeric values. + +```ts +enum Color {Red, Green, Blue} +let c: Color = Color.Green; +``` + +By default, enums begin numbering their members starting at `0`. +You can change this by manually setting the value of one of its members. +For example, we can start the previous example at `1` instead of `0`: + +```ts +enum Color {Red = 1, Green, Blue} +let c: Color = Color.Green; +``` + +Or, even manually set all the values in the enum: + +```ts +enum Color {Red = 1, Green = 2, Blue = 4} +let c: Color = Color.Green; +``` + +A handy feature of enums is that you can also go from a numeric value to the name of that value in the enum. +For example, if we had the value `2` but weren't sure what that mapped to in the `Color` enum above, we could look up the corresponding name: + +```ts +enum Color {Red = 1, Green, Blue} +let colorName: string = Color[2]; + +console.log(colorName); // Displays 'Green' as its value is 2 above +``` + +# Any + +We may need to describe the type of variables that we do not know when we are writing an application. +These values may come from dynamic content, e.g. from the user or a 3rd party library. +In these cases, we want to opt-out of type checking and let the values pass through compile-time checks. +To do so, we label these with the `any` type: + +```ts +let notSure: any = 4; +notSure = "maybe a string instead"; +notSure = false; // okay, definitely a boolean +``` + +The `any` type is a powerful way to work with existing JavaScript, allowing you to gradually opt-in and opt-out of type checking during compilation. +You might expect `Object` to play a similar role, as it does in other languages. +However, variables of type `Object` only allow you to assign any value to them. You can't call arbitrary methods on them, even ones that actually exist: + +```ts +let notSure: any = 4; +notSure.ifItExists(); // okay, ifItExists might exist at runtime +notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check) + +let prettySure: Object = 4; +prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'. +``` + +The `any` type is also handy if you know some part of the type, but perhaps not all of it. +For example, you may have an array but the array has a mix of different types: + +```ts +let list: any[] = [1, true, "free"]; + +list[1] = 100; +``` + +# Void + +`void` is a little like the opposite of `any`: the absence of having any type at all. +You may commonly see this as the return type of functions that do not return a value: + +```ts +function warnUser(): void { + console.log("This is my warning message"); +} +``` + +Declaring variables of type `void` is not useful because you can only assign `null` (only if `--strictNullChecks` is not specified, see next section) or `undefined` to them: + +```ts +let unusable: void = undefined; +unusable = null; // OK if `--strictNullChecks` is not given +``` + +# Null and Undefined + +In TypeScript, both `undefined` and `null` actually have their own types named `undefined` and `null` respectively. +Much like `void`, they're not extremely useful on their own: + +```ts +// Not much else we can assign to these variables! +let u: undefined = undefined; +let n: null = null; +``` + +By default `null` and `undefined` are subtypes of all other types. +That means you can assign `null` and `undefined` to something like `number`. + +However, when using the `--strictNullChecks` flag, `null` and `undefined` are only assignable to `any` and their respective types (the one exception being that `undefined` is also assignable to `void`). +This helps avoid *many* common errors. +In cases where you want to pass in either a `string` or `null` or `undefined`, you can use the union type `string | null | undefined`. + +Union types are an advanced topic that we'll cover in a later chapter. + +> As a note: we encourage the use of `--strictNullChecks` when possible, but for the purposes of this handbook, we will assume it is turned off. + +# Never + +The `never` type represents the type of values that never occur. +For instance, `never` is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns; +Variables also acquire the type `never` when narrowed by any type guards that can never be true. + +The `never` type is a subtype of, and assignable to, every type; however, *no* type is a subtype of, or assignable to, `never` (except `never` itself). +Even `any` isn't assignable to `never`. + +Some examples of functions returning `never`: + +```ts +// Function returning never must have unreachable end point +function error(message: string): never { + throw new Error(message); +} + +// Inferred return type is never +function fail() { + return error("Something failed"); +} + +// Function returning never must have unreachable end point +function infiniteLoop(): never { + while (true) { + } +} +``` + +# Object + +`object` is a type that represents the non-primitive type, i.e. anything that is not `number`, `string`, `boolean`, `symbol`, `null`, or `undefined`. + +With `object` type, APIs like `Object.create` can be better represented. For example: + +```ts +declare function create(o: object | null): void; + +create({ prop: 0 }); // OK +create(null); // OK + +create(42); // Error +create("string"); // Error +create(false); // Error +create(undefined); // Error +``` + +# Type assertions + +Sometimes you'll end up in a situation where you'll know more about a value than TypeScript does. +Usually this will happen when you know the type of some entity could be more specific than its current type. + +*Type assertions* are a way to tell the compiler "trust me, I know what I'm doing." +A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. +It has no runtime impact, and is used purely by the compiler. +TypeScript assumes that you, the programmer, have performed any special checks that you need. + +Type assertions have two forms. +One is the "angle-bracket" syntax: + +```ts +let someValue: any = "this is a string"; + +let strLength: number = (someValue).length; +``` + +And the other is the `as`-syntax: + +```ts +let someValue: any = "this is a string"; + +let strLength: number = (someValue as string).length; +``` + +The two samples are equivalent. +Using one over the other is mostly a choice of preference; however, when using TypeScript with JSX, only `as`-style assertions are allowed. + +# A note about `let` + +You may've noticed that so far, we've been using the `let` keyword instead of JavaScript's `var` keyword which you might be more familiar with. +The `let` keyword is actually a newer JavaScript construct that TypeScript makes available. +We'll discuss the details later, but many common problems in JavaScript are alleviated by using `let`, so you should use it instead of `var` whenever possible. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Classes.md b/packages/handbook-v1/en/Classes.md new file mode 100644 index 000000000000..4b45d50c7445 --- /dev/null +++ b/packages/handbook-v1/en/Classes.md @@ -0,0 +1,543 @@ +--- +title: Classes +layout: docs +permalink: /docs/handbook/classes.html +--- +{% raw %}# Introduction + +Traditional JavaScript uses functions and prototype-based inheritance to build up reusable components, but this may feel a bit awkward to programmers more comfortable with an object-oriented approach, where classes inherit functionality and objects are built from these classes. +Starting with ECMAScript 2015, also known as ECMAScript 6, JavaScript programmers will be able to build their applications using this object-oriented class-based approach. +In TypeScript, we allow developers to use these techniques now, and compile them down to JavaScript that works across all major browsers and platforms, without having to wait for the next version of JavaScript. + +# Classes + +Let's take a look at a simple class-based example: + +```ts +class Greeter { + greeting: string; + constructor(message: string) { + this.greeting = message; + } + greet() { + return "Hello, " + this.greeting; + } +} + +let greeter = new Greeter("world"); +``` + +The syntax should look familiar if you've used C# or Java before. +We declare a new class `Greeter`. This class has three members: a property called `greeting`, a constructor, and a method `greet`. + +You'll notice that in the class when we refer to one of the members of the class we prepend `this.`. +This denotes that it's a member access. + +In the last line we construct an instance of the `Greeter` class using `new`. +This calls into the constructor we defined earlier, creating a new object with the `Greeter` shape, and running the constructor to initialize it. + +# Inheritance + +In TypeScript, we can use common object-oriented patterns. +One of the most fundamental patterns in class-based programming is being able to extend existing classes to create new ones using inheritance. + +Let's take a look at an example: + +```ts +class Animal { + move(distanceInMeters: number = 0) { + console.log(`Animal moved ${distanceInMeters}m.`); + } +} + +class Dog extends Animal { + bark() { + console.log('Woof! Woof!'); + } +} + +const dog = new Dog(); +dog.bark(); +dog.move(10); +dog.bark(); +``` + +This example shows the most basic inheritance feature: classes inherit properties and methods from base classes. +Here, `Dog` is a *derived* class that derives from the `Animal` *base* class using the `extends` keyword. +Derived classes are often called *subclasses*, and base classes are often called *superclasses*. + +Because `Dog` extends the functionality from `Animal`, we were able to create an instance of `Dog` that could both `bark()` and `move()`. + +Let's now look at a more complex example. + +```ts +class Animal { + name: string; + constructor(theName: string) { this.name = theName; } + move(distanceInMeters: number = 0) { + console.log(`${this.name} moved ${distanceInMeters}m.`); + } +} + +class Snake extends Animal { + constructor(name: string) { super(name); } + move(distanceInMeters = 5) { + console.log("Slithering..."); + super.move(distanceInMeters); + } +} + +class Horse extends Animal { + constructor(name: string) { super(name); } + move(distanceInMeters = 45) { + console.log("Galloping..."); + super.move(distanceInMeters); + } +} + +let sam = new Snake("Sammy the Python"); +let tom: Animal = new Horse("Tommy the Palomino"); + +sam.move(); +tom.move(34); +``` + +This example covers a few other features we didn't previously mention. +Again, we see the `extends` keywords used to create two new subclasses of `Animal`: `Horse` and `Snake`. + +One difference from the prior example is that each derived class that contains a constructor function *must* call `super()` which will execute the constructor of the base class. +What's more, before we *ever* access a property on `this` in a constructor body, we *have* to call `super()`. +This is an important rule that TypeScript will enforce. + +The example also shows how to override methods in the base class with methods that are specialized for the subclass. +Here both `Snake` and `Horse` create a `move` method that overrides the `move` from `Animal`, giving it functionality specific to each class. +Note that even though `tom` is declared as an `Animal`, since its value is a `Horse`, calling `tom.move(34)` will call the overriding method in `Horse`: + +```Text +Slithering... +Sammy the Python moved 5m. +Galloping... +Tommy the Palomino moved 34m. +``` + +# Public, private, and protected modifiers + +## Public by default + +In our examples, we've been able to freely access the members that we declared throughout our programs. +If you're familiar with classes in other languages, you may have noticed in the above examples we haven't had to use the word `public` to accomplish this; for instance, C# requires that each member be explicitly labeled `public` to be visible. +In TypeScript, each member is `public` by default. + +You may still mark a member `public` explicitly. +We could have written the `Animal` class from the previous section in the following way: + +```ts +class Animal { + public name: string; + public constructor(theName: string) { this.name = theName; } + public move(distanceInMeters: number) { + console.log(`${this.name} moved ${distanceInMeters}m.`); + } +} +``` + +## Understanding `private` + +When a member is marked `private`, it cannot be accessed from outside of its containing class. For example: + +```ts +class Animal { + private name: string; + constructor(theName: string) { this.name = theName; } +} + +new Animal("Cat").name; // Error: 'name' is private; +``` + +TypeScript is a structural type system. +When we compare two different types, regardless of where they came from, if the types of all members are compatible, then we say the types themselves are compatible. + +However, when comparing types that have `private` and `protected` members, we treat these types differently. +For two types to be considered compatible, if one of them has a `private` member, then the other must have a `private` member that originated in the same declaration. +The same applies to `protected` members. + +Let's look at an example to better see how this plays out in practice: + +```ts +class Animal { + private name: string; + constructor(theName: string) { this.name = theName; } +} + +class Rhino extends Animal { + constructor() { super("Rhino"); } +} + +class Employee { + private name: string; + constructor(theName: string) { this.name = theName; } +} + +let animal = new Animal("Goat"); +let rhino = new Rhino(); +let employee = new Employee("Bob"); + +animal = rhino; +animal = employee; // Error: 'Animal' and 'Employee' are not compatible +``` + +In this example, we have an `Animal` and a `Rhino`, with `Rhino` being a subclass of `Animal`. +We also have a new class `Employee` that looks identical to `Animal` in terms of shape. +We create some instances of these classes and then try to assign them to each other to see what will happen. +Because `Animal` and `Rhino` share the `private` side of their shape from the same declaration of `private name: string` in `Animal`, they are compatible. However, this is not the case for `Employee`. +When we try to assign from an `Employee` to `Animal` we get an error that these types are not compatible. +Even though `Employee` also has a `private` member called `name`, it's not the one we declared in `Animal`. + +## Understanding `protected` + +The `protected` modifier acts much like the `private` modifier with the exception that members declared `protected` can also be accessed within deriving classes. For example, + +```ts +class Person { + protected name: string; + constructor(name: string) { this.name = name; } +} + +class Employee extends Person { + private department: string; + + constructor(name: string, department: string) { + super(name); + this.department = department; + } + + public getElevatorPitch() { + return `Hello, my name is ${this.name} and I work in ${this.department}.`; + } +} + +let howard = new Employee("Howard", "Sales"); +console.log(howard.getElevatorPitch()); +console.log(howard.name); // error +``` + +Notice that while we can't use `name` from outside of `Person`, we can still use it from within an instance method of `Employee` because `Employee` derives from `Person`. + +A constructor may also be marked `protected`. +This means that the class cannot be instantiated outside of its containing class, but can be extended. For example, + +```ts +class Person { + protected name: string; + protected constructor(theName: string) { this.name = theName; } +} + +// Employee can extend Person +class Employee extends Person { + private department: string; + + constructor(name: string, department: string) { + super(name); + this.department = department; + } + + public getElevatorPitch() { + return `Hello, my name is ${this.name} and I work in ${this.department}.`; + } +} + +let howard = new Employee("Howard", "Sales"); +let john = new Person("John"); // Error: The 'Person' constructor is protected +``` + +# Readonly modifier + +You can make properties readonly by using the `readonly` keyword. +Readonly properties must be initialized at their declaration or in the constructor. + +```ts +class Octopus { + readonly name: string; + readonly numberOfLegs: number = 8; + constructor (theName: string) { + this.name = theName; + } +} +let dad = new Octopus("Man with the 8 strong legs"); +dad.name = "Man with the 3-piece suit"; // error! name is readonly. +``` + +## Parameter properties + +In our last example, we had to declare a readonly member `name` and a constructor parameter `theName` in the `Octopus` class. This is needed in order to have the value of `theName` accessible after the `Octopus` constructor is executed. +*Parameter properties* let you create and initialize a member in one place. +Here's a further revision of the previous `Octopus` class using a parameter property: + +```ts +class Octopus { + readonly numberOfLegs: number = 8; + constructor(readonly name: string) { + } +} +``` + +Notice how we dropped `theName` altogether and just use the shortened `readonly name: string` parameter on the constructor to create and initialize the `name` member. +We've consolidated the declarations and assignment into one location. + +Parameter properties are declared by prefixing a constructor parameter with an accessibility modifier or `readonly`, or both. +Using `private` for a parameter property declares and initializes a private member; likewise, the same is done for `public`, `protected`, and `readonly`. + +# Accessors + +TypeScript supports getters/setters as a way of intercepting accesses to a member of an object. +This gives you a way of having finer-grained control over how a member is accessed on each object. + +Let's convert a simple class to use `get` and `set`. +First, let's start with an example without getters and setters. + +```ts +class Employee { + fullName: string; +} + +let employee = new Employee(); +employee.fullName = "Bob Smith"; +if (employee.fullName) { + console.log(employee.fullName); +} +``` + +While allowing people to randomly set `fullName` directly is pretty handy, we may also want enforce some constraints when `fullName` is set. + +In this version, we add a setter that checks the length of the `newName` to make sure it's compatible with the max-length of our backing database field. If it isn't we throw an error notifying client code that something went wrong. + +To preserve existing functionality, we also add a simple getter that retrieves `fullName` unmodified. + +```ts +const fullNameMaxLength = 10; + +class Employee { + private _fullName: string; + + get fullName(): string { + return this._fullName; + } + + set fullName(newName: string) { + if (newName && newName.length > fullNameMaxLength) { + throw new Error("fullName has a max length of " + fullNameMaxLength); + } + + this._fullName = newName; + } +} + +let employee = new Employee(); +employee.fullName = "Bob Smith"; +if (employee.fullName) { + console.log(employee.fullName); +} +``` + +To prove to ourselves that our accessor is now checking the length of values, we can attempt to assign a name longer than 10 characters and verify that we get an error. + +A couple of things to note about accessors: + +First, accessors require you to set the compiler to output ECMAScript 5 or higher. +Downleveling to ECMAScript 3 is not supported. +Second, accessors with a `get` and no `set` are automatically inferred to be `readonly`. +This is helpful when generating a `.d.ts` file from your code, because users of your property can see that they can't change it. + +# Static Properties + +Up to this point, we've only talked about the *instance* members of the class, those that show up on the object when it's instantiated. +We can also create *static* members of a class, those that are visible on the class itself rather than on the instances. +In this example, we use `static` on the origin, as it's a general value for all grids. +Each instance accesses this value through prepending the name of the class. +Similarly to prepending `this.` in front of instance accesses, here we prepend `Grid.` in front of static accesses. + +```ts +class Grid { + static origin = {x: 0, y: 0}; + calculateDistanceFromOrigin(point: {x: number; y: number;}) { + let xDist = (point.x - Grid.origin.x); + let yDist = (point.y - Grid.origin.y); + return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale; + } + constructor (public scale: number) { } +} + +let grid1 = new Grid(1.0); // 1x scale +let grid2 = new Grid(5.0); // 5x scale + +console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); +console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10})); +``` + +# Abstract Classes + +Abstract classes are base classes from which other classes may be derived. +They may not be instantiated directly. +Unlike an interface, an abstract class may contain implementation details for its members. +The `abstract` keyword is used to define abstract classes as well as abstract methods within an abstract class. + +```ts +abstract class Animal { + abstract makeSound(): void; + move(): void { + console.log("roaming the earth..."); + } +} +``` + +Methods within an abstract class that are marked as abstract do not contain an implementation and must be implemented in derived classes. +Abstract methods share a similar syntax to interface methods. +Both define the signature of a method without including a method body. +However, abstract methods must include the `abstract` keyword and may optionally include access modifiers. + +```ts +abstract class Department { + + constructor(public name: string) { + } + + printName(): void { + console.log("Department name: " + this.name); + } + + abstract printMeeting(): void; // must be implemented in derived classes +} + +class AccountingDepartment extends Department { + + constructor() { + super("Accounting and Auditing"); // constructors in derived classes must call super() + } + + printMeeting(): void { + console.log("The Accounting Department meets each Monday at 10am."); + } + + generateReports(): void { + console.log("Generating accounting reports..."); + } +} + +let department: Department; // ok to create a reference to an abstract type +department = new Department(); // error: cannot create an instance of an abstract class +department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass +department.printName(); +department.printMeeting(); +department.generateReports(); // error: method doesn't exist on declared abstract type +``` + +# Advanced Techniques + +## Constructor functions + +When you declare a class in TypeScript, you are actually creating multiple declarations at the same time. +The first is the type of the *instance* of the class. + +```ts +class Greeter { + greeting: string; + constructor(message: string) { + this.greeting = message; + } + greet() { + return "Hello, " + this.greeting; + } +} + +let greeter: Greeter; +greeter = new Greeter("world"); +console.log(greeter.greet()); +``` + +Here, when we say `let greeter: Greeter`, we're using `Greeter` as the type of instances of the class `Greeter`. +This is almost second nature to programmers from other object-oriented languages. + +We're also creating another value that we call the *constructor function*. +This is the function that is called when we `new` up instances of the class. +To see what this looks like in practice, let's take a look at the JavaScript created by the above example: + +```ts +let Greeter = (function () { + function Greeter(message) { + this.greeting = message; + } + Greeter.prototype.greet = function () { + return "Hello, " + this.greeting; + }; + return Greeter; +})(); + +let greeter; +greeter = new Greeter("world"); +console.log(greeter.greet()); +``` + +Here, `let Greeter` is going to be assigned the constructor function. +When we call `new` and run this function, we get an instance of the class. +The constructor function also contains all of the static members of the class. +Another way to think of each class is that there is an *instance* side and a *static* side. + +Let's modify the example a bit to show this difference: + +```ts +class Greeter { + static standardGreeting = "Hello, there"; + greeting: string; + greet() { + if (this.greeting) { + return "Hello, " + this.greeting; + } + else { + return Greeter.standardGreeting; + } + } +} + +let greeter1: Greeter; +greeter1 = new Greeter(); +console.log(greeter1.greet()); + +let greeterMaker: typeof Greeter = Greeter; +greeterMaker.standardGreeting = "Hey there!"; + +let greeter2: Greeter = new greeterMaker(); +console.log(greeter2.greet()); +``` + +In this example, `greeter1` works similarly to before. +We instantiate the `Greeter` class, and use this object. +This we have seen before. + +Next, we then use the class directly. +Here we create a new variable called `greeterMaker`. +This variable will hold the class itself, or said another way its constructor function. +Here we use `typeof Greeter`, that is "give me the type of the `Greeter` class itself" rather than the instance type. +Or, more precisely, "give me the type of the symbol called `Greeter`," which is the type of the constructor function. +This type will contain all of the static members of Greeter along with the constructor that creates instances of the `Greeter` class. +We show this by using `new` on `greeterMaker`, creating new instances of `Greeter` and invoking them as before. + +## Using a class as an interface + +As we said in the previous section, a class declaration creates two things: a type representing instances of the class and a constructor function. +Because classes create types, you can use them in the same places you would be able to use interfaces. + +```ts +class Point { + x: number; + y: number; +} + +interface Point3d extends Point { + z: number; +} + +let point3d: Point3d = {x: 1, y: 2, z: 3}; +``` + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Compiler Options in MSBuild.md b/packages/handbook-v1/en/Compiler Options in MSBuild.md new file mode 100644 index 000000000000..84609610317b --- /dev/null +++ b/packages/handbook-v1/en/Compiler Options in MSBuild.md @@ -0,0 +1,128 @@ +--- +title: Compiler Options in MSBuild +layout: docs +permalink: /docs/handbook/compiler-options-in-msbuild.html +--- +{% raw %}## Overview + +Compiler options can be specified using MSBuild properties within an MSBuild project. + +## Example + +```XML + + false + true + + + true + false + + +``` + +## Mappings + +Compiler Option | MSBuild Property Name | Allowed Values +---------------------------------------------|--------------------------------------------|----------------- +`--allowJs` | *Not supported in MSBuild* | +`--allowSyntheticDefaultImports` | TypeScriptAllowSyntheticDefaultImports | boolean +`--allowUnreachableCode` | TypeScriptAllowUnreachableCode | boolean +`--allowUnusedLabels` | TypeScriptAllowUnusedLabels | boolean +`--alwaysStrict` | TypeScriptAlwaysStrict | boolean +`--baseUrl` | TypeScriptBaseUrl | File path +`--charset` | TypeScriptCharset | +`--declaration` | TypeScriptGeneratesDeclarations | boolean +`--declarationDir` | TypeScriptDeclarationDir | File path +`--diagnostics` | *Not supported in MSBuild* | +`--disableSizeLimit` | *Not supported in MSBuild* | +`--emitBOM` | TypeScriptEmitBOM | boolean +`--emitDecoratorMetadata` | TypeScriptEmitDecoratorMetadata | boolean +`--emitDeclarationOnly` | TypeScriptEmitDeclarationOnly | boolean +`--esModuleInterop` | TypeScriptESModuleInterop | boolean +`--experimentalAsyncFunctions` | TypeScriptExperimentalAsyncFunctions | boolean +`--experimentalDecorators` | TypeScriptExperimentalDecorators | boolean +`--forceConsistentCasingInFileNames` | TypeScriptForceConsistentCasingInFileNames | boolean +`--help` | *Not supported in MSBuild* | +`--importHelpers` | TypeScriptImportHelpers | boolean +`--inlineSourceMap` | TypeScriptInlineSourceMap | boolean +`--inlineSources` | TypeScriptInlineSources | boolean +`--init` | *Not supported in MSBuild* | +`--isolatedModules` | TypeScriptIsolatedModules | boolean +`--jsx` | TypeScriptJSXEmit | `react`, `react-native`, `preserve` +`--jsxFactory` | TypeScriptJSXFactory | qualified name +`--lib` | TypeScriptLib | Comma-separated list of strings +`--listEmittedFiles` | *Not supported in MSBuild* | +`--listFiles` | *Not supported in MSBuild* | +`--locale` | *automatic* | Automatically set to PreferredUILang value +`--mapRoot` | TypeScriptMapRoot | File path +`--maxNodeModuleJsDepth` | *Not supported in MSBuild* | +`--module` | TypeScriptModuleKind | `AMD`, `CommonJs`, `UMD`, `System` or `ES6` +`--moduleResolution` | TypeScriptModuleResolution | `Classic` or `Node` +`--newLine` | TypeScriptNewLine | `CRLF` or `LF` +`--noEmit` | *Not supported in MSBuild* | +`--noEmitHelpers` | TypeScriptNoEmitHelpers | boolean +`--noEmitOnError` | TypeScriptNoEmitOnError | boolean +`--noFallthroughCasesInSwitch` | TypeScriptNoFallthroughCasesInSwitch | boolean +`--noImplicitAny` | TypeScriptNoImplicitAny | boolean +`--noImplicitReturns` | TypeScriptNoImplicitReturns | boolean +`--noImplicitThis` | TypeScriptNoImplicitThis | boolean +`--noImplicitUseStrict` | TypeScriptNoImplicitUseStrict | boolean +`--noStrictGenericChecks` | TypeScriptNoStrictGenericChecks | boolean +`--noUnusedLocals` | TypeScriptNoUnusedLocals | boolean +`--noUnusedParameters` | TypeScriptNoUnusedParameters | boolean +`--noLib` | TypeScriptNoLib | boolean +`--noResolve` | TypeScriptNoResolve | boolean +`--out` | TypeScriptOutFile | File path +`--outDir` | TypeScriptOutDir | File path +`--outFile` | TypeScriptOutFile | File path +`--paths` | *Not supported in MSBuild* | +`--preserveConstEnums` | TypeScriptPreserveConstEnums | boolean +`--preserveSymlinks` | TypeScriptPreserveSymlinks | boolean +`--listEmittedFiles` | *Not supported in MSBuild* | +`--pretty` | *Not supported in MSBuild* | +`--reactNamespace` | TypeScriptReactNamespace | string +`--removeComments` | TypeScriptRemoveComments | boolean +`--rootDir` | TypeScriptRootDir | File path +`--rootDirs` | *Not supported in MSBuild* | +`--skipLibCheck` | TypeScriptSkipLibCheck | boolean +`--skipDefaultLibCheck` | TypeScriptSkipDefaultLibCheck | boolean +`--sourceMap` | TypeScriptSourceMap | File path +`--sourceRoot` | TypeScriptSourceRoot | File path +`--strict` | TypeScriptStrict | boolean +`--strictFunctionTypes` | TypeScriptStrictFunctionTypes | boolean +`--strictNullChecks` | TypeScriptStrictNullChecks | boolean +`--strictPropertyInitialization` | TypeScriptStrictPropertyInitialization | boolean +`--stripInternal` | TypeScriptStripInternal | boolean +`--suppressExcessPropertyErrors` | TypeScriptSuppressExcessPropertyErrors | boolean +`--suppressImplicitAnyIndexErrors` | TypeScriptSuppressImplicitAnyIndexErrors | boolean +`--target` | TypeScriptTarget | `ES3`, `ES5`, or `ES6` +`--traceResolution` | *Not supported in MSBuild* | +`--types` | *Not supported in MSBuild* | +`--typeRoots` | *Not supported in MSBuild* | +`--watch` | *Not supported in MSBuild* | +*MSBuild only option* | TypeScriptAdditionalFlags | *Any compiler option* + +## What is supported in my version of Visual Studio? + +Look in your `C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets` file. +The authoritative mappings between MSBuild XML tags and `tsc` compiler options live in there. + +## ToolsVersion + +The value of `1.7` property in the project file identifies the compiler version to use to build (1.7 in this example). +This allows a project to build against the same versions of the compiler on different machines. + +If `TypeScriptToolsVersion` is not specified, the latest compiler version installed on the machine will be used to build. + +Users using newer versions of TS, will see a prompt to upgrade their project on first load. + +## TypeScriptCompileBlocked + +If you are using a different build tool to build your project (e.g. gulp, grunt , etc.) and VS for the development and debugging experience, set `true` in your project. +This should give you all the editing support, but not the build when you hit F5. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Compiler Options.md b/packages/handbook-v1/en/Compiler Options.md new file mode 100644 index 000000000000..de54563eba9a --- /dev/null +++ b/packages/handbook-v1/en/Compiler Options.md @@ -0,0 +1,110 @@ +--- +title: Compiler Options +layout: docs +permalink: /docs/handbook/compiler-options.html +--- +{% raw %}## Compiler Options + +Option | Type | Default | Description +-----------------------------------------------|-----------|--------------------------------|---------------------------------------------------------------------- +`--allowJs` | `boolean` | `false` | Allow JavaScript files to be compiled. +`--allowSyntheticDefaultImports` | `boolean` | `module === "system"` or `--esModuleInterop` | Allow default imports from modules with no default export. This does not affect code emit, just typechecking. +`--allowUmdGlobalAccess` | `boolean` | `false` | Allow accessing UMD globals from modules. +`--allowUnreachableCode` | `boolean` | `false` | Do not report errors on unreachable code. +`--allowUnusedLabels` | `boolean` | `false` | Do not report errors on unused labels. +`--alwaysStrict` | `boolean` | `false` | Parse in strict mode and emit `"use strict"` for each source file +`--baseUrl` | `string` | | Base directory to resolve non-relative module names. See [Module Resolution documentation](./Module%20Resolution.md#base-url) for more details. +`--build`
`-b` | `boolean` | `false` | Builds this project and all of its dependencies specified by [Project References](./Project%20References.md). Note that this flag is not compatible with others on this page. See more [here](./Project%20References.md) +`--charset` | `string` | `"utf8"` | The character set of the input files. +`--checkJs` | `boolean` | `false` | Report errors in `.js` files. Use in conjunction with `--allowJs`. +`--composite` | `boolean` | `true` | Ensure TypeScript can determine where to find the outputs of the referenced project to compile project. +`--declaration`
`-d` | `boolean` | `false` | Generates corresponding `.d.ts` file. +`--declarationDir` | `string` | | Output directory for generated declaration files. +`--declarationMap` | `boolean` | `false` | Generates a sourcemap for each corresponding '.d.ts' file. +`--diagnostics` | `boolean` | `false` | Show diagnostic information. +`--disableSizeLimit` | `boolean` | `false` | Disable size limitation on JavaScript project. +`--downlevelIteration` | `boolean` | `false` | Provide full support for iterables in `for..of`, spread and destructuring when targeting ES5 or ES3. +`--emitBOM` | `boolean` | `false` | Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. +`--emitDeclarationOnly` | `boolean` | `false` | Only emit '.d.ts' declaration files. +`--emitDecoratorMetadata`[1] | `boolean` | `false` | Emit design-type metadata for decorated declarations in source. See [issue #2577](https://github.com/Microsoft/TypeScript/issues/2577) for details. +`--esModuleInterop` | `boolean` | `false` | Emit `__importStar` and `__importDefault` helpers for runtime babel ecosystem compatibility and enable `--allowSyntheticDefaultImports` for typesystem compatibility. +`--experimentalDecorators`[1] | `boolean` | `false` | Enables experimental support for ES decorators. +`--extendedDiagnostics` | `boolean` | `false` | Show verbose diagnostic information +`--forceConsistentCasingInFileNames` | `boolean` | `false` | Disallow inconsistently-cased references to the same file. +`--help`
`-h` | | | Print help message. +`--importHelpers` | `boolean` | `false` | Import emit helpers (e.g. `__extends`, `__rest`, etc..) from [`tslib`](https://www.npmjs.com/package/tslib) +`--incremental` | `boolean` | `true` if `composite` is on, `false` otherwise | Enable incremental compilation by reading/writing information from prior compilations to a file on disk. This file is controlled by the `--tsBuildInfoFile` flag. +`--inlineSourceMap` | `boolean` | `false` | Emit a single file with source maps instead of having a separate file. +`--inlineSources` | `boolean` | `false` | Emit the source alongside the sourcemaps within a single file; requires `--inlineSourceMap` or `--sourceMap` to be set. +`--init` | | | Initializes a TypeScript project and creates a `tsconfig.json` file. +`--isolatedModules` | `boolean` | `false` | Perform additional checks to ensure that separate compilation (such as with [`transpileModule`](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#a-simple-transform-function) or [@babel/plugin-transform-typescript](https://babeljs.io/docs/en/babel-plugin-transform-typescript)) would be safe. +`--jsx` | `string` | `"preserve"` | Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`. See [JSX](./JSX.md). +`--jsxFactory` | `string` | `"React.createElement"` | Specify the JSX factory function to use when targeting react JSX emit, e.g. `React.createElement` or `h`. +`--keyofStringsOnly` | `boolean` | `false` | Resolve `keyof` to string valued property names only (no numbers or symbols). +`--lib` | `string[]`| | List of library files to be included in the compilation.
Possible values are:
► `ES5`
► `ES6`
► `ES2015`
► `ES7`
► `ES2016`
► `ES2017`
► `ES2018`
► `ESNext`
► `DOM`
► `DOM.Iterable`
► `WebWorker`
► `ScriptHost`
► `ES2015.Core`
► `ES2015.Collection`
► `ES2015.Generator`
► `ES2015.Iterable`
► `ES2015.Promise`
► `ES2015.Proxy`
► `ES2015.Reflect`
► `ES2015.Symbol`
► `ES2015.Symbol.WellKnown`
► `ES2016.Array.Include`
► `ES2017.object`
► `ES2017.Intl`
► `ES2017.SharedMemory`
► `ES2017.String`
► `ES2017.TypedArrays`
► `ES2018.Intl`
► `ES2018.Promise`
► `ES2018.RegExp`
► `ESNext.AsyncIterable`
► `ESNext.Array`
► `ESNext.Intl`
► `ESNext.Symbol`

Note: If `--lib` is not specified a default list of libraries are injected. The default libraries injected are:
► For `--target ES5`: `DOM,ES5,ScriptHost`
► For `--target ES6`: `DOM,ES6,DOM.Iterable,ScriptHost` +`--listEmittedFiles` | `boolean` | `false` | Print names of generated files part of the compilation. +`--listFiles` | `boolean` | `false` | Print names of files part of the compilation. +`--locale` | `string` | *(platform specific)* | The locale to use to show error messages, e.g. en-us.
Possible values are:
► English (US): `en`
► Czech: `cs`
► German: `de`
► Spanish: `es`
► French: `fr`
► Italian: `it`
► Japanese: `ja`
► Korean: `ko`
► Polish: `pl`
► Portuguese(Brazil): `pt-BR`
► Russian: `ru`
► Turkish: `tr`
► Simplified Chinese: `zh-CN`
► Traditional Chinese: `zh-TW` +`--mapRoot` | `string` | | Specifies the location where debugger should locate map files instead of generated locations. Use this flag if the .map files will be located at run-time in a different location than the .js files. The location specified will be embedded in the sourceMap to direct the debugger where the map files will be located. This flag will not create the specified path and generate the map files in that location. Instead, create a post build step that moves the files to the specified path. +`--maxNodeModuleJsDepth` | `number` | `0` | The maximum dependency depth to search under node_modules and load JavaScript files. Only applicable with `--allowJs`. +`--module`
`-m` | `string` | `target === "ES3" or "ES5" ? "CommonJS" : "ES6"` | Specify module code generation: `"None"`, `"CommonJS"`, `"AMD"`, `"System"`, `"UMD"`, `"ES6"`, `"ES2015"` or `"ESNext"`.
► Only `"AMD"` and `"System"` can be used in conjunction with `--outFile`.
► `"ES6"` and `"ES2015"` values may be used when targeting `"ES5"` or lower. +`--moduleResolution` | `string` | `module === "AMD" or "System" or "ES6" ? "Classic" : "Node"` | Determine how modules get resolved. Either `"Node"` for Node.js/io.js style resolution, or `"Classic"`. See [Module Resolution documentation](./Module%20Resolution.md) for more details. +`--newLine` | `string` | *(platform specific)* | Use the specified end of line sequence to be used when emitting files: `"crlf"` (windows) or `"lf"` (unix)." +`--noEmit` | `boolean` | `false` | Do not emit outputs. +`--noEmitHelpers` | `boolean` | `false` | Do not generate custom helper functions like `__extends` in compiled output. +`--noEmitOnError` | `boolean` | `false` | Do not emit outputs if any errors were reported. +`--noErrorTruncation` | `boolean` | `false` | Do not truncate error messages. +`--noFallthroughCasesInSwitch` | `boolean` | `false` | Report errors for fallthrough cases in switch statement. +`--noImplicitAny` | `boolean` | `false` | Raise error on expressions and declarations with an implied `any` type. +`--noImplicitReturns` | `boolean` | `false` | Report an error when not all code paths in function return a value. +`--noImplicitThis` | `boolean` | `false` | Raise error on `this` expressions with an implied `any` type. +`--noImplicitUseStrict` | `boolean` | `false` | Do not emit `"use strict"` directives in module output. +`--noLib` | `boolean` | `false` | Do not include the default library file (`lib.d.ts`). +`--noResolve` | `boolean` | `false` | Do not add triple-slash references or module import targets to the list of compiled files. +`--noStrictGenericChecks` | `boolean` | `false` | Disable strict checking of generic signatures in function types. +`--noUnusedLocals` | `boolean` | `false` | Report errors on unused locals. +`--noUnusedParameters` | `boolean` | `false` | Report errors on unused parameters. +~~`--out`~~ | `string` | | DEPRECATED. Use `--outFile` instead. +`--outDir` | `string` | | Redirect output structure to the directory. +`--outFile` | `string` | | Concatenate and emit output to single file. The order of concatenation is determined by the list of files passed to the compiler on the command line along with triple-slash references and imports. See [output file order documentation](https://github.com/Microsoft/TypeScript/wiki/FAQ#how-do-i-control-file-ordering-in-combined-output---out-) for more details. +`paths`[2] | `Object` | | List of path mapping entries for module names to locations relative to the `baseUrl`. See [Module Resolution documentation](./Module%20Resolution.md#path-mapping) for more details. +`--preserveConstEnums` | `boolean` | `false` | Do not erase const enum declarations in generated code. See [const enums documentation](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#94-constant-enum-declarations) for more details. +`--preserveSymlinks` | `boolean` | `false` | Do not resolve symlinks to their real path; treat a symlinked file like a real one. +`--preserveWatchOutput` | `boolean` | `false` | Keep outdated console output in watch mode instead of clearing the screen +`--pretty` | `boolean` | `true` unless piping to another program or redirecting output to a file | Stylize errors and messages using color and context. +`--project`
`-p` | `string` | | Compile a project given a valid configuration file.
The argument can be a file path to a valid JSON configuration file, or a directory path to a directory containing a `tsconfig.json` file.
See [tsconfig.json](./tsconfig.json.md) documentation for more details. +`--reactNamespace` | `string` | `"React"` | DEPRECATED. Use `--jsxFactory` instead.
Specifies the object invoked for `createElement` and `__spread` when targeting `"react"` JSX emit. +`--removeComments` | `boolean` | `false` | Remove all comments except copy-right header comments beginning with `/*!` +`--resolveJsonModule` | `boolean` | `false` | Include modules imported with `.json` extension. +`--rootDir` | `string` | *(common root directory is computed from the list of input files)* | Specifies the root directory of input files. Only use to control the output directory structure with `--outDir`. +`rootDirs`[2] | `string[]`| | List of root folders whose combined content represent the structure of the project at runtime. See [Module Resolution documentation](./Module%20Resolution.md#virtual-directories-with-rootdirs) for more details. +`--showConfig` | `boolean` | `false` | Rather than actually execute a build with the other input options and config files, show the final implied config file in the output. +`--skipDefaultLibCheck` | `boolean` | `false` | DEPRECATED. Use `--skipLibCheck` instead.
Skip type checking of [default library declaration files](./Triple-Slash%20Directives.md#-reference-no-default-libtrue). +`--skipLibCheck` | `boolean` | `false` | Skip type checking of all declaration files (`*.d.ts`). +`--sourceMap` | `boolean` | `false` | Generates corresponding `.map` file. +`--sourceRoot` | `string` | | Specifies the location where debugger should locate TypeScript files instead of source locations. Use this flag if the sources will be located at run-time in a different location than that at design-time. The location specified will be embedded in the sourceMap to direct the debugger where the source files will be located. +`--strict` | `boolean` | `false` | Enable all strict type checking options.
Enabling `--strict` enables `--noImplicitAny`, `--noImplicitThis`, `--alwaysStrict`, `--strictBindCallApply`, `--strictNullChecks`, `--strictFunctionTypes` and `--strictPropertyInitialization`. +`--strictBindCallApply` | `boolean` | `false` | Enable stricter checking of the `bind`, `call`, and `apply` methods on functions. +`--strictFunctionTypes` | `boolean` | `false` | Disable bivariant parameter checking for function types. +`--strictPropertyInitialization` | `boolean` | `false` | Ensure non-undefined class properties are initialized in the constructor. This option requires `--strictNullChecks` be enabled in order to take effect. +`--strictNullChecks` | `boolean` | `false` | In strict null checking mode, the `null` and `undefined` values are not in the domain of every type and are only assignable to themselves and `any` (the one exception being that `undefined` is also assignable to `void`). +`--suppressExcessPropertyErrors` | `boolean` | `false` | Suppress excess property checks for object literals. +`--suppressImplicitAnyIndexErrors` | `boolean` | `false` | Suppress `--noImplicitAny` errors for indexing objects lacking index signatures. See [issue #1232](https://github.com/Microsoft/TypeScript/issues/1232#issuecomment-64510362) for more details. +`--target`
`-t` | `string` | `"ES3"` | Specify ECMAScript target version:
► `"ES3"` (default)
► `"ES5"`
► `"ES6"`/`"ES2015"`
► `"ES2016"`
► `"ES2017"`
► `"ES2018"`
► `"ES2019"`
► `"ES2020"`
► `"ESNext"`

Note: `"ESNext"` targets latest supported [ES proposed features](https://github.com/tc39/proposals). +`--traceResolution` | `boolean` | `false` | Report module resolution log messages. +`--tsBuildInfoFile` | `string` | `.tsbuildinfo` | Specify what file to store incremental build information in. +`--types` | `string[]`| | List of names of type definitions to include. See [@types, --typeRoots and --types](./tsconfig.json.md#types-typeroots-and-types) for more details. +`--typeRoots` | `string[]`| | List of folders to include type definitions from. See [@types, --typeRoots and --types](./tsconfig.json.md#types-typeroots-and-types) for more details. +`--version`
`-v` | | | Print the compiler's version. +`--watch`
`-w` | | | Run the compiler in watch mode. Watch input files and trigger recompilation on changes. The implementation of watching files and directories can be configured using environment variable. See [configuring watch](./Configuring%20Watch.md) for more details. + +* [1] These options are experimental. +* [2] These options are only allowed in `tsconfig.json`, and not through command-line switches. + +## Related + +* Setting compiler options in [`tsconfig.json`](./tsconfig.json.md) files. +* Setting compiler options in [MSBuild projects](./Compiler%20Options%20in%20MSBuild.md). + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Configuring Watch.md b/packages/handbook-v1/en/Configuring Watch.md new file mode 100644 index 000000000000..75fa6302ce6d --- /dev/null +++ b/packages/handbook-v1/en/Configuring Watch.md @@ -0,0 +1,38 @@ +--- +title: Configuring Watch +layout: docs +permalink: /docs/handbook/configuring-watch.html +--- +{% raw %}Compiler supports configuring how to watch files and directories using the environment variables. + +## Configuring file watching using environment variable `TSC_WATCHFILE` + +Option | Description +-----------------------------------------------|---------------------------------------------------------------------- +`PriorityPollingInterval` | Use `fs.watchFile` but use different polling intervals for source files, config files and missing files +`DynamicPriorityPolling` | Use a dynamic queue where in the frequently modified files will be polled at shorter interval and the files unchanged will be polled less frequently +`UseFsEvents` | Use `fs.watch` which uses file system events (but might not be accurate on different OS) to get the notifications for the file changes/creation/deletion. Note that few OS eg. linux has limit on number of watches and failing to create watcher using `fs.watch` will result it in creating using `fs.watchFile` +`UseFsEventsWithFallbackDynamicPolling` | This option is similar to `UseFsEvents` except on failing to create watch using `fs.watch`, the fallback watching happens through dynamic polling queues (as explained in `DynamicPriorityPolling`) +`UseFsEventsOnParentDirectory` | This option watches parent directory of the file with `fs.watch` (using file system events) thus being low on CPU but can compromise accuracy. +default (no value specified) | If environment variable `TSC_NONPOLLING_WATCHER` is set to true, watches parent directory of files (just like `UseFsEventsOnParentDirectory`). Otherwise watch files using `fs.watchFile` with `250ms` as the timeout for any file + +## Configuring directory watching using environment variable `TSC_WATCHDIRECTORY` + +The watching of directory on platforms that don't support recursive directory watching natively in node, is supported through recursively creating directory watcher for the child directories using different options selected by `TSC_WATCHDIRECTORY`. Note that on platforms that support native recursive directory watching (e.g windows) the value of this environment variable is ignored. + +Option | Description +-----------------------------------------------|---------------------------------------------------------------------- +`RecursiveDirectoryUsingFsWatchFile` | Use `fs.watchFile` to watch the directories and child directories which is a polling watch (consuming CPU cycles) +`RecursiveDirectoryUsingDynamicPriorityPolling`| Use dynamic polling queue to poll changes to the directory and child directories. +default (no value specified) | Use `fs.watch` to watch directories and child directories + +## Background + +`--watch` implementation of the compiler relies on `fs.watch` and `fs.watchFile` provided by node, both of these methods have pros and cons. + +`fs.watch` uses file system events to notify the changes in the file/directory. But this is OS dependent and the notification is not completely reliable and does not work as expected on many OS. Also there could be limit on number of watches that can be created, eg. linux and we could exhaust it pretty quickly with programs that include large number of files. But because this uses file system events, there is not much CPU cycle involved. Compiler typically uses `fs.watch` to watch directories (eg. source directories included by config file, directories in which module resolution failed etc) These can handle the missing precision in notifying about the changes. But recursive watching is supported on only Windows and OSX. That means we need something to replace the recursive nature on other OS. + +`fs.watchFile` uses polling and thus involves CPU cycles. But this is the most reliable mechanism to get the update on the status of file/directory. Compiler typically uses `fs.watchFile` to watch source files, config files and missing files (missing file references) that means the CPU usage depends on number of files in the program. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Declaration Merging.md b/packages/handbook-v1/en/Declaration Merging.md new file mode 100644 index 000000000000..96dc645dc0b6 --- /dev/null +++ b/packages/handbook-v1/en/Declaration Merging.md @@ -0,0 +1,331 @@ +--- +title: Declaration Merging +layout: docs +permalink: /docs/handbook/declaration-merging.html +--- +{% raw %}# Introduction + +Some of the unique concepts in TypeScript describe the shape of JavaScript objects at the type level. +One example that is especially unique to TypeScript is the concept of 'declaration merging'. +Understanding this concept will give you an advantage when working with existing JavaScript. +It also opens the door to more advanced abstraction concepts. + +For the purposes of this article, "declaration merging" means that the compiler merges two separate declarations declared with the same name into a single definition. +This merged definition has the features of both of the original declarations. +Any number of declarations can be merged; it's not limited to just two declarations. + +# Basic Concepts + +In TypeScript, a declaration creates entities in at least one of three groups: namespace, type, or value. +Namespace-creating declarations create a namespace, which contains names that are accessed using a dotted notation. +Type-creating declarations do just that: they create a type that is visible with the declared shape and bound to the given name. +Lastly, value-creating declarations create values that are visible in the output JavaScript. + +| Declaration Type | Namespace | Type | Value | +|------------------|:---------:|:----:|:-----:| +| Namespace | X | | X | +| Class | | X | X | +| Enum | | X | X | +| Interface | | X | | +| Type Alias | | X | | +| Function | | | X | +| Variable | | | X | + +Understanding what is created with each declaration will help you understand what is merged when you perform a declaration merge. + +# Merging Interfaces + +The simplest, and perhaps most common, type of declaration merging is interface merging. +At the most basic level, the merge mechanically joins the members of both declarations into a single interface with the same name. + +```ts +interface Box { + height: number; + width: number; +} + +interface Box { + scale: number; +} + +let box: Box = {height: 5, width: 6, scale: 10}; +``` + +Non-function members of the interfaces should be unique. +If they are not unique, they must be of the same type. +The compiler will issue an error if the interfaces both declare a non-function member of the same name, but of different types. + +For function members, each function member of the same name is treated as describing an overload of the same function. +Of note, too, is that in the case of interface `A` merging with later interface `A`, the second interface will have a higher precedence than the first. + +That is, in the example: + +```ts +interface Cloner { + clone(animal: Animal): Animal; +} + +interface Cloner { + clone(animal: Sheep): Sheep; +} + +interface Cloner { + clone(animal: Dog): Dog; + clone(animal: Cat): Cat; +} +``` + +The three interfaces will merge to create a single declaration as so: + +```ts +interface Cloner { + clone(animal: Dog): Dog; + clone(animal: Cat): Cat; + clone(animal: Sheep): Sheep; + clone(animal: Animal): Animal; +} +``` + +Notice that the elements of each group maintains the same order, but the groups themselves are merged with later overload sets ordered first. + +One exception to this rule is specialized signatures. +If a signature has a parameter whose type is a *single* string literal type (e.g. not a union of string literals), then it will be bubbled toward the top of its merged overload list. + +For instance, the following interfaces will merge together: + +```ts +interface Document { + createElement(tagName: any): Element; +} +interface Document { + createElement(tagName: "div"): HTMLDivElement; + createElement(tagName: "span"): HTMLSpanElement; +} +interface Document { + createElement(tagName: string): HTMLElement; + createElement(tagName: "canvas"): HTMLCanvasElement; +} +``` + +The resulting merged declaration of `Document` will be the following: + +```ts +interface Document { + createElement(tagName: "canvas"): HTMLCanvasElement; + createElement(tagName: "div"): HTMLDivElement; + createElement(tagName: "span"): HTMLSpanElement; + createElement(tagName: string): HTMLElement; + createElement(tagName: any): Element; +} +``` + +# Merging Namespaces + +Similarly to interfaces, namespaces of the same name will also merge their members. +Since namespaces create both a namespace and a value, we need to understand how both merge. + +To merge the namespaces, type definitions from exported interfaces declared in each namespace are themselves merged, forming a single namespace with merged interface definitions inside. + +To merge the namespace value, at each declaration site, if a namespace already exists with the given name, it is further extended by taking the existing namespace and adding the exported members of the second namespace to the first. + +The declaration merge of `Animals` in this example: + +```ts +namespace Animals { + export class Zebra { } +} + +namespace Animals { + export interface Legged { numberOfLegs: number; } + export class Dog { } +} +``` + +is equivalent to: + +```ts +namespace Animals { + export interface Legged { numberOfLegs: number; } + + export class Zebra { } + export class Dog { } +} +``` + +This model of namespace merging is a helpful starting place, but we also need to understand what happens with non-exported members. +Non-exported members are only visible in the original (un-merged) namespace. This means that after merging, merged members that came from other declarations cannot see non-exported members. + +We can see this more clearly in this example: + +```ts +namespace Animal { + let haveMuscles = true; + + export function animalsHaveMuscles() { + return haveMuscles; + } +} + +namespace Animal { + export function doAnimalsHaveMuscles() { + return haveMuscles; // Error, because haveMuscles is not accessible here + } +} +``` + +Because `haveMuscles` is not exported, only the `animalsHaveMuscles` function that shares the same un-merged namespace can see the symbol. +The `doAnimalsHaveMuscles` function, even though it's part of the merged `Animal` namespace can not see this un-exported member. + +# Merging Namespaces with Classes, Functions, and Enums + +Namespaces are flexible enough to also merge with other types of declarations. +To do so, the namespace declaration must follow the declaration it will merge with. The resulting declaration has properties of both declaration types. +TypeScript uses this capability to model some of the patterns in JavaScript as well as other programming languages. + +## Merging Namespaces with Classes + +This gives the user a way of describing inner classes. + +```ts +class Album { + label: Album.AlbumLabel; +} +namespace Album { + export class AlbumLabel { } +} +``` + +The visibility rules for merged members is the same as described in the 'Merging Namespaces' section, so we must export the `AlbumLabel` class for the merged class to see it. +The end result is a class managed inside of another class. +You can also use namespaces to add more static members to an existing class. + +In addition to the pattern of inner classes, you may also be familiar with the JavaScript practice of creating a function and then extending the function further by adding properties onto the function. +TypeScript uses declaration merging to build up definitions like this in a type-safe way. + +```ts +function buildLabel(name: string): string { + return buildLabel.prefix + name + buildLabel.suffix; +} + +namespace buildLabel { + export let suffix = ""; + export let prefix = "Hello, "; +} + +console.log(buildLabel("Sam Smith")); +``` + +Similarly, namespaces can be used to extend enums with static members: + +```ts +enum Color { + red = 1, + green = 2, + blue = 4 +} + +namespace Color { + export function mixColor(colorName: string) { + if (colorName == "yellow") { + return Color.red + Color.green; + } + else if (colorName == "white") { + return Color.red + Color.green + Color.blue; + } + else if (colorName == "magenta") { + return Color.red + Color.blue; + } + else if (colorName == "cyan") { + return Color.green + Color.blue; + } + } +} +``` + +# Disallowed Merges + +Not all merges are allowed in TypeScript. +Currently, classes can not merge with other classes or with variables. +For information on mimicking class merging, see the [Mixins in TypeScript](./Mixins.md) section. + +# Module Augmentation + +Although JavaScript modules do not support merging, you can patch existing objects by importing and then updating them. +Let's look at a toy Observable example: + + +```ts +// observable.ts +export class Observable { + // ... implementation left as an exercise for the reader ... +} + +// map.ts +import { Observable } from "./observable"; +Observable.prototype.map = function (f) { + // ... another exercise for the reader +} +``` + +This works fine in TypeScript too, but the compiler doesn't know about `Observable.prototype.map`. +You can use module augmentation to tell the compiler about it: + +```ts +// observable.ts +export class Observable { + // ... implementation left as an exercise for the reader ... +} + +// map.ts +import { Observable } from "./observable"; +declare module "./observable" { + interface Observable { + map(f: (x: T) => U): Observable; + } +} +Observable.prototype.map = function (f) { + // ... another exercise for the reader +} + + +// consumer.ts +import { Observable } from "./observable"; +import "./map"; +let o: Observable; +o.map(x => x.toFixed()); +``` + +The module name is resolved the same way as module specifiers in `import`/`export`. +See [Modules](./Modules.md) for more information. +Then the declarations in an augmentation are merged as if they were declared in the same file as the original. + +However, there are two limitations to keep in mind: + +1. You can't declare new top-level declarations in the augmentation -- just patches to existing declarations. +2. Default exports also cannot be augmented, only named exports (since you need to augment an export by its exported name, and `default` is a reserved word - see [#14080](https://github.com/Microsoft/TypeScript/issues/14080) for details) + +## Global augmentation + +You can also add declarations to the global scope from inside a module: + +```ts +// observable.ts +export class Observable { + // ... still no implementation ... +} + +declare global { + interface Array { + toObservable(): Observable; + } +} + +Array.prototype.toObservable = function () { + // ... +} +``` + +Global augmentations have the same behavior and limits as module augmentations. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Decorators.md b/packages/handbook-v1/en/Decorators.md new file mode 100644 index 000000000000..1056bb103c9e --- /dev/null +++ b/packages/handbook-v1/en/Decorators.md @@ -0,0 +1,508 @@ +--- +title: Decorators +layout: docs +permalink: /docs/handbook/decorators.html +--- +{% raw %}# Introduction + +With the introduction of Classes in TypeScript and ES6, there now exist certain scenarios that require additional features to support annotating or modifying classes and class members. +Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members. +Decorators are a [stage 2 proposal](https://github.com/tc39/proposal-decorators) for JavaScript and are available as an experimental feature of TypeScript. + +> NOTE  Decorators are an experimental feature that may change in future releases. + +To enable experimental support for decorators, you must enable the `experimentalDecorators` compiler option either on the command line or in your `tsconfig.json`: + +**Command Line**: + +```shell +tsc --target ES5 --experimentalDecorators +``` + +**tsconfig.json**: + +```json +{ + "compilerOptions": { + "target": "ES5", + "experimentalDecorators": true + } +} +``` + +# Decorators + +A *Decorator* is a special kind of declaration that can be attached to a [class declaration](#class-decorators), [method](#method-decorators), [accessor](#accessor-decorators), [property](#property-decorators), or [parameter](#parameter-decorators). +Decorators use the form `@expression`, where `expression` must evaluate to a function that will be called at runtime with information about the decorated declaration. + +For example, given the decorator `@sealed` we might write the `sealed` function as follows: + +```ts +function sealed(target) { + // do something with 'target' ... +} +``` + +> NOTE  You can see a more detailed example of a decorator in [Class Decorators](#class-decorators), below. + +## Decorator Factories + +If we want to customize how a decorator is applied to a declaration, we can write a decorator factory. +A *Decorator Factory* is simply a function that returns the expression that will be called by the decorator at runtime. + +We can write a decorator factory in the following fashion: + +```ts +function color(value: string) { // this is the decorator factory + return function (target) { // this is the decorator + // do something with 'target' and 'value'... + } +} +``` + +> NOTE  You can see a more detailed example of a decorator factory in [Method Decorators](#method-decorators), below. + +## Decorator Composition + +Multiple decorators can be applied to a declaration, as in the following examples: + +* On a single line: + + ```ts + @f @g x + ``` + +* On multiple lines: + + ```ts + @f + @g + x + ``` + +When multiple decorators apply to a single declaration, their evaluation is similar to [function composition in mathematics](http://en.wikipedia.org/wiki/Function_composition). In this model, when composing functions *f* and *g*, the resulting composite (*f* ∘ *g*)(*x*) is equivalent to *f*(*g*(*x*)). + +As such, the following steps are performed when evaluating multiple decorators on a single declaration in TypeScript: + +1. The expressions for each decorator are evaluated top-to-bottom. +2. The results are then called as functions from bottom-to-top. + +If we were to use [decorator factories](#decorator-factories), we can observe this evaluation order with the following example: + +```ts +function f() { + console.log("f(): evaluated"); + return function (target, propertyKey: string, descriptor: PropertyDescriptor) { + console.log("f(): called"); + } +} + +function g() { + console.log("g(): evaluated"); + return function (target, propertyKey: string, descriptor: PropertyDescriptor) { + console.log("g(): called"); + } +} + +class C { + @f() + @g() + method() {} +} +``` + +Which would print this output to the console: + +```shell +f(): evaluated +g(): evaluated +g(): called +f(): called +``` + +## Decorator Evaluation + +There is a well defined order to how decorators applied to various declarations inside of a class are applied: + +1. *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each instance member. +2. *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each static member. +3. *Parameter Decorators* are applied for the constructor. +4. *Class Decorators* are applied for the class. + +## Class Decorators + +A *Class Decorator* is declared just before a class declaration. +The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition. +A class decorator cannot be used in a declaration file, or in any other ambient context (such as on a `declare` class). + +The expression for the class decorator will be called as a function at runtime, with the constructor of the decorated class as its only argument. + +If the class decorator returns a value, it will replace the class declaration with the provided constructor function. + +> NOTE  Should you choose to return a new constructor function, you must take care to maintain the original prototype. +The logic that applies decorators at runtime will **not** do this for you. + +The following is an example of a class decorator (`@sealed`) applied to the `Greeter` class: + +```ts +@sealed +class Greeter { + greeting: string; + constructor(message: string) { + this.greeting = message; + } + greet() { + return "Hello, " + this.greeting; + } +} +``` + +We can define the `@sealed` decorator using the following function declaration: + +```ts +function sealed(constructor: Function) { + Object.seal(constructor); + Object.seal(constructor.prototype); +} +``` + +When `@sealed` is executed, it will seal both the constructor and its prototype. + +Next we have an example of how to override the constructor. + +```ts +function classDecorator(constructor:T) { + return class extends constructor { + newProperty = "new property"; + hello = "override"; + } +} + +@classDecorator +class Greeter { + property = "property"; + hello: string; + constructor(m: string) { + this.hello = m; + } +} + +console.log(new Greeter("world")); +``` + +## Method Decorators + +A *Method Decorator* is declared just before a method declaration. +The decorator is applied to the *Property Descriptor* for the method, and can be used to observe, modify, or replace a method definition. +A method decorator cannot be used in a declaration file, on an overload, or in any other ambient context (such as in a `declare` class). + +The expression for the method decorator will be called as a function at runtime, with the following three arguments: + +1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member. +2. The name of the member. +3. The *Property Descriptor* for the member. + +> NOTE  The *Property Descriptor* will be `undefined` if your script target is less than `ES5`. + +If the method decorator returns a value, it will be used as the *Property Descriptor* for the method. + +> NOTE  The return value is ignored if your script target is less than `ES5`. + +The following is an example of a method decorator (`@enumerable`) applied to a method on the `Greeter` class: + +```ts +class Greeter { + greeting: string; + constructor(message: string) { + this.greeting = message; + } + + @enumerable(false) + greet() { + return "Hello, " + this.greeting; + } +} +``` + +We can define the `@enumerable` decorator using the following function declaration: + +```ts +function enumerable(value: boolean) { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + descriptor.enumerable = value; + }; +} +``` + +The `@enumerable(false)` decorator here is a [decorator factory](#decorator-factories). +When the `@enumerable(false)` decorator is called, it modifies the `enumerable` property of the property descriptor. + +## Accessor Decorators + +An *Accessor Decorator* is declared just before an accessor declaration. +The accessor decorator is applied to the *Property Descriptor* for the accessor and can be used to observe, modify, or replace an accessor's definitions. +An accessor decorator cannot be used in a declaration file, or in any other ambient context (such as in a `declare` class). + +> NOTE  TypeScript disallows decorating both the `get` and `set` accessor for a single member. +Instead, all decorators for the member must be applied to the first accessor specified in document order. +This is because decorators apply to a *Property Descriptor*, which combines both the `get` and `set` accessor, not each declaration separately. + +The expression for the accessor decorator will be called as a function at runtime, with the following three arguments: + +1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member. +2. The name of the member. +3. The *Property Descriptor* for the member. + +> NOTE  The *Property Descriptor* will be `undefined` if your script target is less than `ES5`. + +If the accessor decorator returns a value, it will be used as the *Property Descriptor* for the member. + +> NOTE  The return value is ignored if your script target is less than `ES5`. + +The following is an example of an accessor decorator (`@configurable`) applied to a member of the `Point` class: + +```ts +class Point { + private _x: number; + private _y: number; + constructor(x: number, y: number) { + this._x = x; + this._y = y; + } + + @configurable(false) + get x() { return this._x; } + + @configurable(false) + get y() { return this._y; } +} +``` + +We can define the `@configurable` decorator using the following function declaration: + +```ts +function configurable(value: boolean) { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + descriptor.configurable = value; + }; +} +``` + +## Property Decorators + +A *Property Decorator* is declared just before a property declaration. +A property decorator cannot be used in a declaration file, or in any other ambient context (such as in a `declare` class). + +The expression for the property decorator will be called as a function at runtime, with the following two arguments: + +1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member. +2. The name of the member. + +> NOTE  A *Property Descriptor* is not provided as an argument to a property decorator due to how property decorators are initialized in TypeScript. +This is because there is currently no mechanism to describe an instance property when defining members of a prototype, and no way to observe or modify the initializer for a property. The return value is ignored too. +As such, a property decorator can only be used to observe that a property of a specific name has been declared for a class. + +We can use this information to record metadata about the property, as in the following example: + +```ts +class Greeter { + @format("Hello, %s") + greeting: string; + + constructor(message: string) { + this.greeting = message; + } + greet() { + let formatString = getFormat(this, "greeting"); + return formatString.replace("%s", this.greeting); + } +} +``` + +We can then define the `@format` decorator and `getFormat` functions using the following function declarations: + +```ts +import "reflect-metadata"; + +const formatMetadataKey = Symbol("format"); + +function format(formatString: string) { + return Reflect.metadata(formatMetadataKey, formatString); +} + +function getFormat(target: any, propertyKey: string) { + return Reflect.getMetadata(formatMetadataKey, target, propertyKey); +} +``` + +The `@format("Hello, %s")` decorator here is a [decorator factory](#decorator-factories). +When `@format("Hello, %s")` is called, it adds a metadata entry for the property using the `Reflect.metadata` function from the `reflect-metadata` library. +When `getFormat` is called, it reads the metadata value for the format. + +> NOTE  This example requires the `reflect-metadata` library. +See [Metadata](#metadata) for more information about the `reflect-metadata` library. + +## Parameter Decorators + +A *Parameter Decorator* is declared just before a parameter declaration. +The parameter decorator is applied to the function for a class constructor or method declaration. +A parameter decorator cannot be used in a declaration file, an overload, or in any other ambient context (such as in a `declare` class). + +The expression for the parameter decorator will be called as a function at runtime, with the following three arguments: + +1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member. +2. The name of the member. +3. The ordinal index of the parameter in the function's parameter list. + +> NOTE  A parameter decorator can only be used to observe that a parameter has been declared on a method. + +The return value of the parameter decorator is ignored. + +The following is an example of a parameter decorator (`@required`) applied to parameter of a member of the `Greeter` class: + +```ts +class Greeter { + greeting: string; + + constructor(message: string) { + this.greeting = message; + } + + @validate + greet(@required name: string) { + return "Hello " + name + ", " + this.greeting; + } +} +``` + +We can then define the `@required` and `@validate` decorators using the following function declarations: + +```ts +import "reflect-metadata"; + +const requiredMetadataKey = Symbol("required"); + +function required(target: Object, propertyKey: string | symbol, parameterIndex: number) { + let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || []; + existingRequiredParameters.push(parameterIndex); + Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey); +} + +function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor) { + let method = descriptor.value; + descriptor.value = function () { + let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName); + if (requiredParameters) { + for (let parameterIndex of requiredParameters) { + if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) { + throw new Error("Missing required argument."); + } + } + } + + return method.apply(this, arguments); + } +} +``` + +The `@required` decorator adds a metadata entry that marks the parameter as required. +The `@validate` decorator then wraps the existing `greet` method in a function that validates the arguments before invoking the original method. + +> NOTE  This example requires the `reflect-metadata` library. +See [Metadata](#metadata) for more information about the `reflect-metadata` library. + +## Metadata + +Some examples use the `reflect-metadata` library which adds a polyfill for an [experimental metadata API](https://github.com/rbuckton/ReflectDecorators). +This library is not yet part of the ECMAScript (JavaScript) standard. +However, once decorators are officially adopted as part of the ECMAScript standard these extensions will be proposed for adoption. + +You can install this library via npm: + +```shell +npm i reflect-metadata --save +``` + +TypeScript includes experimental support for emitting certain types of metadata for declarations that have decorators. +To enable this experimental support, you must set the `emitDecoratorMetadata` compiler option either on the command line or in your `tsconfig.json`: + +**Command Line**: + +```shell +tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata +``` + +**tsconfig.json**: + +```json +{ + "compilerOptions": { + "target": "ES5", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} +``` + +When enabled, as long as the `reflect-metadata` library has been imported, additional design-time type information will be exposed at runtime. + +We can see this in action in the following example: + +```ts +import "reflect-metadata"; + +class Point { + x: number; + y: number; +} + +class Line { + private _p0: Point; + private _p1: Point; + + @validate + set p0(value: Point) { this._p0 = value; } + get p0() { return this._p0; } + + @validate + set p1(value: Point) { this._p1 = value; } + get p1() { return this._p1; } +} + +function validate(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor) { + let set = descriptor.set; + descriptor.set = function (value: T) { + let type = Reflect.getMetadata("design:type", target, propertyKey); + if (!(value instanceof type)) { + throw new TypeError("Invalid type."); + } + set.call(target, value); + } +} +``` + +The TypeScript compiler will inject design-time type information using the `@Reflect.metadata` decorator. +You could consider it the equivalent of the following TypeScript: + +```ts +class Line { + private _p0: Point; + private _p1: Point; + + @validate + @Reflect.metadata("design:type", Point) + set p0(value: Point) { this._p0 = value; } + get p0() { return this._p0; } + + @validate + @Reflect.metadata("design:type", Point) + set p1(value: Point) { this._p1 = value; } + get p1() { return this._p1; } +} + +``` + +> NOTE  Decorator metadata is an experimental feature and may introduce breaking changes in future releases. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Enums.md b/packages/handbook-v1/en/Enums.md new file mode 100644 index 000000000000..837d9ff8db4a --- /dev/null +++ b/packages/handbook-v1/en/Enums.md @@ -0,0 +1,338 @@ +--- +title: Enums +layout: docs +permalink: /docs/handbook/enums.html +--- +{% raw %}# Enums + +Enums allow us to define a set of named constants. +Using enums can make it easier to document intent, or create a set of distinct cases. +TypeScript provides both numeric and string-based enums. + +## Numeric enums + +We'll first start off with numeric enums, which are probably more familiar if you're coming from other languages. +An enum can be defined using the `enum` keyword. + +```ts +enum Direction { + Up = 1, + Down, + Left, + Right, +} +``` + +Above, we have a numeric enum where `Up` is initialized with `1`. +All of the following members are auto-incremented from that point on. +In other words, `Direction.Up` has the value `1`, `Down` has `2`, `Left` has `3`, and `Right` has `4`. + +If we wanted, we could leave off the initializers entirely: + +```ts +enum Direction { + Up, + Down, + Left, + Right, +} +``` + +Here, `Up` would have the value `0`, `Down` would have `1`, etc. +This auto-incrementing behavior is useful for cases where we might not care about the member values themselves, but do care that each value is distinct from other values in the same enum. + +Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum: + +```ts +enum Response { + No = 0, + Yes = 1, +} + +function respond(recipient: string, message: Response): void { + // ... +} + +respond("Princess Caroline", Response.Yes) +``` + +Numeric enums can be mixed in [computed and constant members (see below)](#computed-and-constant-members). +The short story is, enums without initializers either need to be first, or have to come after numeric enums initialized with numeric constants or other constant enum members. +In other words, the following isn't allowed: + +```ts +enum E { + A = getSomeValue(), + B, // Error! Enum member must have initializer. +} +``` + +## String enums + +String enums are a similar concept, but have some subtle [runtime differences](#enums-at-runtime) as documented below. +In a string enum, each member has to be constant-initialized with a string literal, or with another string enum member. + +```ts +enum Direction { + Up = "UP", + Down = "DOWN", + Left = "LEFT", + Right = "RIGHT", +} +``` + +While string enums don't have auto-incrementing behavior, string enums have the benefit that they "serialize" well. +In other words, if you were debugging and had to read the runtime value of a numeric enum, the value is often opaque - it doesn't convey any useful meaning on its own (though [reverse mapping](#enums-at-runtime) can often help), string enums allow you to give a meaningful and readable value when your code runs, independent of the name of the enum member itself. + +## Heterogeneous enums + +Technically enums can be mixed with string and numeric members, but it's not clear why you would ever want to do so: + +```ts +enum BooleanLikeHeterogeneousEnum { + No = 0, + Yes = "YES", +} +``` + +Unless you're really trying to take advantage of JavaScript's runtime behavior in a clever way, it's advised that you don't do this. + +## Computed and constant members + +Each enum member has a value associated with it which can be either *constant* or *computed*. +An enum member is considered constant if: + +* It is the first member in the enum and it has no initializer, in which case it's assigned the value `0`: + + ```ts + // E.X is constant: + enum E { X } + ``` + +* It does not have an initializer and the preceding enum member was a *numeric* constant. + In this case the value of the current enum member will be the value of the preceding enum member plus one. + + ```ts + // All enum members in 'E1' and 'E2' are constant. + + enum E1 { X, Y, Z } + + enum E2 { + A = 1, B, C + } + ``` + +* The enum member is initialized with a constant enum expression. + A constant enum expression is a subset of TypeScript expressions that can be fully evaluated at compile time. + An expression is a constant enum expression if it is: + + 1. a literal enum expression (basically a string literal or a numeric literal) + 2. a reference to previously defined constant enum member (which can originate from a different enum) + 3. a parenthesized constant enum expression + 4. one of the `+`, `-`, `~` unary operators applied to constant enum expression + 5. `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `>>>`, `&`, `|`, `^` binary operators with constant enum expressions as operands + + It is a compile time error for constant enum expressions to be evaluated to `NaN` or `Infinity`. + +In all other cases enum member is considered computed. + +```ts +enum FileAccess { + // constant members + None, + Read = 1 << 1, + Write = 1 << 2, + ReadWrite = Read | Write, + // computed member + G = "123".length +} +``` + +## Union enums and enum member types + +There is a special subset of constant enum members that aren't calculated: literal enum members. +A literal enum member is a constant enum member with no initialized value, or with values that are initialized to + +* any string literal (e.g. `"foo"`, `"bar`, `"baz"`) +* any numeric literal (e.g. `1`, `100`) +* a unary minus applied to any numeric literal (e.g. `-1`, `-100`) + +When all members in an enum have literal enum values, some special semantics come to play. + +The first is that enum members also become types as well! +For example, we can say that certain members can *only* have the value of an enum member: + +```ts +enum ShapeKind { + Circle, + Square, +} + +interface Circle { + kind: ShapeKind.Circle; + radius: number; +} + +interface Square { + kind: ShapeKind.Square; + sideLength: number; +} + +let c: Circle = { + kind: ShapeKind.Square, // Error! Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'. + radius: 100, +} +``` + +The other change is that enum types themselves effectively become a *union* of each enum member. +While we haven't discussed [union types](./Advanced%20Types.md#union-types) yet, all that you need to know is that with union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in the enum itself. +Because of that, TypeScript can catch silly bugs where we might be comparing values incorrectly. +For example: + +```ts +enum E { + Foo, + Bar, +} + +function f(x: E) { + if (x !== E.Foo || x !== E.Bar) { + // ~~~~~~~~~~~ + // Error! This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap. + } +} +``` + +In that example, we first checked whether `x` was *not* `E.Foo`. +If that check succeeds, then our `||` will short-circuit, and the body of the 'if' will run. +However, if the check didn't succeed, then `x` can *only* be `E.Foo`, so it doesn't make sense to see whether it's equal to `E.Bar`. + +## Enums at runtime + +Enums are real objects that exist at runtime. +For example, the following enum + +```ts +enum E { + X, Y, Z +} +``` + +can actually be passed around to functions + +```ts +function f(obj: { X: number }) { + return obj.X; +} + +// Works, since 'E' has a property named 'X' which is a number. +f(E); +``` + +## Enums at compile time + +Even though Enums are real objects that exist at runtime, the `keyof` keyword works differently than you might expect for typical objects. Instead, use `keyof typeof` to get a Type that represents all Enum keys as strings. + +```ts +enum LogLevel { + ERROR, WARN, INFO, DEBUG +} + +/** + * This is equivalent to: + * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'; + */ +type LogLevelStrings = keyof typeof LogLevel; + +function printImportant(key: LogLevelStrings, message: string) { + const num = LogLevel[key]; + if (num <= LogLevel.WARN) { + console.log('Log level key is: ', key); + console.log('Log level value is: ', num); + console.log('Log level message is: ', message); + } +} +printImportant('ERROR', 'This is a message'); +``` + +### Reverse mappings + +In addition to creating an object with property names for members, numeric enums members also get a *reverse mapping* from enum values to enum names. +For example, in this example: + +```ts +enum Enum { + A +} +let a = Enum.A; +let nameOfA = Enum[a]; // "A" +``` + +TypeScript might compile this down to something like the the following JavaScript: + +```js +var Enum; +(function (Enum) { + Enum[Enum["A"] = 0] = "A"; +})(Enum || (Enum = {})); +var a = Enum.A; +var nameOfA = Enum[a]; // "A" +``` + +In this generated code, an enum is compiled into an object that stores both forward (`name` -> `value`) and reverse (`value` -> `name`) mappings. +References to other enum members are always emitted as property accesses and never inlined. + +Keep in mind that string enum members *do not* get a reverse mapping generated at all. + +### `const` enums + +In most cases, enums are a perfectly valid solution. +However sometimes requirements are tighter. +To avoid paying the cost of extra generated code and additional indirection when accessing enum values, it's possible to use `const` enums. +Const enums are defined using the `const` modifier on our enums: + +```ts +const enum Enum { + A = 1, + B = A * 2 +} +``` + +Const enums can only use constant enum expressions and unlike regular enums they are completely removed during compilation. +Const enum members are inlined at use sites. +This is possible since const enums cannot have computed members. + +```ts +const enum Directions { + Up, + Down, + Left, + Right +} + +let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right] +``` + +in generated code will become + +```js +var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; +``` + +## Ambient enums + +Ambient enums are used to describe the shape of already existing enum types. + +```ts +declare enum Enum { + A = 1, + B, + C = 2 +} +``` + +One important difference between ambient and non-ambient enums is that, in regular enums, members that don't have an initializer will be considered constant if its preceding enum member is considered constant. +In contrast, an ambient (and non-const) enum member that does not have initializer is *always* considered computed. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Functions.md b/packages/handbook-v1/en/Functions.md new file mode 100644 index 000000000000..66fb1da6a66b --- /dev/null +++ b/packages/handbook-v1/en/Functions.md @@ -0,0 +1,482 @@ +--- +title: Functions +layout: docs +permalink: /docs/handbook/functions.html +--- +{% raw %}# Introduction + +Functions are the fundamental building block of any application in JavaScript. +They're how you build up layers of abstraction, mimicking classes, information hiding, and modules. +In TypeScript, while there are classes, namespaces, and modules, functions still play the key role in describing how to *do* things. +TypeScript also adds some new capabilities to the standard JavaScript functions to make them easier to work with. + +# Functions + +To begin, just as in JavaScript, TypeScript functions can be created both as a named function or as an anonymous function. +This allows you to choose the most appropriate approach for your application, whether you're building a list of functions in an API or a one-off function to hand off to another function. + +To quickly recap what these two approaches look like in JavaScript: + +```ts +// Named function +function add(x, y) { + return x + y; +} + +// Anonymous function +let myAdd = function(x, y) { return x + y; }; +``` + +Just as in JavaScript, functions can refer to variables outside of the function body. +When they do so, they're said to *capture* these variables. +While understanding how this works (and the trade-offs when using this technique) is outside of the scope of this article, having a firm understanding how this mechanic works is an important piece of working with JavaScript and TypeScript. + +```ts +let z = 100; + +function addToZ(x, y) { + return x + y + z; +} +``` + +# Function Types + +## Typing the function + +Let's add types to our simple examples from earlier: + +```ts +function add(x: number, y: number): number { + return x + y; +} + +let myAdd = function(x: number, y: number): number { return x + y; }; +``` + +We can add types to each of the parameters and then to the function itself to add a return type. +TypeScript can figure the return type out by looking at the return statements, so we can also optionally leave this off in many cases. + +## Writing the function type + +Now that we've typed the function, let's write the full type of the function out by looking at each piece of the function type. + +```ts +let myAdd: (x: number, y: number) => number = + function(x: number, y: number): number { return x + y; }; +``` + +A function's type has the same two parts: the type of the arguments and the return type. +When writing out the whole function type, both parts are required. +We write out the parameter types just like a parameter list, giving each parameter a name and a type. +This name is just to help with readability. +We could have instead written: + +```ts +let myAdd: (baseValue: number, increment: number) => number = + function(x: number, y: number): number { return x + y; }; +``` + +As long as the parameter types line up, it's considered a valid type for the function, regardless of the names you give the parameters in the function type. + +The second part is the return type. +We make it clear which is the return type by using a fat arrow (`=>`) between the parameters and the return type. +As mentioned before, this is a required part of the function type, so if the function doesn't return a value, you would use `void` instead of leaving it off. + +Of note, only the parameters and the return type make up the function type. +Captured variables are not reflected in the type. +In effect, captured variables are part of the "hidden state" of any function and do not make up its API. + +## Inferring the types + +In playing with the example, you may notice that the TypeScript compiler can figure out the type even if you only have types on one side of the equation: + + +```ts +// myAdd has the full function type +let myAdd = function(x: number, y: number): number { return x + y; }; + +// The parameters 'x' and 'y' have the type number +let myAdd: (baseValue: number, increment: number) => number = + function(x, y) { return x + y; }; +``` + +This is called "contextual typing", a form of type inference. +This helps cut down on the amount of effort to keep your program typed. + +# Optional and Default Parameters + +In TypeScript, every parameter is assumed to be required by the function. +This doesn't mean that it can't be given `null` or `undefined`, but rather, when the function is called, the compiler will check that the user has provided a value for each parameter. +The compiler also assumes that these parameters are the only parameters that will be passed to the function. +In short, the number of arguments given to a function has to match the number of parameters the function expects. + +```ts +function buildName(firstName: string, lastName: string) { + return firstName + " " + lastName; +} + +let result1 = buildName("Bob"); // error, too few parameters +let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters +let result3 = buildName("Bob", "Adams"); // ah, just right +``` + +In JavaScript, every parameter is optional, and users may leave them off as they see fit. +When they do, their value is `undefined`. +We can get this functionality in TypeScript by adding a `?` to the end of parameters we want to be optional. +For example, let's say we want the last name parameter from above to be optional: + +```ts +function buildName(firstName: string, lastName?: string) { + if (lastName) + return firstName + " " + lastName; + else + return firstName; +} + +let result1 = buildName("Bob"); // works correctly now +let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters +let result3 = buildName("Bob", "Adams"); // ah, just right +``` + +Any optional parameters must follow required parameters. +Had we wanted to make the first name optional, rather than the last name, we would need to change the order of parameters in the function, putting the first name last in the list. + +In TypeScript, we can also set a value that a parameter will be assigned if the user does not provide one, or if the user passes `undefined` in its place. +These are called default-initialized parameters. +Let's take the previous example and default the last name to `"Smith"`. + +```ts +function buildName(firstName: string, lastName = "Smith") { + return firstName + " " + lastName; +} + +let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith" +let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith" +let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters +let result4 = buildName("Bob", "Adams"); // ah, just right +``` + +Default-initialized parameters that come after all required parameters are treated as optional, and just like optional parameters, can be omitted when calling their respective function. +This means optional parameters and trailing default parameters will share commonality in their types, so both + +```ts +function buildName(firstName: string, lastName?: string) { + // ... +} +``` + +and + +```ts +function buildName(firstName: string, lastName = "Smith") { + // ... +} +``` + +share the same type `(firstName: string, lastName?: string) => string`. +The default value of `lastName` disappears in the type, only leaving behind the fact that the parameter is optional. + +Unlike plain optional parameters, default-initialized parameters don't *need* to occur after required parameters. +If a default-initialized parameter comes before a required parameter, users need to explicitly pass `undefined` to get the default initialized value. +For example, we could write our last example with only a default initializer on `firstName`: + +```ts +function buildName(firstName = "Will", lastName: string) { + return firstName + " " + lastName; +} + +let result1 = buildName("Bob"); // error, too few parameters +let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters +let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams" +let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams" +``` + +# Rest Parameters + +Required, optional, and default parameters all have one thing in common: they talk about one parameter at a time. +Sometimes, you want to work with multiple parameters as a group, or you may not know how many parameters a function will ultimately take. +In JavaScript, you can work with the arguments directly using the `arguments` variable that is visible inside every function body. + +In TypeScript, you can gather these arguments together into a variable: + +```ts +function buildName(firstName: string, ...restOfName: string[]) { + return firstName + " " + restOfName.join(" "); +} + +// employeeName will be "Joseph Samuel Lucas MacKinzie" +let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie"); +``` + +*Rest parameters* are treated as a boundless number of optional parameters. +When passing arguments for a rest parameter, you can use as many as you want; you can even pass none. +The compiler will build an array of the arguments passed in with the name given after the ellipsis (`...`), allowing you to use it in your function. + +The ellipsis is also used in the type of the function with rest parameters: + +```ts +function buildName(firstName: string, ...restOfName: string[]) { + return firstName + " " + restOfName.join(" "); +} + +let buildNameFun: (fname: string, ...rest: string[]) => string = buildName; +``` + +# `this` + +Learning how to use `this` in JavaScript is something of a rite of passage. +Since TypeScript is a superset of JavaScript, TypeScript developers also need to learn how to use `this` and how to spot when it's not being used correctly. +Fortunately, TypeScript lets you catch incorrect uses of `this` with a couple of techniques. +If you need to learn how `this` works in JavaScript, though, first read Yehuda Katz's [Understanding JavaScript Function Invocation and "this"](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/). +Yehuda's article explains the inner workings of `this` very well, so we'll just cover the basics here. + +## `this` and arrow functions + +In JavaScript, `this` is a variable that's set when a function is called. +This makes it a very powerful and flexible feature, but it comes at the cost of always having to know about the context that a function is executing in. +This is notoriously confusing, especially when returning a function or passing a function as an argument. + +Let's look at an example: + +```ts +let deck = { + suits: ["hearts", "spades", "clubs", "diamonds"], + cards: Array(52), + createCardPicker: function() { + return function() { + let pickedCard = Math.floor(Math.random() * 52); + let pickedSuit = Math.floor(pickedCard / 13); + + return {suit: this.suits[pickedSuit], card: pickedCard % 13}; + } + } +} + +let cardPicker = deck.createCardPicker(); +let pickedCard = cardPicker(); + +alert("card: " + pickedCard.card + " of " + pickedCard.suit); +``` + +Notice that `createCardPicker` is a function that itself returns a function. +If we tried to run the example, we would get an error instead of the expected alert box. +This is because the `this` being used in the function created by `createCardPicker` will be set to `window` instead of our `deck` object. +That's because we call `cardPicker()` on its own. +A top-level non-method syntax call like this will use `window` for `this`. +(Note: under strict mode, `this` will be `undefined` rather than `window`). + +We can fix this by making sure the function is bound to the correct `this` before we return the function to be used later. +This way, regardless of how it's later used, it will still be able to see the original `deck` object. +To do this, we change the function expression to use the ECMAScript 6 arrow syntax. +Arrow functions capture the `this` where the function is created rather than where it is invoked: + +```ts +let deck = { + suits: ["hearts", "spades", "clubs", "diamonds"], + cards: Array(52), + createCardPicker: function() { + // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here + return () => { + let pickedCard = Math.floor(Math.random() * 52); + let pickedSuit = Math.floor(pickedCard / 13); + + return {suit: this.suits[pickedSuit], card: pickedCard % 13}; + } + } +} + +let cardPicker = deck.createCardPicker(); +let pickedCard = cardPicker(); + +alert("card: " + pickedCard.card + " of " + pickedCard.suit); +``` + +Even better, TypeScript will warn you when you make this mistake if you pass the `--noImplicitThis` flag to the compiler. +It will point out that `this` in `this.suits[pickedSuit]` is of type `any`. + +## `this` parameters + +Unfortunately, the type of `this.suits[pickedSuit]` is still `any`. +That's because `this` comes from the function expression inside the object literal. +To fix this, you can provide an explicit `this` parameter. +`this` parameters are fake parameters that come first in the parameter list of a function: + +```ts +function f(this: void) { + // make sure `this` is unusable in this standalone function +} +``` + +Let's add a couple of interfaces to our example above, `Card` and `Deck`, to make the types clearer and easier to reuse: + +```ts +interface Card { + suit: string; + card: number; +} +interface Deck { + suits: string[]; + cards: number[]; + createCardPicker(this: Deck): () => Card; +} +let deck: Deck = { + suits: ["hearts", "spades", "clubs", "diamonds"], + cards: Array(52), + // NOTE: The function now explicitly specifies that its callee must be of type Deck + createCardPicker: function(this: Deck) { + return () => { + let pickedCard = Math.floor(Math.random() * 52); + let pickedSuit = Math.floor(pickedCard / 13); + + return {suit: this.suits[pickedSuit], card: pickedCard % 13}; + } + } +} + +let cardPicker = deck.createCardPicker(); +let pickedCard = cardPicker(); + +alert("card: " + pickedCard.card + " of " + pickedCard.suit); +``` + +Now TypeScript knows that `createCardPicker` expects to be called on a `Deck` object. +That means that `this` is of type `Deck` now, not `any`, so `--noImplicitThis` will not cause any errors. + +### `this` parameters in callbacks + +You can also run into errors with `this` in callbacks, when you pass functions to a library that will later call them. +Because the library that calls your callback will call it like a normal function, `this` will be `undefined`. +With some work you can use `this` parameters to prevent errors with callbacks too. +First, the library author needs to annotate the callback type with `this`: + +```ts +interface UIElement { + addClickListener(onclick: (this: void, e: Event) => void): void; +} +``` + +`this: void` means that `addClickListener` expects `onclick` to be a function that does not require a `this` type. +Second, annotate your calling code with `this`: + +```ts +class Handler { + info: string; + onClickBad(this: Handler, e: Event) { + // oops, used `this` here. using this callback would crash at runtime + this.info = e.message; + } +} +let h = new Handler(); +uiElement.addClickListener(h.onClickBad); // error! +``` + +With `this` annotated, you make it explicit that `onClickBad` must be called on an instance of `Handler`. +Then TypeScript will detect that `addClickListener` requires a function that has `this: void`. +To fix the error, change the type of `this`: + +```ts +class Handler { + info: string; + onClickGood(this: void, e: Event) { + // can't use `this` here because it's of type void! + console.log('clicked!'); + } +} +let h = new Handler(); +uiElement.addClickListener(h.onClickGood); +``` + +Because `onClickGood` specifies its `this` type as `void`, it is legal to pass to `addClickListener`. +Of course, this also means that it can't use `this.info`. +If you want both then you'll have to use an arrow function: + +```ts +class Handler { + info: string; + onClickGood = (e: Event) => { this.info = e.message } +} +``` + +This works because arrow functions use the outer `this`, so you can always pass them to something that expects `this: void`. +The downside is that one arrow function is created per object of type Handler. +Methods, on the other hand, are only created once and attached to Handler's prototype. +They are shared between all objects of type Handler. + +# Overloads + +JavaScript is inherently a very dynamic language. +It's not uncommon for a single JavaScript function to return different types of objects based on the shape of the arguments passed in. + +```ts +let suits = ["hearts", "spades", "clubs", "diamonds"]; + +function pickCard(x): any { + // Check to see if we're working with an object/array + // if so, they gave us the deck and we'll pick the card + if (typeof x == "object") { + let pickedCard = Math.floor(Math.random() * x.length); + return pickedCard; + } + // Otherwise just let them pick the card + else if (typeof x == "number") { + let pickedSuit = Math.floor(x / 13); + return { suit: suits[pickedSuit], card: x % 13 }; + } +} + +let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; +let pickedCard1 = myDeck[pickCard(myDeck)]; +alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); + +let pickedCard2 = pickCard(15); +alert("card: " + pickedCard2.card + " of " + pickedCard2.suit); +``` + +Here, the `pickCard` function will return two different things based on what the user has passed in. +If the users passes in an object that represents the deck, the function will pick the card. +If the user picks the card, we tell them which card they've picked. +But how do we describe this to the type system? + +The answer is to supply multiple function types for the same function as a list of overloads. +This list is what the compiler will use to resolve function calls. +Let's create a list of overloads that describe what our `pickCard` accepts and what it returns. + +```ts +let suits = ["hearts", "spades", "clubs", "diamonds"]; + +function pickCard(x: {suit: string; card: number; }[]): number; +function pickCard(x: number): {suit: string; card: number; }; +function pickCard(x): any { + // Check to see if we're working with an object/array + // if so, they gave us the deck and we'll pick the card + if (typeof x == "object") { + let pickedCard = Math.floor(Math.random() * x.length); + return pickedCard; + } + // Otherwise just let them pick the card + else if (typeof x == "number") { + let pickedSuit = Math.floor(x / 13); + return { suit: suits[pickedSuit], card: x % 13 }; + } +} + +let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; +let pickedCard1 = myDeck[pickCard(myDeck)]; +alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); + +let pickedCard2 = pickCard(15); +alert("card: " + pickedCard2.card + " of " + pickedCard2.suit); +``` + +With this change, the overloads now give us type checked calls to the `pickCard` function. + +In order for the compiler to pick the correct type check, it follows a similar process to the underlying JavaScript. +It looks at the overload list and, proceeding with the first overload, attempts to call the function with the provided parameters. +If it finds a match, it picks this overload as the correct overload. +For this reason, it's customary to order overloads from most specific to least specific. + +Note that the `function pickCard(x): any` piece is not part of the overload list, so it only has two overloads: one that takes an object and one that takes a number. +Calling `pickCard` with any other parameter types would cause an error. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Generics.md b/packages/handbook-v1/en/Generics.md new file mode 100644 index 000000000000..aa7a5960e18f --- /dev/null +++ b/packages/handbook-v1/en/Generics.md @@ -0,0 +1,333 @@ +--- +title: Generics +layout: docs +permalink: /docs/handbook/generics.html +--- +{% raw %}# Introduction + +A major part of software engineering is building components that not only have well-defined and consistent APIs, but are also reusable. +Components that are capable of working on the data of today as well as the data of tomorrow will give you the most flexible capabilities for building up large software systems. + +In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is *generics*, that is, being able to create a component that can work over a variety of types rather than a single one. +This allows users to consume these components and use their own types. + +# Hello World of Generics + +To start off, let's do the "hello world" of generics: the identity function. +The identity function is a function that will return back whatever is passed in. +You can think of this in a similar way to the `echo` command. + +Without generics, we would either have to give the identity function a specific type: + +```ts +function identity(arg: number): number { + return arg; +} +``` + +Or, we could describe the identity function using the `any` type: + +```ts +function identity(arg: any): any { + return arg; +} +``` + +While using `any` is certainly generic in that it will cause the function to accept any and all types for the type of `arg`, we actually are losing the information about what that type was when the function returns. +If we passed in a number, the only information we have is that any type could be returned. + +Instead, we need a way of capturing the type of the argument in such a way that we can also use it to denote what is being returned. +Here, we will use a *type variable*, a special kind of variable that works on types rather than values. + +```ts +function identity(arg: T): T { + return arg; +} +``` + +We've now added a type variable `T` to the identity function. +This `T` allows us to capture the type the user provides (e.g. `number`), so that we can use that information later. +Here, we use `T` again as the return type. On inspection, we can now see the same type is used for the argument and the return type. +This allows us to traffic that type information in one side of the function and out the other. + +We say that this version of the `identity` function is generic, as it works over a range of types. +Unlike using `any`, it's also just as precise (ie, it doesn't lose any information) as the first `identity` function that used numbers for the argument and return type. + +Once we've written the generic identity function, we can call it in one of two ways. +The first way is to pass all of the arguments, including the type argument, to the function: + +```ts +let output = identity("myString"); // type of output will be 'string' +``` + +Here we explicitly set `T` to be `string` as one of the arguments to the function call, denoted using the `<>` around the arguments rather than `()`. + +The second way is also perhaps the most common. Here we use *type argument inference* -- that is, we want the compiler to set the value of `T` for us automatically based on the type of the argument we pass in: + +```ts +let output = identity("myString"); // type of output will be 'string' +``` + +Notice that we didn't have to explicitly pass the type in the angle brackets (`<>`); the compiler just looked at the value `"myString"`, and set `T` to its type. +While type argument inference can be a helpful tool to keep code shorter and more readable, you may need to explicitly pass in the type arguments as we did in the previous example when the compiler fails to infer the type, as may happen in more complex examples. + +# Working with Generic Type Variables + +When you begin to use generics, you'll notice that when you create generic functions like `identity`, the compiler will enforce that you use any generically typed parameters in the body of the function correctly. +That is, that you actually treat these parameters as if they could be any and all types. + +Let's take our `identity` function from earlier: + +```ts +function identity(arg: T): T { + return arg; +} +``` + +What if we want to also log the length of the argument `arg` to the console with each call? +We might be tempted to write this: + +```ts +function loggingIdentity(arg: T): T { + console.log(arg.length); // Error: T doesn't have .length + return arg; +} +``` + +When we do, the compiler will give us an error that we're using the `.length` member of `arg`, but nowhere have we said that `arg` has this member. +Remember, we said earlier that these type variables stand in for any and all types, so someone using this function could have passed in a `number` instead, which does not have a `.length` member. + +Let's say that we've actually intended this function to work on arrays of `T` rather than `T` directly. Since we're working with arrays, the `.length` member should be available. +We can describe this just like we would create arrays of other types: + +```ts +function loggingIdentity(arg: T[]): T[] { + console.log(arg.length); // Array has a .length, so no more error + return arg; +} +``` + +You can read the type of `loggingIdentity` as "the generic function `loggingIdentity` takes a type parameter `T`, and an argument `arg` which is an array of `T`s, and returns an array of `T`s." +If we passed in an array of numbers, we'd get an array of numbers back out, as `T` would bind to `number`. +This allows us to use our generic type variable `T` as part of the types we're working with, rather than the whole type, giving us greater flexibility. + +We can alternatively write the sample example this way: + +```ts +function loggingIdentity(arg: Array): Array { + console.log(arg.length); // Array has a .length, so no more error + return arg; +} +``` + +You may already be familiar with this style of type from other languages. +In the next section, we'll cover how you can create your own generic types like `Array`. + +# Generic Types + +In previous sections, we created generic identity functions that worked over a range of types. +In this section, we'll explore the type of the functions themselves and how to create generic interfaces. + +The type of generic functions is just like those of non-generic functions, with the type parameters listed first, similarly to function declarations: + +```ts +function identity(arg: T): T { + return arg; +} + +let myIdentity: (arg: T) => T = identity; +``` + +We could also have used a different name for the generic type parameter in the type, so long as the number of type variables and how the type variables are used line up. + +```ts +function identity(arg: T): T { + return arg; +} + +let myIdentity: (arg: U) => U = identity; +``` + +We can also write the generic type as a call signature of an object literal type: + +```ts +function identity(arg: T): T { + return arg; +} + +let myIdentity: {(arg: T): T} = identity; +``` + +Which leads us to writing our first generic interface. +Let's take the object literal from the previous example and move it to an interface: + +```ts +interface GenericIdentityFn { + (arg: T): T; +} + +function identity(arg: T): T { + return arg; +} + +let myIdentity: GenericIdentityFn = identity; +``` + +In a similar example, we may want to move the generic parameter to be a parameter of the whole interface. +This lets us see what type(s) we're generic over (e.g. `Dictionary` rather than just `Dictionary`). +This makes the type parameter visible to all the other members of the interface. + +```ts +interface GenericIdentityFn { + (arg: T): T; +} + +function identity(arg: T): T { + return arg; +} + +let myIdentity: GenericIdentityFn = identity; +``` + +Notice that our example has changed to be something slightly different. +Instead of describing a generic function, we now have a non-generic function signature that is a part of a generic type. +When we use `GenericIdentityFn`, we now will also need to specify the corresponding type argument (here: `number`), effectively locking in what the underlying call signature will use. +Understanding when to put the type parameter directly on the call signature and when to put it on the interface itself will be helpful in describing what aspects of a type are generic. + +In addition to generic interfaces, we can also create generic classes. +Note that it is not possible to create generic enums and namespaces. + +# Generic Classes + +A generic class has a similar shape to a generic interface. +Generic classes have a generic type parameter list in angle brackets (`<>`) following the name of the class. + +```ts +class GenericNumber { + zeroValue: T; + add: (x: T, y: T) => T; +} + +let myGenericNumber = new GenericNumber(); +myGenericNumber.zeroValue = 0; +myGenericNumber.add = function(x, y) { return x + y; }; +``` + +This is a pretty literal use of the `GenericNumber` class, but you may have noticed that nothing is restricting it to only use the `number` type. +We could have instead used `string` or even more complex objects. + +```ts +let stringNumeric = new GenericNumber(); +stringNumeric.zeroValue = ""; +stringNumeric.add = function(x, y) { return x + y; }; + +console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); +``` + +Just as with interface, putting the type parameter on the class itself lets us make sure all of the properties of the class are working with the same type. + +As we covered in [our section on classes](./Classes.md), a class has two sides to its type: the static side and the instance side. +Generic classes are only generic over their instance side rather than their static side, so when working with classes, static members can not use the class's type parameter. + +# Generic Constraints + +If you remember from an earlier example, you may sometimes want to write a generic function that works on a set of types where you have some knowledge about what capabilities that set of types will have. +In our `loggingIdentity` example, we wanted to be able to access the `.length` property of `arg`, but the compiler could not prove that every type had a `.length` property, so it warns us that we can't make this assumption. + +```ts +function loggingIdentity(arg: T): T { + console.log(arg.length); // Error: T doesn't have .length + return arg; +} +``` + +Instead of working with any and all types, we'd like to constrain this function to work with any and all types that also have the `.length` property. +As long as the type has this member, we'll allow it, but it's required to have at least this member. +To do so, we must list our requirement as a constraint on what T can be. + +To do so, we'll create an interface that describes our constraint. +Here, we'll create an interface that has a single `.length` property and then we'll use this interface and the `extends` keyword to denote our constraint: + +```ts +interface Lengthwise { + length: number; +} + +function loggingIdentity(arg: T): T { + console.log(arg.length); // Now we know it has a .length property, so no more error + return arg; +} +``` + +Because the generic function is now constrained, it will no longer work over any and all types: + +```ts +loggingIdentity(3); // Error, number doesn't have a .length property +``` + +Instead, we need to pass in values whose type has all the required properties: + +```ts +loggingIdentity({length: 10, value: 3}); +``` + +## Using Type Parameters in Generic Constraints + +You can declare a type parameter that is constrained by another type parameter. +For example, here we'd like to get a property from an object given its name. +We'd like to ensure that we're not accidentally grabbing a property that does not exist on the `obj`, so we'll place a constraint between the two types: + +```ts +function getProperty(obj: T, key: K) { + return obj[key]; +} + +let x = { a: 1, b: 2, c: 3, d: 4 }; + +getProperty(x, "a"); // okay +getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'. +``` + +## Using Class Types in Generics + +When creating factories in TypeScript using generics, it is necessary to refer to class types by their constructor functions. For example, + +```ts +function create(c: {new(): T; }): T { + return new c(); +} +``` + +A more advanced example uses the prototype property to infer and constrain relationships between the constructor function and the instance side of class types. + +```ts +class BeeKeeper { + hasMask: boolean; +} + +class ZooKeeper { + nametag: string; +} + +class Animal { + numLegs: number; +} + +class Bee extends Animal { + keeper: BeeKeeper; +} + +class Lion extends Animal { + keeper: ZooKeeper; +} + +function createInstance(c: new () => A): A { + return new c(); +} + +createInstance(Lion).keeper.nametag; // typechecks! +createInstance(Bee).keeper.hasMask; // typechecks! +``` + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Integrating with Build Tools.md b/packages/handbook-v1/en/Integrating with Build Tools.md new file mode 100644 index 000000000000..65528986bf48 --- /dev/null +++ b/packages/handbook-v1/en/Integrating with Build Tools.md @@ -0,0 +1,281 @@ +--- +title: Integrating with Build Tools +layout: docs +permalink: /docs/handbook/integrating-with-build-tools.html +--- +{% raw %}Build tools + +* [Babel](#babel) +* [Browserify](#browserify) +* [Duo](#duo) +* [Grunt](#grunt) +* [Gulp](#gulp) +* [Jspm](#jspm) +* [Webpack](#webpack) +* [MSBuild](#msbuild) +* [NuGet](#nuget) + +# Babel + +### Install + +```sh +npm install @babel/cli @babel/core @babel/preset-typescript --save-dev +``` + +### .babelrc + +```js +{ + "presets": ["@babel/preset-typescript"] +} +``` +### Using Command Line Interface +```sh +./node_modules/.bin/babel --out-file bundle.js src/index.ts +``` + + +### package.json + +```js +{ + "scripts": { + "build": "babel --out-file bundle.js main.ts" + }, +} +``` + +### Execute Babel from the command line +```sh +npm run build +``` + + +# Browserify + +### Install + +```sh +npm install tsify +``` + +### Using Command Line Interface + +```sh +browserify main.ts -p [ tsify --noImplicitAny ] > bundle.js +``` + +### Using API + +```js +var browserify = require("browserify"); +var tsify = require("tsify"); + +browserify() + .add("main.ts") + .plugin("tsify", { noImplicitAny: true }) + .bundle() + .pipe(process.stdout); +``` + +More details: [smrq/tsify](https://github.com/smrq/tsify) + +# Duo + +### Install + +```sh +npm install duo-typescript +``` + +### Using Command Line Interface + +```sh +duo --use duo-typescript entry.ts +``` + +### Using API + +```js +var Duo = require("duo"); +var fs = require("fs") +var path = require("path") +var typescript = require("duo-typescript"); + +var out = path.join(__dirname, "output.js") + +Duo(__dirname) + .entry("entry.ts") + .use(typescript()) + .run(function (err, results) { + if (err) throw err; + // Write compiled result to output file + fs.writeFileSync(out, results.code); + }); +``` + +More details: [frankwallis/duo-typescript](https://github.com/frankwallis/duo-typescript) + +# Grunt + +### Install + +```sh +npm install grunt-ts +``` + +### Basic Gruntfile.js + +````js +module.exports = function(grunt) { + grunt.initConfig({ + ts: { + default : { + src: ["**/*.ts", "!node_modules/**/*.ts"] + } + } + }); + grunt.loadNpmTasks("grunt-ts"); + grunt.registerTask("default", ["ts"]); +}; +```` + +More details: [TypeStrong/grunt-ts](https://github.com/TypeStrong/grunt-ts) + +# Gulp + +### Install + +```sh +npm install gulp-typescript +``` + +### Basic gulpfile.js + +```js +var gulp = require("gulp"); +var ts = require("gulp-typescript"); + +gulp.task("default", function () { + var tsResult = gulp.src("src/*.ts") + .pipe(ts({ + noImplicitAny: true, + out: "output.js" + })); + return tsResult.js.pipe(gulp.dest("built/local")); +}); +``` + +More details: [ivogabe/gulp-typescript](https://github.com/ivogabe/gulp-typescript) + +# Jspm + +### Install + +```sh +npm install -g jspm@beta +``` + +_Note: Currently TypeScript support in jspm is in 0.16beta_ + +More details: [TypeScriptSamples/jspm](https://github.com/Microsoft/TypeScriptSamples/tree/master/jspm) + +# Webpack + +### Install + +```sh +npm install ts-loader --save-dev +``` + +### Basic webpack.config.js when using Webpack 2 + +```js +module.exports = { + entry: "./src/index.tsx", + output: { + path: '/', + filename: "bundle.js" + }, + resolve: { + extensions: [".tsx", ".ts", ".js", ".json"] + }, + module: { + rules: [ + // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader' + { test: /\.tsx?$/, use: ["ts-loader"], exclude: /node_modules/ } + ] + } +} +``` + +### Basic webpack.config.js when using Webpack 1 + +```js +module.exports = { + entry: "./src/index.tsx", + output: { + filename: "bundle.js" + }, + resolve: { + // Add '.ts' and '.tsx' as a resolvable extension. + extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"] + }, + module: { + rules: [ + // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader' + { test: /\.tsx?$/, loader: "ts-loader" } + ] + } +} +``` + +See [more details on ts-loader here](https://www.npmjs.com/package/ts-loader). + +Alternatives: + +* [awesome-typescript-loader](https://www.npmjs.com/package/awesome-typescript-loader) + +# MSBuild + +Update project file to include locally installed `Microsoft.TypeScript.Default.props` (at the top) and `Microsoft.TypeScript.targets` (at the bottom) files: + +```xml + + + + + + + + false + true + + + true + false + + + + + +``` + +More details about defining MSBuild compiler options: [Setting Compiler Options in MSBuild projects](./Compiler%20Options%20in%20MSBuild.md) + +# NuGet + +* Right-Click -> Manage NuGet Packages +* Search for `Microsoft.TypeScript.MSBuild` +* Hit `Install` +* When install is complete, rebuild! + +More details can be found at [Package Manager Dialog](http://docs.nuget.org/Consume/Package-Manager-Dialog) and [using nightly builds with NuGet](https://github.com/Microsoft/TypeScript/wiki/Nightly-drops#using-nuget-with-msbuild) + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Interfaces.md b/packages/handbook-v1/en/Interfaces.md new file mode 100644 index 000000000000..0d0adeb866c1 --- /dev/null +++ b/packages/handbook-v1/en/Interfaces.md @@ -0,0 +1,575 @@ +--- +title: Interfaces +layout: docs +permalink: /docs/handbook/interfaces.html +--- +{% raw %}# Introduction + +One of TypeScript's core principles is that type checking focuses on the *shape* that values have. +This is sometimes called "duck typing" or "structural subtyping". +In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project. + +# Our First Interface + +The easiest way to see how interfaces work is to start with a simple example: + +```ts +function printLabel(labeledObj: { label: string }) { + console.log(labeledObj.label); +} + +let myObj = {size: 10, label: "Size 10 Object"}; +printLabel(myObj); +``` + +The type checker checks the call to `printLabel`. +The `printLabel` function has a single parameter that requires that the object passed in has a property called `label` of type `string`. +Notice that our object actually has more properties than this, but the compiler only checks that *at least* the ones required are present and match the types required. +There are some cases where TypeScript isn't as lenient, which we'll cover in a bit. + +We can write the same example again, this time using an interface to describe the requirement of having the `label` property that is a string: + +```ts +interface LabeledValue { + label: string; +} + +function printLabel(labeledObj: LabeledValue) { + console.log(labeledObj.label); +} + +let myObj = {size: 10, label: "Size 10 Object"}; +printLabel(myObj); +``` + +The interface `LabeledValue` is a name we can now use to describe the requirement in the previous example. +It still represents having a single property called `label` that is of type `string`. +Notice we didn't have to explicitly say that the object we pass to `printLabel` implements this interface like we might have to in other languages. +Here, it's only the shape that matters. If the object we pass to the function meets the requirements listed, then it's allowed. + +It's worth pointing out that the type checker does not require that these properties come in any sort of order, only that the properties the interface requires are present and have the required type. + +# Optional Properties + +Not all properties of an interface may be required. +Some exist under certain conditions or may not be there at all. +These optional properties are popular when creating patterns like "option bags" where you pass an object to a function that only has a couple of properties filled in. + +Here's an example of this pattern: + +```ts +interface SquareConfig { + color?: string; + width?: number; +} + +function createSquare(config: SquareConfig): {color: string; area: number} { + let newSquare = {color: "white", area: 100}; + if (config.color) { + newSquare.color = config.color; + } + if (config.width) { + newSquare.area = config.width * config.width; + } + return newSquare; +} + +let mySquare = createSquare({color: "black"}); +``` + +Interfaces with optional properties are written similar to other interfaces, with each optional property denoted by a `?` at the end of the property name in the declaration. + +The advantage of optional properties is that you can describe these possibly available properties while still also preventing use of properties that are not part of the interface. +For example, had we mistyped the name of the `color` property in `createSquare`, we would get an error message letting us know: + +```ts +interface SquareConfig { + color?: string; + width?: number; +} + +function createSquare(config: SquareConfig): { color: string; area: number } { + let newSquare = {color: "white", area: 100}; + if (config.clor) { + // Error: Property 'clor' does not exist on type 'SquareConfig' + newSquare.color = config.clor; + } + if (config.width) { + newSquare.area = config.width * config.width; + } + return newSquare; +} + +let mySquare = createSquare({color: "black"}); +``` + +# Readonly properties + +Some properties should only be modifiable when an object is first created. +You can specify this by putting `readonly` before the name of the property: + +```ts +interface Point { + readonly x: number; + readonly y: number; +} +``` + +You can construct a `Point` by assigning an object literal. +After the assignment, `x` and `y` can't be changed. + +```ts +let p1: Point = { x: 10, y: 20 }; +p1.x = 5; // error! +``` + +TypeScript comes with a `ReadonlyArray` type that is the same as `Array` with all mutating methods removed, so you can make sure you don't change your arrays after creation: + +```ts +let a: number[] = [1, 2, 3, 4]; +let ro: ReadonlyArray = a; +ro[0] = 12; // error! +ro.push(5); // error! +ro.length = 100; // error! +a = ro; // error! +``` + +On the last line of the snippet you can see that even assigning the entire `ReadonlyArray` back to a normal array is illegal. +You can still override it with a type assertion, though: + +```ts +a = ro as number[]; +``` + +## `readonly` vs `const` + +The easiest way to remember whether to use `readonly` or `const` is to ask whether you're using it on a variable or a property. +Variables use `const` whereas properties use `readonly`. + +# Excess Property Checks + +In our first example using interfaces, TypeScript lets us pass `{ size: number; label: string; }` to something that only expected a `{ label: string; }`. +We also just learned about optional properties, and how they're useful when describing so-called "option bags". + +However, combining the two naively would allow an error to sneak in. For example, taking our last example using `createSquare`: + +```ts +interface SquareConfig { + color?: string; + width?: number; +} + +function createSquare(config: SquareConfig): { color: string; area: number } { + // ... +} + +let mySquare = createSquare({ colour: "red", width: 100 }); +``` + +Notice the given argument to `createSquare` is spelled *`colour`* instead of `color`. +In plain JavaScript, this sort of thing fails silently. + +You could argue that this program is correctly typed, since the `width` properties are compatible, there's no `color` property present, and the extra `colour` property is insignificant. + +However, TypeScript takes the stance that there's probably a bug in this code. +Object literals get special treatment and undergo *excess property checking* when assigning them to other variables, or passing them as arguments. +If an object literal has any properties that the "target type" doesn't have, you'll get an error: + +```ts +// error: Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'? +let mySquare = createSquare({ colour: "red", width: 100 }); +``` + +Getting around these checks is actually really simple. +The easiest method is to just use a type assertion: + +```ts +let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig); +``` + +However, a better approach might be to add a string index signature if you're sure that the object can have some extra properties that are used in some special way. +If `SquareConfig` can have `color` and `width` properties with the above types, but could *also* have any number of other properties, then we could define it like so: + +```ts +interface SquareConfig { + color?: string; + width?: number; + [propName: string]: any; +} +``` + +We'll discuss index signatures in a bit, but here we're saying a `SquareConfig` can have any number of properties, and as long as they aren't `color` or `width`, their types don't matter. + +One final way to get around these checks, which might be a bit surprising, is to assign the object to another variable: +Since `squareOptions` won't undergo excess property checks, the compiler won't give you an error. + +```ts +let squareOptions = { colour: "red", width: 100 }; +let mySquare = createSquare(squareOptions); +``` + +The above workaround will work as long as you have a common property between `squareOptions` and `SquareConfig`. +In this example, it was the property `width`. It will however, fail if the variable does not have any common object property. For example: + +```ts +let squareOptions = { colour: "red" }; +let mySquare = createSquare(squareOptions); +``` + +Keep in mind that for simple code like above, you probably shouldn't be trying to "get around" these checks. +For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs. +That means if you're running into excess property checking problems for something like option bags, you might need to revise some of your type declarations. +In this instance, if it's okay to pass an object with both a `color` or `colour` property to `createSquare`, you should fix up the definition of `SquareConfig` to reflect that. + +# Function Types + +Interfaces are capable of describing the wide range of shapes that JavaScript objects can take. +In addition to describing an object with properties, interfaces are also capable of describing function types. + +To describe a function type with an interface, we give the interface a call signature. +This is like a function declaration with only the parameter list and return type given. Each parameter in the parameter list requires both name and type. + +```ts +interface SearchFunc { + (source: string, subString: string): boolean; +} +``` + +Once defined, we can use this function type interface like we would other interfaces. +Here, we show how you can create a variable of a function type and assign it a function value of the same type. + +```ts +let mySearch: SearchFunc; +mySearch = function(source: string, subString: string) { + let result = source.search(subString); + return result > -1; +} +``` + +For function types to correctly type check, the names of the parameters do not need to match. +We could have, for example, written the above example like this: + +```ts +let mySearch: SearchFunc; +mySearch = function(src: string, sub: string): boolean { + let result = src.search(sub); + return result > -1; +} +``` + +Function parameters are checked one at a time, with the type in each corresponding parameter position checked against each other. +If you do not want to specify types at all, TypeScript's contextual typing can infer the argument types since the function value is assigned directly to a variable of type `SearchFunc`. +Here, also, the return type of our function expression is implied by the values it returns (here `false` and `true`). +Had the function expression returned numbers or strings, the type checker would have warned us that return type doesn't match the return type described in the `SearchFunc` interface. + +```ts +let mySearch: SearchFunc; +mySearch = function(src, sub) { + let result = src.search(sub); + return result > -1; +} +``` + +# Indexable Types + +Similarly to how we can use interfaces to describe function types, we can also describe types that we can "index into" like `a[10]`, or `ageMap["daniel"]`. +Indexable types have an *index signature* that describes the types we can use to index into the object, along with the corresponding return types when indexing. +Let's take an example: + +```ts +interface StringArray { + [index: number]: string; +} + +let myArray: StringArray; +myArray = ["Bob", "Fred"]; + +let myStr: string = myArray[0]; +``` + +Above, we have a `StringArray` interface that has an index signature. +This index signature states that when a `StringArray` is indexed with a `number`, it will return a `string`. + +There are two types of supported index signatures: string and number. +It is possible to support both types of indexers, but the type returned from a numeric indexer must be a subtype of the type returned from the string indexer. +This is because when indexing with a `number`, JavaScript will actually convert that to a `string` before indexing into an object. +That means that indexing with `100` (a `number`) is the same thing as indexing with `"100"` (a `string`), so the two need to be consistent. + +```ts +class Animal { + name: string; +} +class Dog extends Animal { + breed: string; +} + +// Error: indexing with a numeric string might get you a completely separate type of Animal! +interface NotOkay { + [x: number]: Animal; + [x: string]: Dog; +} +``` + +While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that all properties match their return type. +This is because a string index declares that `obj.property` is also available as `obj["property"]`. +In the following example, `name`'s type does not match the string index's type, and the type checker gives an error: + +```ts +interface NumberDictionary { + [index: string]: number; + length: number; // ok, length is a number + name: string; // error, the type of 'name' is not a subtype of the indexer +} +``` + +However, properties of different types are acceptable if the index signature is a union of the property types: + +```ts +interface NumberOrStringDictionary { + [index: string]: number | string; + length: number; // ok, length is a number + name: string; // ok, name is a string +} +``` + +Finally, you can make index signatures `readonly` in order to prevent assignment to their indices: + +```ts +interface ReadonlyStringArray { + readonly [index: number]: string; +} +let myArray: ReadonlyStringArray = ["Alice", "Bob"]; +myArray[2] = "Mallory"; // error! +``` + +You can't set `myArray[2]` because the index signature is readonly. + +# Class Types + +## Implementing an interface + +One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a class meets a particular contract, is also possible in TypeScript. + +```ts +interface ClockInterface { + currentTime: Date; +} + +class Clock implements ClockInterface { + currentTime: Date = new Date(); + constructor(h: number, m: number) { } +} +``` + +You can also describe methods in an interface that are implemented in the class, as we do with `setTime` in the below example: + +```ts +interface ClockInterface { + currentTime: Date; + setTime(d: Date): void; +} + +class Clock implements ClockInterface { + currentTime: Date = new Date(); + setTime(d: Date) { + this.currentTime = d; + } + constructor(h: number, m: number) { } +} +``` + +Interfaces describe the public side of the class, rather than both the public and private side. +This prohibits you from using them to check that a class also has particular types for the private side of the class instance. + +## Difference between the static and instance sides of classes + +When working with classes and interfaces, it helps to keep in mind that a class has *two* types: the type of the static side and the type of the instance side. +You may notice that if you create an interface with a construct signature and try to create a class that implements this interface you get an error: + +```ts +interface ClockConstructor { + new (hour: number, minute: number); +} + +class Clock implements ClockConstructor { + currentTime: Date; + constructor(h: number, m: number) { } +} +``` + +This is because when a class implements an interface, only the instance side of the class is checked. +Since the constructor sits in the static side, it is not included in this check. + +Instead, you would need to work with the static side of the class directly. +In this example, we define two interfaces, `ClockConstructor` for the constructor and `ClockInterface` for the instance methods. +Then, for convenience, we define a constructor function `createClock` that creates instances of the type that is passed to it: + +```ts +interface ClockConstructor { + new (hour: number, minute: number): ClockInterface; +} +interface ClockInterface { + tick(): void; +} + +function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface { + return new ctor(hour, minute); +} + +class DigitalClock implements ClockInterface { + constructor(h: number, m: number) { } + tick() { + console.log("beep beep"); + } +} +class AnalogClock implements ClockInterface { + constructor(h: number, m: number) { } + tick() { + console.log("tick tock"); + } +} + +let digital = createClock(DigitalClock, 12, 17); +let analog = createClock(AnalogClock, 7, 32); +``` + +Because `createClock`'s first parameter is of type `ClockConstructor`, in `createClock(AnalogClock, 7, 32)`, it checks that `AnalogClock` has the correct constructor signature. + +Another simple way is to use class expressions: + +```ts +interface ClockConstructor { + new (hour: number, minute: number); +} + +interface ClockInterface { + tick(); +} + +const Clock: ClockConstructor = class Clock implements ClockInterface { + constructor(h: number, m: number) {} + tick() { + console.log("beep beep"); + } +} +``` + + +# Extending Interfaces + +Like classes, interfaces can extend each other. +This allows you to copy the members of one interface into another, which gives you more flexibility in how you separate your interfaces into reusable components. + +```ts +interface Shape { + color: string; +} + +interface Square extends Shape { + sideLength: number; +} + +let square = {} as Square; +square.color = "blue"; +square.sideLength = 10; +``` + +An interface can extend multiple interfaces, creating a combination of all of the interfaces. + +```ts +interface Shape { + color: string; +} + +interface PenStroke { + penWidth: number; +} + +interface Square extends Shape, PenStroke { + sideLength: number; +} + +let square = {} as Square; +square.color = "blue"; +square.sideLength = 10; +square.penWidth = 5.0; +``` + +# Hybrid Types + +As we mentioned earlier, interfaces can describe the rich types present in real world JavaScript. +Because of JavaScript's dynamic and flexible nature, you may occasionally encounter an object that works as a combination of some of the types described above. + +One such example is an object that acts as both a function and an object, with additional properties: + +```ts +interface Counter { + (start: number): string; + interval: number; + reset(): void; +} + +function getCounter(): Counter { + let counter = (function (start: number) { }) as Counter; + counter.interval = 123; + counter.reset = function () { }; + return counter; +} + +let c = getCounter(); +c(10); +c.reset(); +c.interval = 5.0; +``` + +When interacting with 3rd-party JavaScript, you may need to use patterns like the above to fully describe the shape of the type. + +# Interfaces Extending Classes + +When an interface type extends a class type it inherits the members of the class but not their implementations. +It is as if the interface had declared all of the members of the class without providing an implementation. +Interfaces inherit even the private and protected members of a base class. +This means that when you create an interface that extends a class with private or protected members, that interface type can only be implemented by that class or a subclass of it. + +This is useful when you have a large inheritance hierarchy, but want to specify that your code works with only subclasses that have certain properties. +The subclasses don't have to be related besides inheriting from the base class. +For example: + +```ts +class Control { + private state: any; +} + +interface SelectableControl extends Control { + select(): void; +} + +class Button extends Control implements SelectableControl { + select() { } +} + +class TextBox extends Control { + select() { } +} + +// Error: Property 'state' is missing in type 'Image'. +class Image implements SelectableControl { + private state: any; + select() { } +} + +class Location { + +} +``` + +In the above example, `SelectableControl` contains all of the members of `Control`, including the private `state` property. +Since `state` is a private member it is only possible for descendants of `Control` to implement `SelectableControl`. +This is because only descendants of `Control` will have a `state` private member that originates in the same declaration, which is a requirement for private members to be compatible. + +Within the `Control` class it is possible to access the `state` private member through an instance of `SelectableControl`. +Effectively, a `SelectableControl` acts like a `Control` that is known to have a `select` method. +The `Button` and `TextBox` classes are subtypes of `SelectableControl` (because they both inherit from `Control` and have a `select` method), but the `Image` and `Location` classes are not. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/Iterators and Generators.md b/packages/handbook-v1/en/Iterators and Generators.md new file mode 100644 index 000000000000..4779643d61d4 --- /dev/null +++ b/packages/handbook-v1/en/Iterators and Generators.md @@ -0,0 +1,90 @@ +--- +title: Iterators and Generators +layout: docs +permalink: /docs/handbook/iterators-and-generators.html +--- +{% raw %}# Iterables + +An object is deemed iterable if it has an implementation for the [`Symbol.iterator`](Symbols.md#symboliterator) property. +Some built-in types like `Array`, `Map`, `Set`, `String`, `Int32Array`, `Uint32Array`, etc. have their `Symbol.iterator` property already implemented. +`Symbol.iterator` function on an object is responsible for returning the list of values to iterate on. + +## `for..of` statements + +`for..of` loops over an iterable object, invoking the `Symbol.iterator` property on the object. +Here is a simple `for..of` loop on an array: + +```ts +let someArray = [1, "string", false]; + +for (let entry of someArray) { + console.log(entry); // 1, "string", false +} +``` + +### `for..of` vs. `for..in` statements + +Both `for..of` and `for..in` statements iterate over lists; the values iterated on are different though, `for..in` returns a list of *keys* on the object being iterated, whereas `for..of` returns a list of *values* of the numeric properties of the object being iterated. + +Here is an example that demonstrates this distinction: + +```ts +let list = [4, 5, 6]; + +for (let i in list) { + console.log(i); // "0", "1", "2", +} + +for (let i of list) { + console.log(i); // "4", "5", "6" +} +``` + +Another distinction is that `for..in` operates on any object; it serves as a way to inspect properties on this object. +`for..of` on the other hand, is mainly interested in values of iterable objects. Built-in objects like `Map` and `Set` implement `Symbol.iterator` property allowing access to stored values. + +```ts +let pets = new Set(["Cat", "Dog", "Hamster"]); +pets["species"] = "mammals"; + +for (let pet in pets) { + console.log(pet); // "species" +} + +for (let pet of pets) { + console.log(pet); // "Cat", "Dog", "Hamster" +} +``` + +### Code generation + +#### Targeting ES5 and ES3 + +When targeting an ES5 or ES3-compliant engine, iterators are only allowed on values of `Array` type. +It is an error to use `for..of` loops on non-Array values, even if these non-Array values implement the `Symbol.iterator` property. + +The compiler will generate a simple `for` loop for a `for..of` loop, for instance: + +```ts +let numbers = [1, 2, 3]; +for (let num of numbers) { + console.log(num); +} +``` + +will be generated as: + +```js +var numbers = [1, 2, 3]; +for (var _i = 0; _i < numbers.length; _i++) { + var num = numbers[_i]; + console.log(num); +} +``` + +#### Targeting ECMAScript 2015 and higher + +When targeting an ECMAScipt 2015-compliant engine, the compiler will generate `for..of` loops to target the built-in iterator implementation in the engine. + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/JSDoc Supported Types.md b/packages/handbook-v1/en/JSDoc Supported Types.md new file mode 100644 index 000000000000..c23a2705fc2d --- /dev/null +++ b/packages/handbook-v1/en/JSDoc Supported Types.md @@ -0,0 +1,591 @@ +--- +title: JSDoc Supported Types +layout: docs +permalink: /docs/handbook/jsdoc-supported-types.html +--- +{% raw %}The list below outlines which constructs are currently supported +when using JSDoc annotations to provide type information in JavaScript files. + +Note any tags which are not explicitly 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` + +The meaning is usually the same, or a superset, of the meaning of the tag given at [jsdoc.app](https://jsdoc.app). +The code below describes the differences and gives some example usage of each tag. + +## `@type` + +You can use the "@type" tag and reference a type name (either primitive, defined in a TypeScript declaration, or in a JSDoc "@typedef" tag). +You can use any Typescript type, and most JSDoc types. + +```js +/** + * @type {string} + */ +var s; + +/** @type {Window} */ +var win; + +/** @type {PromiseLike} */ +var promisedString; + +// You can specify an HTML Element with DOM properties +/** @type {HTMLElement} */ +var myElement = document.querySelector(selector); +element.dataset.myData = ''; + +``` + +`@type` can specify a union type — for example, something can be either a string or a boolean. + +```js +/** + * @type {(string | boolean)} + */ +var sb; +``` + +Note that parentheses are optional for union types. + +```js +/** + * @type {string | boolean} + */ +var sb; +``` + +You can specify array types using a variety of syntaxes: + +```js +/** @type {number[]} */ +var ns; +/** @type {Array.} */ +var nds; +/** @type {Array} */ +var nas; +``` + +You can also specify object literal types. +For example, an object with properties 'a' (string) and 'b' (number) uses the following syntax: + +```js +/** @type {{ a: string, b: number }} */ +var var9; +``` + +You can specify map-like and array-like objects using string and number index signatures, using either standard JSDoc syntax or Typescript syntax. + +```js +/** + * A map-like object that maps arbitrary `string` properties to `number`s. + * + * @type {Object.} + */ +var stringToNumber; + +/** @type {Object.} */ +var arrayLike; +``` + +The preceding two types are equivalent to the Typescript types `{ [x: string]: number }` and `{ [x: number]: any }`. The compiler understands both syntaxes. + +You can specify function types using either Typescript or Closure syntax: + +```js +/** @type {function(string, boolean): number} Closure syntax */ +var sbn; +/** @type {(s: string, b: boolean) => number} Typescript syntax */ +var sbn2; +``` + +Or you can just use the unspecified `Function` type: + +```js +/** @type {Function} */ +var fn7; +/** @type {function} */ +var fn6; +``` + +Other types from Closure also work: + +```js +/** + * @type {*} - can be 'any' type + */ +var star; +/** + * @type {?} - unknown type (same as 'any') + */ +var question; +``` + +### Casts + +Typescript borrows cast syntax from Closure. +This lets you cast types to other types by adding a `@type` tag before any parenthesized expression. + +```js +/** + * @type {number | string} + */ +var numberOrString = Math.random() < 0.5 ? "hello" : 100; +var typeAssertedNumber = /** @type {number} */ (numberOrString) +``` + +### Import types + +You can also import declarations from other files using import types. +This syntax is Typescript-specific and differs from the JSDoc standard: + +```js +/** + * @param p { import("./a").Pet } + */ +function walk(p) { + console.log(`Walking ${p.name}...`); +} +``` + +import types can also be used in type alias declarations: + +```js +/** + * @typedef { import("./a").Pet } Pet + */ + +/** + * @type {Pet} + */ +var myPet; +myPet.name; +``` + +import types can be used to get the type of a value from a module if you don't know the type, or if it has a large type that is annoying to type: + +```js +/** + * @type {typeof import("./a").x } + */ +var x = require("./a").x; +``` + +## `@param` and `@returns` + +`@param` uses the same type syntax as `@type`, but adds a parameter name. +The parameter may also be declared optional by surrounding the name with square brackets: + +```js +// 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 +} +``` + +Likewise, for the return type of a function: + +```js +/** + * @return {PromiseLike} + */ +function ps(){} + +/** + * @returns {{ a: string, b: number }} - May use '@returns' as well as '@return' + */ +function ab(){} +``` + +## `@typedef`, `@callback`, and `@param` + +`@typedef` may be used to define complex types. +Similar syntax works with `@param`. + + +```js +/** + * @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 either `object` or `Object` on the first line. + +```js +/** + * @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 a similar syntax for one-off type specifications. +Note that the nested property names must be prefixed with the name of the parameter: + + +```js +/** + * @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 a function type instead of an object type: + +```js +/** + * @callback Predicate + * @param {string} data + * @param {number} [index] + * @returns {boolean} + */ +/** @type {Predicate} */ +const ok = s => !(s.length % 2); +``` + +Of course, any of these types can be declared using Typescript syntax in a single-line `@typedef`: + +```js +/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */ +/** @typedef {(data: string, index?: number) => boolean} Predicate */ +``` + +## `@template` + +You can declare generic functions with the `@template` tag: + +```js +/** + * @template T + * @param {T} p1 - A generic parameter that flows through to the return type + * @return {T} + */ +function id(x){ return x } +``` + +Use comma or multiple tags to declare multiple type parameters: + +```js +/** + * @template T,U,V + * @template W,X + */ +``` + +You can also specify a type constraint before the type parameter name. +Only the first type parameter in a list is constrained: + +```js +/** + * @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) { + // ???? +} +``` + +Declaring generic classes or types is unsupported. + +## Classes + +Classes can be declared as ES6 classes. + +```js +class C { + /** + * @param {number} data + */ + constructor(data) { + // property types can be inferred + this.name = "foo"; + + // or set explicitly + /** @type {string | null} */ + this.title = null; + + // or simply annotated, if they're set elsewhere + /** @type {number} */ + this.size; + + this.initialize(data); // Should error, initializer expects a string + } + /** + * @param {string} s + */ + initialize = function (s) { + this.size = s.length + } +} + +var c = new C(0); +var result = C(1); // C should only be called with new +``` + +They can also be declared as constructor functions, as described in the next section: + +## `@constructor` + +The compiler infers constructor functions based on this-property assignments, but you can make checking stricter and suggestions better if you add a `@constructor` tag: + +```js +/** + * @constructor + * @param {number} data + */ +function C(data) { + // property types can be inferred + this.name = "foo"; + + // or set explicitly + /** @type {string | null} */ + this.title = null; + + // or simply annotated, if they're set elsewhere + /** @type {number} */ + this.size; + + 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 +``` + +With `@constructor`, `this` is checked inside the constructor function `C`, so you will get suggestions for the `initialize` method and an error if you pass it a number. You will also get an error if you call `C` instead of constructing it. + +Unfortunately, this means that constructor functions that are also callable cannot use `@constructor`. + +## `@this` + +The compiler can usually figure out the type of `this` when it has some context to work with. When it doesn't, you can explicitly specify the type of `this` with `@this`: + +```js +/** + * @this {HTMLElement} + * @param {*} e + */ +function callbackForLater(e) { + this.clientHeight = parseInt(e) // should be fine! +} +``` + +## `@extends` + +When Javascript classes extend a generic base class, there is nowhere to specify what the type parameter should be. The `@extends` tag provides a place for that type parameter: + +```js +/** + * @template T + * @extends {Set} + */ +class SortableSet extends Set { + // ... +} +``` + +Note that `@extends` only works with classes. Currently, there is no way for a constructor function extend a class. + + +## `@enum` + +The `@enum` tag allows you to create an object literal whose members are all of a specified type. Unlike most object literals in Javascript, it does not allow other members. + +```js +/** @enum {number} */ +const JSDocState = { + BeginningOfLine: 0, + SawAsterisk: 1, + SavingComments: 2, +} +``` + +Note that `@enum` is quite different from, and much simpler than, Typescript's `enum`. However, unlike Typescript's enums, `@enum` can have any type: + +```js +/** @enum {function(number): number} */ +const Math = { + add1: n => n + 1, + id: n => -n, + sub1: n => n - 1, +} +``` + +## More examples + +```js +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) =>
{test.a.charAt(0)}
; + +/** + * 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(); +} +``` + +## Patterns that are known NOT to be supported + +Referring to objects in the value space as types doesn't work unless the object also creates a type, like a constructor function. + +```js +function aNormalFunction() { + +} +/** + * @type {aNormalFunction} + */ +var wrong; +/** + * Use 'typeof' instead: + * @type {typeof aNormalFunction} + */ +var right; +``` + +Postfix equals on a property type in an object literal type doesn't specify an optional property: + +```js +/** + * @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 have meaning if `strictNullChecks` is on: + +```js +/** + * @type {?number} + * With strictNullChecks: true -- number | null + * With strictNullChecks: false -- number + */ +var nullable; +``` + +You can also use a union type: +```js +/** + * @type {number | null} + * With strictNullChecks: true -- number | null + * With strictNullChecks: false -- number + */ +var unionNullable; +``` + +Non-nullable types have no meaning and are treated just as their original type: + +```js +/** + * @type {!number} + * Just has type number + */ +var normal; +``` + +Unlike JSDoc's type system, Typescript only allows you to mark types as containing null or not. +There is no explicit non-nullability -- if strictNullChecks is on, then `number` is not nullable. +If it is off, then `number` is nullable. + +### Unsupported tags + +TypeScript ignores any unsupported JSDoc tags. + +The following tags have open issues to support them: + +- `@const` ([issue #19672](https://github.com/Microsoft/TypeScript/issues/19672)) +- `@inheritdoc` ([issue #23215](https://github.com/Microsoft/TypeScript/issues/23215)) +- `@memberof` ([issue #7237](https://github.com/Microsoft/TypeScript/issues/7237)) +- `@readonly` ([issue #17233](https://github.com/Microsoft/TypeScript/issues/17233)) +- `@yields` ([issue #23857](https://github.com/Microsoft/TypeScript/issues/23857)) +- `{@link …}` ([issue #16498](https://github.com/Microsoft/TypeScript/issues/16498)) + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/JSX.md b/packages/handbook-v1/en/JSX.md new file mode 100644 index 000000000000..61066c54595d --- /dev/null +++ b/packages/handbook-v1/en/JSX.md @@ -0,0 +1,477 @@ +--- +title: JSX +layout: docs +permalink: /docs/handbook/jsx.html +--- +{% raw %}# Table of contents + +[Introduction](#introduction) + +[Basic Usage](#basic-usage) + +[The as operator](#the-as-operator) + +[Type Checking](#type-checking) +* [Intrinsic elements](#intrinsic-elements) +* [Value-based elements](#value-based-elements) +* [Function Component](#function-component) +* [Class Component](#class-component) +* [Attribute type checking](#attribute-type-checking) +* [Children Type Checking](#children-type-checking) + +[The JSX result type](#the-jsx-result-type) + +[Embedding Expressions](#embedding-expressions) + +[React integration](#react-integration) + +[Factory Functions](#factory-functions) + +# Introduction +
↥ back to top + +[JSX](https://facebook.github.io/jsx/) is an embeddable XML-like syntax. +It is meant to be transformed into valid JavaScript, though the semantics of that transformation are implementation-specific. +JSX rose to popularity with the [React](https://reactjs.org/) framework, but has since seen other implementations as well. +TypeScript supports embedding, type checking, and compiling JSX directly to JavaScript. + +# Basic usage +↥ back to top + +In order to use JSX you must do two things. + +1. Name your files with a `.tsx` extension +2. Enable the `jsx` option + +TypeScript ships with three JSX modes: `preserve`, `react`, and `react-native`. +These modes only affect the emit stage - type checking is unaffected. +The `preserve` mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. [Babel](https://babeljs.io/)). +Additionally the output will have a `.jsx` file extension. +The `react` mode will emit `React.createElement`, does not need to go through a JSX transformation before use, and the output will have a `.js` file extension. +The `react-native` mode is the equivalent of `preserve` in that it keeps all JSX, but the output will instead have a `.js` file extension. + +Mode | Input | Output | Output File Extension +---------------|-----------|------------------------------|---------------------- +`preserve` | `
` | `
` | `.jsx` +`react` | `
` | `React.createElement("div")` | `.js` +`react-native` | `
` | `
` | `.js` + +You can specify this mode using either the `--jsx` command line flag or the corresponding option in your [tsconfig.json](./tsconfig.json.md) file. + +> *Note: You can specify the JSX factory function to use when targeting react JSX emit with `--jsxFactory` option (defaults to `React.createElement`) + +# The `as` operator +↥ back to top + +Recall how to write a type assertion: + +```ts +var foo = bar; +``` + +This asserts the variable `bar` to have the type `foo`. +Since TypeScript also uses angle brackets for type assertions, combining it with JSX's syntax would introduce certain parsing difficulties. As a result, TypeScript disallows angle bracket type assertions in `.tsx` files. + +Since the above syntax cannot be used in `.tsx` files, an alternate type assertion operator should be used: `as`. +The example can easily be rewritten with the `as` operator. + +```ts +var foo = bar as foo; +``` + +The `as` operator is available in both `.ts` and `.tsx` files, and is identical in behavior to the angle-bracket type assertion style. + +# Type Checking +↥ back to top + +In order to understand type checking with JSX, you must first understand the difference between intrinsic elements and value-based elements. +Given a JSX expression ``, `expr` may either refer to something intrinsic to the environment (e.g. a `div` or `span` in a DOM environment) or to a custom component that you've created. +This is important for two reasons: + +1. For React, intrinsic elements are emitted as strings (`React.createElement("div")`), whereas a component you've created is not (`React.createElement(MyComponent)`). +2. The types of the attributes being passed in the JSX element should be looked up differently. + Intrinsic element attributes should be known *intrinsically* whereas components will likely want to specify their own set of attributes. + +TypeScript uses the [same convention that React does](http://facebook.github.io/react/docs/jsx-in-depth.html#html-tags-vs.-react-components) for distinguishing between these. +An intrinsic element always begins with a lowercase letter, and a value-based element always begins with an uppercase letter. + +## Intrinsic elements +↥ back to top + +Intrinsic elements are looked up on the special interface `JSX.IntrinsicElements`. +By default, if this interface is not specified, then anything goes and intrinsic elements will not be type checked. +However, if this interface *is* present, then the name of the intrinsic element is looked up as a property on the `JSX.IntrinsicElements` interface. +For example: + +```ts +declare namespace JSX { + interface IntrinsicElements { + foo: any + } +} + +; // ok +; // error +``` + +In the above example, `` will work fine but `` will result in an error since it has not been specified on `JSX.IntrinsicElements`. + +> Note: You can also specify a catch-all string indexer on `JSX.IntrinsicElements` as follows: + +```ts +declare namespace JSX { + interface IntrinsicElements { + [elemName: string]: any; + } +} +``` + +## Value-based elements +↥ back to top + +Value-based elements are simply looked up by identifiers that are in scope. + +```ts +import MyComponent from "./myComponent"; + +; // ok +; // error +``` + +There are two ways to define a value-based element: + +1. Function Component (FC) +2. Class Component + +Because these two types of value-based elements are indistinguishable from each other in a JSX expression, first TS tries to resolve the expression as a Function Component using overload resolution. If the process succeeds, then TS finishes resolving the expression to its declaration. If the value fails to resolve as a Function Component, TS will then try to resolve it as a class component. If that fails, TS will report an error. + +### Function Component +↥ back to top + +As the name suggests, the component is defined as a JavaScript function where its first argument is a `props` object. +TS enforces that its return type must be assignable to `JSX.Element`. + +```ts +interface FooProp { + name: string; + X: number; + Y: number; +} + +declare function AnotherComponent(prop: {name: string}); +function ComponentFoo(prop: FooProp) { + return ; +} + +const Button = (prop: {value: string}, context: { color: string }) => + +
+
+ ); +} +``` + +In general, it'd be a good idea to write a few tests for `onIncrement` and `onDecrement` being triggered when their respective buttons are clicked. +Give it a shot to get the hang of writing tests for your components. + +Now that our component is updated, we're ready to wrap it into a container. +Let's create a file named `src/containers/Hello.tsx` and start off with the following imports. + +```ts +import Hello from '../components/Hello'; +import * as actions from '../actions/'; +import { StoreState } from '../types/index'; +import { connect, Dispatch } from 'react-redux'; +``` + +The real two key pieces here are the original `Hello` component as well as the `connect` function from react-redux. +`connect` will be able to actually take our original `Hello` component and turn it into a container using two functions: + +* `mapStateToProps` which massages the data from the current store to part of the shape that our component needs. +* `mapDispatchToProps` which uses creates callback props to pump actions to our store using a given `dispatch` function. + +If we recall, our application state consists of two properties: `languageName` and `enthusiasmLevel`. +Our `Hello` component, on the other hand, expected a `name` and an `enthusiasmLevel`. +`mapStateToProps` will get the relevant data from the store, and adjust it if necessary, for our component's props. +Let's go ahead and write that. + +```ts +export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) { + return { + enthusiasmLevel, + name: languageName, + } +} +``` + +Note that `mapStateToProps` only creates 2 out of 4 of the properties a `Hello` component expects. +Namely, we still want to pass in the `onIncrement` and `onDecrement` callbacks. +`mapDispatchToProps` is a function that takes a dispatcher function. +This dispatcher function can pass actions into our store to make updates, so we can create a pair of callbacks that will call the dispatcher as necessary. + +```ts +export function mapDispatchToProps(dispatch: Dispatch) { + return { + onIncrement: () => dispatch(actions.incrementEnthusiasm()), + onDecrement: () => dispatch(actions.decrementEnthusiasm()), + } +} +``` + +Finally, we're ready to call `connect`. +`connect` will first take `mapStateToProps` and `mapDispatchToProps`, and then return another function that we can use to wrap our component. +Our resulting container is defined with the following line of code: + +```ts +export default connect(mapStateToProps, mapDispatchToProps)(Hello); +``` + +When we're finished, our file should look like this: + +```ts +// src/containers/Hello.tsx + +import Hello from '../components/Hello'; +import * as actions from '../actions/'; +import { StoreState } from '../types/index'; +import { connect, Dispatch } from 'react-redux'; + +export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) { + return { + enthusiasmLevel, + name: languageName, + } +} + +export function mapDispatchToProps(dispatch: Dispatch) { + return { + onIncrement: () => dispatch(actions.incrementEnthusiasm()), + onDecrement: () => dispatch(actions.decrementEnthusiasm()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Hello); +``` + +## Creating a store + +Let's go back to `src/index.tsx`. +To put this all together, we need to create a store with an initial state, and set it up with all of our reducers. + +```ts +import { createStore } from 'redux'; +import { enthusiasm } from './reducers/index'; +import { StoreState } from './types/index'; + +const store = createStore(enthusiasm, { + enthusiasmLevel: 1, + languageName: 'TypeScript', +}); +``` + +`store` is, as you might've guessed, our central store for our application's global state. + +Next, we're going to swap our use of `./src/components/Hello` with `./src/containers/Hello` and use react-redux's `Provider` to wire up our props with our container. +We'll import each: + +```ts +import Hello from './containers/Hello'; +import { Provider } from 'react-redux'; +``` + +and pass our `store` through to the `Provider`'s attributes: + +```ts +ReactDOM.render( + + + , + document.getElementById('root') as HTMLElement +); +``` + +Notice that `Hello` no longer needs props, since we used our `connect` function to adapt our application's state for our wrapped `Hello` component's props. + +# Ejecting + +If at any point, you feel like there are certain customizations that the create-react-app setup has made difficult, you can always opt-out and get the various configuration options you need. +For example, if you'd like to add a Webpack plugin, it might be necessary to take advantage of the "eject" functionality that create-react-app provides. + +Simply run + +```sh +npm run eject +``` + +and you should be good to go! + +As a heads up, you may want to commit all your work before running an eject. +You cannot undo an eject command, so opting out is permanent unless you can recover from a commit prior to running an eject. + +# Next steps + +create-react-app comes with a lot of great stuff. +Much of it is documented in the default `README.md` that was generated for our project, so give that a quick read. + +If you still want to learn more about Redux, you can [check out the official website](http://redux.js.org/) for documentation. +The same goes [for MobX](https://mobx.js.org/). + +If you want to eject at some point, you may need to know a little bit more about Webpack. +You can check out our [React & Webpack walkthrough here](./React%20&%20Webpack.md). + +At some point you might need routing. +There are several solutions, but [react-router](https://github.com/ReactTraining/react-router) is probably the most popular for Redux projects, and is often used in conjunction with [react-router-redux](https://github.com/reactjs/react-router-redux). + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/tutorials/TypeScript in 5 minutes.md b/packages/handbook-v1/en/tutorials/TypeScript in 5 minutes.md new file mode 100644 index 000000000000..2c695604aabf --- /dev/null +++ b/packages/handbook-v1/en/tutorials/TypeScript in 5 minutes.md @@ -0,0 +1,179 @@ +--- +title: TypeScript in 5 minutes +layout: docs +permalink: /docs/handbook/typescript-in-5-minutes.html +--- +{% raw %}Let's get started by building a simple web application with TypeScript. + +## Installing TypeScript + +There are two main ways to get the TypeScript tools: + +* Via npm (the Node.js package manager) +* By installing TypeScript's Visual Studio plugins + +Visual Studio 2017 and Visual Studio 2015 Update 3 include TypeScript by default. +If you didn't install TypeScript with Visual Studio, you can still [download it](/#download-links). + +For NPM users: + +```shell +> npm install -g typescript +``` + +## Building your first TypeScript file + +In your editor, type the following JavaScript code in `greeter.ts`: + +```ts +function greeter(person) { + return "Hello, " + person; +} + +let user = "Jane User"; + +document.body.textContent = greeter(user); +``` + +## Compiling your code + +We used a `.ts` extension, but this code is just JavaScript. +You could have copy/pasted this straight out of an existing JavaScript app. + +At the command line, run the TypeScript compiler: + +```shell +tsc greeter.ts +``` + +The result will be a file `greeter.js` which contains the same JavaScript that you fed in. +We're up and running using TypeScript in our JavaScript app! + +Now we can start taking advantage of some of the new tools TypeScript offers. +Add a `: string` type annotation to the 'person' function argument as shown here: + +```ts +function greeter(person: string) { + return "Hello, " + person; +} + +let user = "Jane User"; + +document.body.textContent = greeter(user); +``` + +## Type annotations + +Type annotations in TypeScript are lightweight ways to record the intended contract of the function or variable. +In this case, we intend the greeter function to be called with a single string parameter. +We can try changing the call greeter to pass an array instead: + +```ts +function greeter(person: string) { + return "Hello, " + person; +} + +let user = [0, 1, 2]; + +document.body.textContent = greeter(user); +``` + +Re-compiling, you'll now see an error: + +```shell +error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'. +``` + +Similarly, try removing all the arguments to the greeter call. +TypeScript will let you know that you have called this function with an unexpected number of parameters. +In both cases, TypeScript can offer static analysis based on both the structure of your code, and the type annotations you provide. + +Notice that although there were errors, the `greeter.js` file is still created. +You can use TypeScript even if there are errors in your code. But in this case, TypeScript is warning that your code will likely not run as expected. + +## Interfaces + +Let's develop our sample further. Here we use an interface that describes objects that have a firstName and lastName field. +In TypeScript, two types are compatible if their internal structure is compatible. +This allows us to implement an interface just by having the shape the interface requires, without an explicit `implements` clause. + +```ts +interface Person { + firstName: string; + lastName: string; +} + +function greeter(person: Person) { + return "Hello, " + person.firstName + " " + person.lastName; +} + +let user = { firstName: "Jane", lastName: "User" }; + +document.body.textContent = greeter(user); +``` + +## Classes + +Finally, let's extend the example one last time with classes. +TypeScript supports new features in JavaScript, like support for class-based object-oriented programming. + +Here we're going to create a `Student` class with a constructor and a few public fields. +Notice that classes and interfaces play well together, letting the programmer decide on the right level of abstraction. + +Also of note, the use of `public` on arguments to the constructor is a shorthand that allows us to automatically create properties with that name. + +```ts +class Student { + fullName: string; + constructor(public firstName: string, public middleInitial: string, public lastName: string) { + this.fullName = firstName + " " + middleInitial + " " + lastName; + } +} + +interface Person { + firstName: string; + lastName: string; +} + +function greeter(person: Person) { + return "Hello, " + person.firstName + " " + person.lastName; +} + +let user = new Student("Jane", "M.", "User"); + +document.body.textContent = greeter(user); +``` + +Re-run `tsc greeter.ts` and you'll see the generated JavaScript is the same as the earlier code. +Classes in TypeScript are just a shorthand for the same prototype-based OO that is frequently used in JavaScript. + +## Running your TypeScript web app + +Now type the following in `greeter.html`: + +```html + + + TypeScript Greeter + + + + +``` + +Open `greeter.html` in the browser to run your first simple TypeScript web application! + +Optional: Open `greeter.ts` in Visual Studio, or copy the code into the TypeScript playground. +You can hover over identifiers to see their types. +Notice that in some cases these types are inferred automatically for you. +Re-type the last line, and see completion lists and parameter help based on the types of the DOM elements. +Put your cursor on the reference to the greeter function, and hit F12 to go to its definition. +Notice, too, that you can right-click on a symbol and use refactoring to rename it. + +The type information provided works together with the tools to work with JavaScript at application scale. +For more examples of what's possible in TypeScript, see the Samples section of the website. + +![Visual Studio picture](/assets/images/docs/greet_person.png) + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/tutorials/tsconfig.json.md b/packages/handbook-v1/en/tutorials/tsconfig.json.md new file mode 100644 index 000000000000..bdecbbb5c302 --- /dev/null +++ b/packages/handbook-v1/en/tutorials/tsconfig.json.md @@ -0,0 +1,216 @@ +--- +title: tsconfig.json +layout: docs +permalink: /docs/handbook/tsconfig-json.html +--- +{% raw %}## Overview + +The presence of a `tsconfig.json` file in a directory indicates that the directory is the root of a TypeScript project. +The `tsconfig.json` file specifies the root files and the compiler options required to compile the project. +A project is compiled in one of the following ways: + +## Using tsconfig.json + +* By invoking tsc with no input files, in which case the compiler searches for the `tsconfig.json` file starting in the current directory and continuing up the parent directory chain. +* By invoking tsc with no input files and a `--project` (or just `-p`) command line option that specifies the path of a directory containing a `tsconfig.json` file, or a path to a valid `.json` file containing the configurations. + +When input files are specified on the command line, `tsconfig.json` files are ignored. + +## Examples + +Example `tsconfig.json` files: + +* Using the `"files"` property + + ```json + { + "compilerOptions": { + "module": "commonjs", + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true + }, + "files": [ + "core.ts", + "sys.ts", + "types.ts", + "scanner.ts", + "parser.ts", + "utilities.ts", + "binder.ts", + "checker.ts", + "emitter.ts", + "program.ts", + "commandLineParser.ts", + "tsc.ts", + "diagnosticInformationMap.generated.ts" + ] + } + ``` + +* Using the `"include"` and `"exclude"` properties + + ```json + { + "compilerOptions": { + "module": "system", + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + "outFile": "../../built/local/tsc.js", + "sourceMap": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] + } + ``` + +## Details + +The `"compilerOptions"` property can be omitted, in which case the compiler's defaults are used. See our full list of supported [Compiler Options](./Compiler%20Options.md). + +The `"files"` property takes a list of relative or absolute file paths. +The `"include"` and `"exclude"` properties take a list of glob-like file patterns. +The supported glob wildcards are: + +* `*` matches zero or more characters (excluding directory separators) +* `?` matches any one character (excluding directory separators) +* `**/` recursively matches any subdirectory + +If a segment of a glob pattern includes only `*` or `.*`, then only files with supported extensions are included (e.g. `.ts`, `.tsx`, and `.d.ts` by default with `.js` and `.jsx` if `allowJs` is set to true). + +If the `"files"` and `"include"` are both left unspecified, the compiler defaults to including all TypeScript (`.ts`, `.d.ts` and `.tsx`) files in the containing directory and subdirectories except those excluded using the `"exclude"` property. JS files (`.js` and `.jsx`) are also included if `allowJs` is set to true. +If the `"files"` or `"include"` properties are specified, the compiler will instead include the union of the files included by those two properties. +Files in the directory specified using the `"outDir"` compiler option are excluded as long as `"exclude"` property is not specified. + +Files included using `"include"` can be filtered using the `"exclude"` property. +However, files included explicitly using the `"files"` property are always included regardless of `"exclude"`. +The `"exclude"` property defaults to excluding the `node_modules`, `bower_components`, `jspm_packages` and `` directories when not specified. + +Any files that are referenced by files included via the `"files"` or `"include"` properties are also included. +Similarly, if a file `B.ts` is referenced by another file `A.ts`, then `B.ts` cannot be excluded unless the referencing file `A.ts` is also specified in the `"exclude"` list. + +Please note that the compiler does not include files that can be possible outputs; e.g. if the input includes `index.ts`, then `index.d.ts` and `index.js` are excluded. +In general, having files that differ only in extension next to each other is not recommended. + +A `tsconfig.json` file is permitted to be completely empty, which compiles all files included by default (as described above) with the default compiler options. + +Compiler options specified on the command line override those specified in the `tsconfig.json` file. + +## `@types`, `typeRoots` and `types` + +By default all *visible* "`@types`" packages are included in your compilation. +Packages in `node_modules/@types` of any enclosing folder are considered *visible*; +specifically, that means packages within `./node_modules/@types/`, `../node_modules/@types/`, `../../node_modules/@types/`, and so on. + +If `typeRoots` is specified, *only* packages under `typeRoots` will be included. +For example: + +```json +{ + "compilerOptions": { + "typeRoots" : ["./typings"] + } +} +``` + +This config file will include *all* packages under `./typings`, and no packages from `./node_modules/@types`. + +If `types` is specified, only packages listed will be included. +For instance: + +```json +{ + "compilerOptions": { + "types" : ["node", "lodash", "express"] + } +} +``` + +This `tsconfig.json` file will *only* include `./node_modules/@types/node`, `./node_modules/@types/lodash` and `./node_modules/@types/express`. +Other packages under `node_modules/@types/*` will not be included. + +A types package is a folder with a file called `index.d.ts` or a folder with a `package.json` that has a `types` field. + +Specify `"types": []` to disable automatic inclusion of `@types` packages. + +Keep in mind that automatic inclusion is only important if you're using files with global declarations (as opposed to files declared as modules). +If you use an `import "foo"` statement, for instance, TypeScript may still look through `node_modules` & `node_modules/@types` folders to find the `foo` package. + +## Configuration inheritance with `extends` + +A `tsconfig.json` file can inherit configurations from another file using the `extends` property. + +The `extends` is a top-level property in `tsconfig.json` (alongside `compilerOptions`, `files`, `include`, and `exclude`). +`extends`' value is a string containing a path to another configuration file to inherit from. + +The configuration from the base file are loaded first, then overridden by those in the inheriting config file. +If a circularity is encountered, we report an error. + +`files`, `include` and `exclude` from the inheriting config file *overwrite* those from the base config file. + +All relative paths found in the configuration file will be resolved relative to the configuration file they originated in. + +For example: + +`configs/base.json`: + +```json +{ + "compilerOptions": { + "noImplicitAny": true, + "strictNullChecks": true + } +} +``` + +`tsconfig.json`: + +```json +{ + "extends": "./configs/base", + "files": [ + "main.ts", + "supplemental.ts" + ] +} +``` + +`tsconfig.nostrictnull.json`: + +```json +{ + "extends": "./tsconfig", + "compilerOptions": { + "strictNullChecks": false + } +} +``` + +## `compileOnSave` + +Setting a top-level property `compileOnSave` signals to the IDE to generate all files for a given tsconfig.json upon saving. + +```json +{ + "compileOnSave": true, + "compilerOptions": { + "noImplicitAny" : true + } +} +``` + +This feature is currently supported in Visual Studio 2015 with TypeScript 1.8.4 and above, and [atom-typescript](https://github.com/TypeStrong/atom-typescript#compile-on-save) plugin. + +## Schema + +Schema can be found at: [http://json.schemastore.org/tsconfig](http://json.schemastore.org/tsconfig) + + +{% endraw %} \ No newline at end of file diff --git a/packages/handbook-v1/en/typings-for-npm-packages.html b/packages/handbook-v1/en/typings-for-npm-packages.html new file mode 100644 index 000000000000..58642e803771 --- /dev/null +++ b/packages/handbook-v1/en/typings-for-npm-packages.html @@ -0,0 +1,10 @@ + + + +Writing Declaration Files + + + + \ No newline at end of file diff --git a/packages/handbook-v1/en/writing-declaration-files.html b/packages/handbook-v1/en/writing-declaration-files.html new file mode 100644 index 000000000000..f9b7b9f04a7a --- /dev/null +++ b/packages/handbook-v1/en/writing-declaration-files.html @@ -0,0 +1,10 @@ + + + +Writing Declaration Files + + + + \ No newline at end of file diff --git a/packages/handbook-v1/en/writing-definition-files.html b/packages/handbook-v1/en/writing-definition-files.html new file mode 100644 index 000000000000..f9b7b9f04a7a --- /dev/null +++ b/packages/handbook-v1/en/writing-definition-files.html @@ -0,0 +1,10 @@ + + + +Writing Declaration Files + + + + \ No newline at end of file diff --git a/packages/typescript-playground/.npmignore b/packages/playground/.npmignore similarity index 100% rename from packages/typescript-playground/.npmignore rename to packages/playground/.npmignore diff --git a/packages/typescript-playground/README.md b/packages/playground/README.md similarity index 100% rename from packages/typescript-playground/README.md rename to packages/playground/README.md diff --git a/packages/typescript-playground/index.html b/packages/playground/index.html similarity index 100% rename from packages/typescript-playground/index.html rename to packages/playground/index.html diff --git a/packages/typescript-playground/index.tsx b/packages/playground/index.tsx similarity index 100% rename from packages/typescript-playground/index.tsx rename to packages/playground/index.tsx diff --git a/packages/typescript-playground/package.json b/packages/playground/package.json similarity index 100% rename from packages/typescript-playground/package.json rename to packages/playground/package.json diff --git a/packages/typescript-playground/tsconfig.json b/packages/playground/tsconfig.json similarity index 100% rename from packages/typescript-playground/tsconfig.json rename to packages/playground/tsconfig.json diff --git a/packages/typescript-playground/yarn.lock b/packages/playground/yarn.lock similarity index 100% rename from packages/typescript-playground/yarn.lock rename to packages/playground/yarn.lock diff --git a/packages/typescript-sandbox/.gitignore b/packages/sandbox/.gitignore similarity index 100% rename from packages/typescript-sandbox/.gitignore rename to packages/sandbox/.gitignore diff --git a/packages/typescript-sandbox/README.md b/packages/sandbox/README.md similarity index 100% rename from packages/typescript-sandbox/README.md rename to packages/sandbox/README.md diff --git a/packages/typescript-sandbox/index.tsx b/packages/sandbox/index.tsx similarity index 59% rename from packages/typescript-sandbox/index.tsx rename to packages/sandbox/index.tsx index c040bcd4d1ff..a367d8b2d3d3 100644 --- a/packages/typescript-sandbox/index.tsx +++ b/packages/sandbox/index.tsx @@ -1,6 +1,6 @@ -import '../typescript-playground/react-app-polyfill/ie11'; +import '../playground/react-app-polyfill/ie11'; import * as React from 'react'; -import * as ReactDOM from '../typescript-playground/node_modules/@types/react-dom'; +import * as ReactDOM from '../playground/node_modules/@types/react-dom'; import { Thing } from '../playground/dist'; const App = () => { diff --git a/packages/typescript-sandbox/package.json b/packages/sandbox/package.json similarity index 100% rename from packages/typescript-sandbox/package.json rename to packages/sandbox/package.json diff --git a/packages/typescript-sandbox/src/index.ts b/packages/sandbox/src/index.ts similarity index 100% rename from packages/typescript-sandbox/src/index.ts rename to packages/sandbox/src/index.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/languageFeatures.ts b/packages/sandbox/src/monaco-typescript/languageFeatures.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/languageFeatures.ts rename to packages/sandbox/src/monaco-typescript/languageFeatures.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/lib/lib.ts b/packages/sandbox/src/monaco-typescript/lib/lib.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/lib/lib.ts rename to packages/sandbox/src/monaco-typescript/lib/lib.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServices-amd.js b/packages/sandbox/src/monaco-typescript/lib/typescriptServices-amd.js similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServices-amd.js rename to packages/sandbox/src/monaco-typescript/lib/typescriptServices-amd.js diff --git a/packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServices.d.ts b/packages/sandbox/src/monaco-typescript/lib/typescriptServices.d.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServices.d.ts rename to packages/sandbox/src/monaco-typescript/lib/typescriptServices.d.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServices.js b/packages/sandbox/src/monaco-typescript/lib/typescriptServices.js similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServices.js rename to packages/sandbox/src/monaco-typescript/lib/typescriptServices.js diff --git a/packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServicesMetadata.ts b/packages/sandbox/src/monaco-typescript/lib/typescriptServicesMetadata.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/lib/typescriptServicesMetadata.ts rename to packages/sandbox/src/monaco-typescript/lib/typescriptServicesMetadata.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/monaco.contribution.ts b/packages/sandbox/src/monaco-typescript/monaco.contribution.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/monaco.contribution.ts rename to packages/sandbox/src/monaco-typescript/monaco.contribution.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/monaco.d.ts b/packages/sandbox/src/monaco-typescript/monaco.d.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/monaco.d.ts rename to packages/sandbox/src/monaco-typescript/monaco.d.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/ts.worker.ts b/packages/sandbox/src/monaco-typescript/ts.worker.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/ts.worker.ts rename to packages/sandbox/src/monaco-typescript/ts.worker.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/tsMode.ts b/packages/sandbox/src/monaco-typescript/tsMode.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/tsMode.ts rename to packages/sandbox/src/monaco-typescript/tsMode.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/tsWorker.ts b/packages/sandbox/src/monaco-typescript/tsWorker.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/tsWorker.ts rename to packages/sandbox/src/monaco-typescript/tsWorker.ts diff --git a/packages/typescript-sandbox/src/monaco-typescript/tsconfig.esm.json b/packages/sandbox/src/monaco-typescript/tsconfig.esm.json similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/tsconfig.esm.json rename to packages/sandbox/src/monaco-typescript/tsconfig.esm.json diff --git a/packages/typescript-sandbox/src/monaco-typescript/tsconfig.json b/packages/sandbox/src/monaco-typescript/tsconfig.json similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/tsconfig.json rename to packages/sandbox/src/monaco-typescript/tsconfig.json diff --git a/packages/typescript-sandbox/src/monaco-typescript/workerManager.ts b/packages/sandbox/src/monaco-typescript/workerManager.ts similarity index 100% rename from packages/typescript-sandbox/src/monaco-typescript/workerManager.ts rename to packages/sandbox/src/monaco-typescript/workerManager.ts diff --git a/packages/typescript-sandbox/src/monacoTSVersions.ts b/packages/sandbox/src/monacoTSVersions.ts similarity index 100% rename from packages/typescript-sandbox/src/monacoTSVersions.ts rename to packages/sandbox/src/monacoTSVersions.ts diff --git a/packages/typescript-sandbox/src/runtime.ts b/packages/sandbox/src/runtime.ts similarity index 100% rename from packages/typescript-sandbox/src/runtime.ts rename to packages/sandbox/src/runtime.ts diff --git a/packages/typescript-sandbox/src/startPlayground.ts b/packages/sandbox/src/startPlayground.ts similarity index 100% rename from packages/typescript-sandbox/src/startPlayground.ts rename to packages/sandbox/src/startPlayground.ts diff --git a/packages/typescript-sandbox/src/typeAcquisition.ts b/packages/sandbox/src/typeAcquisition.ts similarity index 100% rename from packages/typescript-sandbox/src/typeAcquisition.ts rename to packages/sandbox/src/typeAcquisition.ts diff --git a/packages/typescript-sandbox/test/blah.test.tsx b/packages/sandbox/test/blah.test.tsx similarity index 100% rename from packages/typescript-sandbox/test/blah.test.tsx rename to packages/sandbox/test/blah.test.tsx diff --git a/packages/typescript-sandbox/tsconfig.json b/packages/sandbox/tsconfig.json similarity index 100% rename from packages/typescript-sandbox/tsconfig.json rename to packages/sandbox/tsconfig.json diff --git a/packages/typescriptlang-org/LICENSE b/packages/typescriptlang-org/LICENSE deleted file mode 100644 index 20f91f2b3c52..000000000000 --- a/packages/typescriptlang-org/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 gatsbyjs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/typescriptlang-org/bootup/createPages.js b/packages/typescriptlang-org/bootup/createPages.js new file mode 100644 index 000000000000..b96aaf82a7b9 --- /dev/null +++ b/packages/typescriptlang-org/bootup/createPages.js @@ -0,0 +1,18 @@ +const {setupRedirects} = require("../src/redirects/setupRedirects") +const {createOldHandbookPages} = require("./ingestion/createPagesForOldHandbook") + +/** @type { import("gatsby").GatsbyNode["createPages"] } */ +const createPages = async (args) => { + // Basically this function should be passing the right + // functions down to other places to handle their own + // creation of the pages + + setupRedirects(args.actions.createRedirect) + createOldHandbookPages(args.graphql, args.actions.createPage) + + return null +} + +module.export = { + createPages, +} diff --git a/packages/typescriptlang-org/bootup/ingestion/createPagesForOldHandbook.js b/packages/typescriptlang-org/bootup/ingestion/createPagesForOldHandbook.js new file mode 100644 index 000000000000..ab68fd2aa495 --- /dev/null +++ b/packages/typescriptlang-org/bootup/ingestion/createPagesForOldHandbook.js @@ -0,0 +1,51 @@ +const path = require(`path`) + +/** + * @param { import("gatsby").NodePluginArgs["graphql"]} graphql + * @param { import("gatsby").NodePluginArgs["actions"]["createPage"]} createPage + */ +const createOldHandbookPages = async (graphql, createPage) => { + const handbookPage = path.resolve(`../src/templates/handbook.js`) + const result = await graphql(` + query GetAllHandbookDocs { + allFile(filter: { sourceInstanceName: { eq: "handbook-v1" } }) { + nodes { + name + modifiedTime + + childMarkdownRemark { + frontmatter { + permalink + } + } + } + } + } + `) + + if (result.errors) { + throw result.errors + } + + const v1HandbookDocs = result.data.allFile.edges + v1HandbookDocs.forEach((post, index) => { + const previous = index === posts.length - 1 ? null : posts[index + 1].node + const next = index === 0 ? null : posts[index - 1].node + + console.log("Making page") + createPage({ + path: post.node.childMarkdownRemark.permalink, + component: handbookPage, + context: { + slug: post.node.childMarkdownRemark.permalink, + previous, + next, + }, + }) + }) + +} + +module.exports = { + createOldHandbookPages +} diff --git a/packages/typescriptlang-org/bootup/onCreateNode.js b/packages/typescriptlang-org/bootup/onCreateNode.js new file mode 100644 index 000000000000..f3d178f7b814 --- /dev/null +++ b/packages/typescriptlang-org/bootup/onCreateNode.js @@ -0,0 +1,20 @@ +const { createFilePath } = require(`gatsby-source-filesystem`) + +const onCreateNode = ({ node, actions, getNode }) => { + const { createNodeField } = actions + console.log("Creating node: ") + console.log(node) + + if (node.internal.type === `MarkdownRemark`) { + const value = createFilePath({ node, getNode }) + createNodeField({ + name: `slug`, + node, + value, + }) + } +} + +module.exports = { + onCreateNode +} diff --git a/packages/typescriptlang-org/gatsby-config.js b/packages/typescriptlang-org/gatsby-config.js index 6b75bf4207df..bf024d719d2b 100644 --- a/packages/typescriptlang-org/gatsby-config.js +++ b/packages/typescriptlang-org/gatsby-config.js @@ -1,3 +1,34 @@ module.exports = { - plugins: [`gatsby-plugin-typescript`], + plugins: [ + `gatsby-plugin-typescript`, + { + resolve: `gatsby-source-filesystem`, + options: { + path: `${__dirname}/../handbook-v1/en`, + name: `handbook-v1`, + }, + }, + { + resolve: `gatsby-transformer-remark`, + options: { + plugins: [ + { + resolve: `gatsby-remark-images`, + options: { + maxWidth: 590, + }, + }, + { + resolve: `gatsby-remark-responsive-iframe`, + options: { + wrapperStyle: `margin-bottom: 1.0725rem`, + }, + }, + `gatsby-remark-prismjs`, + `gatsby-remark-copy-linked-files`, + `gatsby-remark-smartypants`, + ], + }, + }, + ] } diff --git a/packages/typescriptlang-org/gatsby-node.js b/packages/typescriptlang-org/gatsby-node.js new file mode 100644 index 000000000000..abea86b15f6a --- /dev/null +++ b/packages/typescriptlang-org/gatsby-node.js @@ -0,0 +1,12 @@ + +const { createPages } = require("./bootup/createPages") +const { onCreateNode } = require("./bootup/onCreateNode") + +/** @type { import("gatsby").GatsbyNode } */ +const config = {}; +exports.config = config + +config.createPages = createPages +config.onCreateNode = onCreateNode + +module.exports = config diff --git a/packages/typescriptlang-org/package.json b/packages/typescriptlang-org/package.json index 32300772a406..1deb2b1d96cd 100644 --- a/packages/typescriptlang-org/package.json +++ b/packages/typescriptlang-org/package.json @@ -1,8 +1,8 @@ { "name": "typescriptlang-org", "private": true, - "description": "A simplified bare-bones starter for Gatsby", - "version": "0.1.0", + "description": "The TypeScript Website", + "version": "0.0.0", "license": "MIT", "scripts": { "build": "gatsby build", @@ -27,9 +27,9 @@ }, "repository": { "type": "git", - "url": "https://github.com/gatsbyjs/gatsby-starter-hello-world" + "url": "https://github.com/microsoft/TypeScript-Website" }, "bugs": { - "url": "https://github.com/gatsbyjs/gatsby/issues" + "url": "https://github.com//microsoft/TypeScript-Website/issues" } } diff --git a/packages/typescriptlang-org/src/components/layout.tsx b/packages/typescriptlang-org/src/components/layout.tsx new file mode 100644 index 000000000000..a16bc05096f6 --- /dev/null +++ b/packages/typescriptlang-org/src/components/layout.tsx @@ -0,0 +1,67 @@ +import React from "react" +import { Link } from "gatsby" + + +declare const __PATH_PREFIX__: string + +export class Layout extends React.Component { + render() { + const { location, title, children } = this.props + const rootPath = `${__PATH_PREFIX__}/` + let header + + if (location.pathname === rootPath) { + header = ( +

+ + {title} + +

+ ) + } else { + header = ( +

+ + {title} + +

+ ) + } + return ( +
+
{header}
+
{children}
+
+ Old stuff + © {new Date().getFullYear()}, Built with + {` `} + Gatsby +
+
+ ) + } +} diff --git a/packages/typescriptlang-org/src/redirects/oldestRedirects.js b/packages/typescriptlang-org/src/redirects/oldestRedirects.js new file mode 100644 index 000000000000..2753aaf04225 --- /dev/null +++ b/packages/typescriptlang-org/src/redirects/oldestRedirects.js @@ -0,0 +1,10 @@ +// These reflect a +const redirects = { + "Playground": "play", + "Tutorial": "unsure", + "Handbook": "docs/handbook" +} + +module.exports = { + redirects +} diff --git a/packages/typescriptlang-org/src/redirects/setupRedirects.js b/packages/typescriptlang-org/src/redirects/setupRedirects.js new file mode 100644 index 000000000000..e9eb2c080780 --- /dev/null +++ b/packages/typescriptlang-org/src/redirects/setupRedirects.js @@ -0,0 +1,22 @@ +const { redirects } = require("./oldestRedirects") + + +/** + * Whoah yeah! + * @param { import("gatsby").NodePluginArgs["actions"]["createRedirect"]} createRedirect + */ +const setupRedirects = (createRedirect) => { + const fromArray = Object.keys(redirects) + fromArray.forEach(from => { + const to = redirects[from] + + createRedirect({ + fromPath: from, + toPath: to + }) + }); +} + +module.exports = { + setupRedirects +} diff --git a/packages/typescriptlang-org/src/templates/handbook.js b/packages/typescriptlang-org/src/templates/handbook.js new file mode 100644 index 000000000000..661fff32dc0a --- /dev/null +++ b/packages/typescriptlang-org/src/templates/handbook.js @@ -0,0 +1,55 @@ +// @ts-check +import React from "react" +import { Link, graphql } from "gatsby" + +// import Bio from "../components/bio" +import {Layout} from "../components/layout" +// import SEO from "../components/seo" +// import { rhythm, scale } from "../utils/typography" + +class BlogPostTemplate extends React.Component { + render() { + const post = this.props.data.markdownRemark + const siteTitle = this.props.data.site.siteMetadata.title + const { previous, next } = this.props.pageContext + return ( + + {/* */} +

{post.frontmatter.title}

+

{post.frontmatter.date}

+
+
+
    +
  • + {previous && ( + ← {previous.frontmatter.title} + )} +
  • +
  • + {next && ( + {next.frontmatter.title} → + )} +
  • +
+ + ) + } +} + +export default BlogPostTemplate + +export const pageQuery = graphql` + query BlogPostBySlug($slug: String!) { + markdownRemark(fields: { slug: { eq: $slug } }) { + id + excerpt(pruneLength: 160) + html + frontmatter { + permalink + } + } + } +` diff --git a/test/fixtures/old-site-map.ts b/test/fixtures/old-site-map.ts new file mode 100644 index 000000000000..961ffde2e490 --- /dev/null +++ b/test/fixtures/old-site-map.ts @@ -0,0 +1,102 @@ +/** All the URLs which need a corresponding page of some sort */ +export const oldSiteMap = ` +index.html +CNAME +Handbook/index.html +Playground/index.html +Tutorial/index.html +Web.config +atom.xml +community/friends.html +community/index.html +community/stats.html +crossdomain.xml +docs/handbook/advanced-types.html +docs/handbook/angular.html +docs/handbook/asp-net-core.html +docs/handbook/basic-types.html +docs/handbook/classes.html +docs/handbook/compiler-options-in-msbuild.html +docs/handbook/compiler-options.html +docs/handbook/configuring-watch.html +docs/handbook/declaration-files/by-example.html +docs/handbook/declaration-files/consumption.html +docs/handbook/declaration-files/deep-dive.html +docs/handbook/declaration-files/do-s-and-don-ts.html +docs/handbook/declaration-files/introduction.html +docs/handbook/declaration-files/library-structures.html +docs/handbook/declaration-files/publishing.html +docs/handbook/declaration-files/templates/global-d-ts.html +docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html +docs/handbook/declaration-files/templates/global-plugin-d-ts.html +docs/handbook/declaration-files/templates/module-class-d-ts.html +docs/handbook/declaration-files/templates/module-d-ts.html +docs/handbook/declaration-files/templates/module-function-d-ts.html +docs/handbook/declaration-files/templates/module-plugin-d-ts.html +docs/handbook/declaration-files/templates/templates.html +docs/handbook/declaration-merging.html +docs/handbook/decorators.html +docs/handbook/enums.html +docs/handbook/functions.html +docs/handbook/generics.html +docs/handbook/gulp.html +docs/handbook/integrating-with-build-tools.html +docs/handbook/interfaces.html +docs/handbook/iterators-and-generators.html +docs/handbook/jsdoc-supported-types.html +docs/handbook/jsx.html +docs/handbook/migrating-from-javascript.html +docs/handbook/mixins.html +docs/handbook/module-resolution.html +docs/handbook/modules.html +docs/handbook/namespaces-and-modules.html +docs/handbook/namespaces.html +docs/handbook/nightly-builds.html +docs/handbook/project-references.html +docs/handbook/react-&-webpack.html +docs/handbook/react.html +docs/handbook/release-notes/typescript-1-1.html +docs/handbook/release-notes/typescript-1-3.html +docs/handbook/release-notes/typescript-1-4.html +docs/handbook/release-notes/typescript-1-5.html +docs/handbook/release-notes/typescript-1-6.html +docs/handbook/release-notes/typescript-1-7.html +docs/handbook/release-notes/typescript-1-8.html +docs/handbook/release-notes/typescript-2-0.html +docs/handbook/release-notes/typescript-2-1.html +docs/handbook/release-notes/typescript-2-2.html +docs/handbook/release-notes/typescript-2-3.html +docs/handbook/release-notes/typescript-2-4.html +docs/handbook/release-notes/typescript-2-5.html +docs/handbook/release-notes/typescript-2-6.html +docs/handbook/release-notes/typescript-2-7.html +docs/handbook/release-notes/typescript-2-8.html +docs/handbook/release-notes/typescript-2-9.html +docs/handbook/release-notes/typescript-3-0.html +docs/handbook/release-notes/typescript-3-1.html +docs/handbook/release-notes/typescript-3-2.html +docs/handbook/release-notes/typescript-3-3.html +docs/handbook/release-notes/typescript-3-4.html +docs/handbook/release-notes/typescript-3-5.html +docs/handbook/symbols.html +docs/handbook/triple-slash-directives.html +docs/handbook/tsconfig-json.html +docs/handbook/type-checking-javascript-files.html +docs/handbook/type-compatibility.html +docs/handbook/type-inference.html +docs/handbook/typescript-in-5-minutes.html +docs/handbook/typings-for-npm-packages.html +docs/handbook/utility-types.html +docs/handbook/variable-declarations.html +docs/handbook/writing-declaration-files.html +docs/handbook/writing-definition-files.html +docs/handbook/home.html +docs/handbook/index.html +docs/handbook/tutorial.html +humans.txt +index.html +play/index.html +robots.txt +samples/index.html +sitemap.xml +`