( << Back) TypeManipulation.ts
1: console.log(`
2: --- 1 --- Creating Types from Types https://www.typescriptlang.org/docs/handbook/2/types-from-types.html
3: `)
4:
5: //Generics - Types which take parameters
6: //Keyof Type Operator - Using the keyof operator to create new types
7: //Typeof Type Operator - Using the typeof operator to create new types
8: //Indexed Access Types - Using Type['a'] syntax to access a subset of a type
9: //Conditional Types - Types which act like if statements in the type system
10: //Mapped Types - Creating types by mapping each property in an existing type
11: //Template Literal Types - Mapped types which change properties via template literal strings
12:
13: console.log(`
14: --- 1 --- note about Keyof
15: `)
16:
17: //https://www.typescriptlang.org/docs/handbook/2/keyof-types.html
18:
19: type Point = { x: number; y: number };
20: type P = keyof Point;
21: //The following type P is the same type as type P = "x" | "y"
22:
23: type Mapish = { [k: string]: boolean };
24: type M = keyof Mapish;
25: //M is string | number — this is because JavaScript object keys are always coerced to a string, so obj[0] is always the same as obj["0"]
26:
27: let k1: M = "1";
28: let k2: M = 1
29: console.log(k1, k2)
30:
31: console.log(`
32: --- 2 --- js typeOf
33: `)
34: //https://www.typescriptlang.org/docs/handbook/2/typeof-types.html
35:
36:
37: //JavaScript already has a typeof operator you can use in an expression context, TypeScript adds a typeof operator you can use in a type context
38: let chars = "111"
39: let ns: typeof chars = '111'
40: console.log(ns)
41:
42: // simple onplace type declaration
43: let P1: () => {
44: x: number;
45: y: number
46: } = () => ({
47: x: 5,
48: y: 10
49: })
50:
51: // the same declaration with type
52: function fn() {
53: return { x: 10, y: 3 };
54: }
55:
56: type Pn = typeof fn;
57: let P2: Pn = () => ({
58: x: 5,
59: y: 10
60: })
61:
62: //ReturnType
63: type Pm = ReturnType<typeof fn>;
64: let P3: Pm = { x: 1, y: 2 }
65:
66: console.log(P1(), P2(), P3)
67:
68:
69: console.log(`
70: --- 3 --- Indexed Access Types
71: `)
72:
73: type Person1 = { age: number; name: string; alive: boolean };
74: type Age1 = Person1["age"];
75:
76: let P4: Age1 = 10
77: console.log(P4)
78:
79: const MyArray1 = [
80: { name: "Alice", age: 15 },
81: { name: "Bob", age: 23 },
82: { name: "Eve", age: 38 },
83: ];
84:
85: type Person2 = typeof MyArray1[number];
86:
87: type Age2 = typeof MyArray1[number]["age"];
88:
89: type Age3 = Person2["age"];
90:
91: let P5: Person2 = { name: "A", age: 1 }
92: let P6: Age2 = 1
93: let P7: Age3 = 2
94:
95: console.log(P5, P6, P7)
96:
97: console.log(`
98: --- 4 --- Conditional Types ( SomeType extends OtherType ? TrueType : FalseType; )
99: `)
100:
101: interface IdLabel {
102: id: number /* some fields */;
103: }
104: interface NameLabel {
105: name: string /* other fields */;
106: }
107:
108: //overloading function
109: function createLabel1(id: number): IdLabel;
110: function createLabel1(name: string): NameLabel;
111: function createLabel1(nameOrId: string | number): IdLabel | NameLabel;
112: function createLabel1(nameOrId: string | number): IdLabel | NameLabel {
113: throw "unimplemented";
114: }
115:
116: // the same as below:
117: //The extends keyword on an interface allows us to effectively copy members from other named types, and add whatever new members we want
118:
119: type NameOrId<T extends number | string> = T extends number
120: ? IdLabel
121: : NameLabel;
122:
123: function createLabel2<T extends number | string>(idOrName: T): NameOrId<T> {
124: throw "unimplemented";
125: }
126:
127: console.log(`
128: --- 5 --- Inferring Within Conditional Types
129: `)
130:
131: //https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types
132: //we used the infer keyword to declaratively introduce a new generic type variable named Item instead of specifying how to retrieve the element type of Type within the true branch.
133: //For example, for simple cases, we can extract the return type out from function types:
134:
135: type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
136: ? Return
137: : never;
138:
139: type Nm = GetReturnType<() => number>;
140:
141: type Str = GetReturnType<(x: string) => string>;
142:
143: type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
144:
145: console.log(`
146: --- 6 --- Distributive Conditional Types
147: `)
148: //https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
149: //When conditional types act on a generic type, they become distributive when given a union type.
150: //If we plug a union type into ToArray, then the conditional type will be applied to each member of that union.
151:
152: type ToArray<Type> = Type extends any ? Type[] : never;
153:
154: type StrArrOrNumArr = ToArray<string | number>;
155:
156: console.log(`
157: --- 7 --- Mapped Types
158: `)
159:
160:
161: //https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
162: //When you don’t want to repeat yourself, sometimes a type needs to be based on another type
163: //Mapped types build on the syntax for index signatures,
164:
165: type OnlyBoolsAndPerson = {
166: [key: string]: boolean | Person1;
167: };
168:
169: const conforms: OnlyBoolsAndPerson = {
170: del: true,
171: rodney: false,
172: };
173:
174: //A mapped type is a generic type which uses a union of PropertyKeys (frequently created via a keyof) to iterate through keys to create a type:
175:
176: type OptionsFlags<T> = {
177: [Property in keyof T]: boolean;
178: };
179:
180: type Features = {
181: darkMode: () => void;
182: newUserProfile: () => void;
183: };
184:
185: type FeatureOptions = OptionsFlags<Features>;
186:
187: console.log(`
188: --- 8 --- Mapping Modifiers, readonly and ?
189: `)
190:
191: // Removes 'readonly' attributes from a type's properties
192: type CreateMutable<Type> = {
193: -readonly [Property in keyof Type]: Type[Property];
194: };
195:
196: type LockedAccount = {
197: readonly id: string;
198: readonly name: string;
199: };
200:
201: type UnlockedAccount = CreateMutable<LockedAccount>;
202:
203: // Removes 'optional' attributes from a type's properties
204: type Concrete<Type> = {
205: [Property in keyof Type]-?: Type[Property];
206: };
207:
208: type MaybeUser = {
209: id: string;
210: name?: string;
211: age?: number;
212: };
213:
214: type User = Concrete<MaybeUser>;
215:
216: console.log(`
217: --- 9 --- Key Remapping via as
218: `)
219:
220: type MappedTypeWithNewProperties<T> = {
221: [Properties in keyof T as P]: T[Properties]
222: }
223:
224: //You can leverage features like template literal types to create new property names from prior ones
225: type Getters<Type> = {
226: [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
227: };
228:
229: interface Person {
230: name: string;
231: age: number;
232: location: string;
233: }
234:
235: type LazyPerson = Getters<Person>;
236:
237:
238: //You can map over arbitrary unions, not just unions of string | number | symbol, but unions of any type:
239:
240: type EventConfig<Events extends { kind: string }> = {
241: [E in Events as E["kind"]]: (event: E) => void;
242: }
243:
244: type SquareEvent = { kind: "square", x: number, y: number };
245: type CircleEvent = { kind: "circle", radius: number };
246:
247: type Config = EventConfig<SquareEvent | CircleEvent>
248:
249:
250: console.log(`
251: --- 9 --- Template Literal Types
252: `)
253:
254: type World = "world";
255:
256: type Greeting = `hello ${World}`;
257:
258: type EmailLocaleIDs = "welcome_email" | "email_heading";
259: type FooterLocaleIDs = "footer_title" | "footer_sendoff";
260:
261: type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
262:
263: type AllLocaleID = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
264: type Lang = "en" | "ja" | "pt";
265:
266: type LocaleMessageIDs = `${Lang}_${AllLocaleID}`;
267:
268:
269: console.log(`
270: --- 10 --- Intrinsic String Manipulation Types : Uppercase<StringType>, Lowercase<StringType>, Capitalize<StringType>, Uncapitalize<StringType>
271: `)
272:
273: type Greeting1 = "Hello, world"
274: type ShoutyGreeting = Uppercase<Greeting1>
275:
276: type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
277: type MainID = ASCIICacheKey<"my_app">
278:
279: console.log(`
280: --- 11 --- Intersection Types
281: `)
282:
283: interface Colorful {
284: color: string;
285: }
286: interface Circle {
287: radius: number;
288: }
289:
290: type ColorfulCircle = Colorful & Circle;
291:
292: function draw(circle: Colorful & Circle) {
293: console.log(`Color was ${circle.color}`);
294: console.log(`Radius was ${circle.radius}`);
295: }
296:
297: // okay
298: //draw({ color: "blue", radius: 42 });
299:
300: console.log(`
301: --- 12 --- type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.
302: `)
303:
304: type OrNull<T> = T | null;
305:
306: type OneOrMany<T> = T | T[];
307:
308: type OneOrManyOrNull1<T> = OrNull<OneOrMany<T>>;
309:
310: type OneOrManyOrNull2<T> = OneOrMany<T> | null
311:
312: type OneOrManyOrNullStrings1 = OneOrManyOrNull1<string>;
313:
314: type OneOrManyOrNullStrings2 = OneOrManyOrNull2<string>;
315:
316: console.log(`
317: --- 13 --- Readonly Array Type and readonly tuple type
318: `)
319:
320: function doStuff(values: ReadonlyArray<string>) {
321: // We can read from 'values'...
322: const copy = values.slice();
323: console.log(`The first value is ${values[0]}`);
324:
325: // ...but we can't mutate 'values'.
326: // values.push("hello!");
327: }
328:
329: const roArray: ReadonlyArray<string> = ["red", "green", "blue"];
330:
331: function doSomething(pair: readonly [string, number]) {
332: // ...
333: }
334:
335: console.log(`
336: --- 14 ---
337: `)
338:
339:
340:
341: console.log(`
342: --- 15 --- Template Literal Types examples
343: `)
344:
345: const person = makeWatchedObject({
346: firstName: "Saoirse",
347: lastName: "Ronan",
348: age: 26,
349: });
350:
351: // makeWatchedObject has added `on` to the anonymous Object
352: person.on("firstNameChanged", (newValue) => {
353: console.log(`firstName was changed to ${newValue}!`);
354: });
355:
356: type PropEventSource<Type> = {
357: on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
358: };
359:
360: /// Create a "watched object" with an `on` method, so that you can watch for changes to properties.
361: declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
362:
363: console.log(`
364: --- 16 --- Inference with Template Literals
365: `)
366: //https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#inference-with-template-literals
367:
368: type PropEventSource1<Type> = {
369: on<Key extends string & keyof Type>
370: (eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void): void;
371: };
372:
373: declare function makeWatchedObject1<Type>(obj: Type): Type & PropEventSource1<Type>;
374:
375: const person1 = makeWatchedObject1({
376: firstName: "Saoirse",
377: lastName: "Ronan",
378: age: 26
379: });
380:
381: person1.on("firstNameChanged", newName => {
382: console.log(`new name is ${newName.toUpperCase()}`);
383: });
384:
385: person1.on("ageChanged", newAge => {
386:
387: if (newAge < 0) {
388: console.warn("warning! negative age");
389: }
390: })
Front context:
Comments (
)
Link to this page:
http://www.vb-net.com/TypescriptLearningStartPoint/TypeManipulation.htm
|