visit
Navigating the treacherous waters of TypeScript type definitions for a Map
can sometimes feel like trying to tame a wild beast. Why, you ask? Well, there are a few formidable obstacles in our path:
The Dynamic Dance of JavaScript: In the world of JavaScript, variables can shape-shift between types at the drop of a hat. It's a dynamic realm where nothing stands still. Now, enter TypeScript, with its determination to bring order through static typing. Trying to reconcile this dynamic nature with TypeScript's rigid expectations can be quite the puzzle.
Map's Malleable Keys and Values: The JavaScript Map
is a shape-shifter's paradise. It welcomes keys and values of all stripes, from objects to functions to plain old primitives. This kaleidoscope of possibilities can leave us scratching our heads when we try to pin down precise types in TypeScript.
The Absence of Key Discipline: JavaScript doesn't believe in being strict when it comes to keys in a Map
. It lets any data type waltz in and claim that role. TypeScript, on the other hand, strives to strike a balance between flexibility and type safety, which can feel like a tightrope walk.
So, my friend, as we embark on this journey to define TypeScript types for a Map
, remember that we're navigating through a dynamic landscape filled with flexible creatures and a lack of key order. It's an adventure that requires both creativity and caution.
TypeScript is like a careful librarian;
It wants to know precisely what you're storing. However, since a Map can hold various types of data, it can be a challenge to define the type accurately.
Let's get started.
Have a heart-to-heart conversation with your computer. Explain to it why TypeScript, Node.js, an IDE, and git are essential for your programming journey.
Tell your computer how much you appreciate its hard work and that installing these tools would mean the world to you.
You never know; it might just oblige.
flexible, powerful, but sometimes unpredictable.
TypeScript swoops in to bring order to this frontier.
Why did the terrestrial Map go to therapy?- Because it had too many issues with self-location!
- Why did the Map go to the doctor?
- Because it was feeling a little flat!
When you call set(key, value)
to add a pair, TypeScript computes a hash of the key. This hash is used to determine where in memory to store the value.
set()
, get()
, delete()
) is typically constant time (O(1)) on average, which means that the memory usage doesn't significantly increase with the size of the Map.
Methods for interacting with a Map include:
git clone //github.com/nutsloop/typescript-maps-and-types.git
cd typescript-maps-and-types
npm install
npm run build
You will see the following output because TypeScript has a complaint.
> @nutsloop/[email protected] build
> npx tsc
src/lib/city.ts:58:28 - error TS2769: No overload matches this call.
Overload 1 of 2, '(key: "name" | "country" | "gps-coordinates", value: string): CityInfo', gave the following error.
Argument of type '"actual"' is not assignable to parameter of type '"name" | "country" | "gps-coordinates"'.
Overload 2 of 2, '(key: "population", value: number): CityInfo', gave the following error.
Argument of type '"actual"' is not assignable to parameter of type '"population"'.
58 city.get( 'Beijing' ).set( 'actual', 'Beijing' );
~~~~~~~~
TSFILE: /some-path/typescript-maps-and-types/index.js
TSFILE: /some-path/typescript-maps-and-types/types/index.d.ts
TSFILE: /some-path/typescript-maps-and-types/lib/city.js
TSFILE: /some-path/typescript-maps-and-types/types/lib/city.d.ts
Found 1 error in src/lib/city.ts:58
open the file src/lib/city.ts
and fix the error.
city.get( 'Beijing' ).set( 'actual', 'Beijing' );
change it to
city.get( 'Beijing' ).set( 'name', 'Beijing' );
npm run build
you will see the following output.
> @nutsloop/[email protected] build
> npx tsc
TSFILE: /Volumes/code/nutsloop/typescript-maps-and-types/index.js
TSFILE: /Volumes/code/nutsloop/typescript-maps-and-types/types/index.d.ts
TSFILE: /Volumes/code/nutsloop/typescript-maps-and-types/lib/city.js
TSFILE: /Volumes/code/nutsloop/typescript-maps-and-types/types/lib/city.d.ts
everything is fine now.
Why was TypeScript complaining?
type City = Map<string, CityInfo>;
Think of City
as the cover of your atlas.
It tells us that it's a collection of cities, but it's not just any collection, it's a Map.
And in this Map, the keys are strings (which will be the city names), and the values are CityInfo
.
type CityInfo = Map<'name' | 'gps-coordinates' | 'country', string> &
Map<'population', number>;
Now, CityInfo
is like a special page inside your atlas that holds detailed information about each city.
It uses two sets of keys and values:
'name'
, 'gps-coordinates'
, or 'country'
, and the corresponding values are strings. This is like having specific sections on your city page for the city's name, GPS coordinates, and country.'population'
, and the value is a number. This section is dedicated to the city's population.
So, in the wrong code, we were trying to set the value of the key 'actual'
which is not defined in the type CityInfo
,
and that's why TypeScript was complaining.
is it clear now?
Defining the correct TypeScript type for a Map
can be challenging for several reasons:
Dynamic Nature of JavaScript: JavaScript is dynamically typed, which means variables can change types at runtime. TypeScript aims to provide static typing, making it complex to map JavaScript's dynamic nature to TypeScript's static types.
Map's Flexible Keys and Values: JavaScript Map
allows keys and values of various data types, including objects, functions, and primitives, making it challenging to define strict types in TypeScript.
Lack of Static Key Constraints: JavaScript doesn't enforce strict type constraints on keys in a Map
, allowing any data type as a key. TypeScript must balance flexibility and type safety.
Use of Generics: TypeScript's Map
type uses generics to specify key and value types, which can lead to complex type definitions when dealing with diverse data types.
Type Inference and Union Types: TypeScript uses type inference, resulting in complex union types for Map
due to a variety of possible data types.
Recursive Definitions: Defining Map
with nested maps or complex data structures can lead to intricate type definitions.
Interoperability: TypeScript must work with existing JavaScript code, including libraries without TypeScript definitions, complicating type accuracy for external Map
instances.
Faster Access: Maps are designed for faster data access, especially when you have a lot of items. With objects, JavaScript needs to convert non-string keys to strings before accessing them, which can slow things down. Maps allow you to use any data type as keys, so there's no need for this conversion step.
Order Preservation: Maps preserve the order of items, which means they remember the sequence in which you added them. Objects, on the other hand, don't guarantee any specific order. This can be important when you need to iterate through your data in a particular sequence.
Easier Key Removal: Removing an item from a Map is more straightforward and efficient than deleting a property from an object, especially when you have a lot of data.
With Maps, you can use the
delete()
method, which is faster than thedelete
operator for objects.
You said something before about the heap, What the heck is the heap?
Remember that
You're casting spells and breathing life into digital creatures within the mystical Heap, where fantasy and reality merge in a dance of bytes and dreams.
I think I've said enough.
npm run tsc:watch ## typescript compiler in watch mode
touch src/edit.ts
src/edit.ts
and add the following code.
import { City, CityInfo } from './lib/city.js';
export function editing_city( city: City ): void {
const NewOrleansCityInfo: CityInfo = new Map();
NewOrleansCityInfo.set( 'name', 'New Orleans' );
NewOrleansCityInfo.set( 'gps-coordinates', '29.9511° N, 90.0715° W' );
NewOrleansCityInfo.set( 'country', 'United States' );
NewOrleansCityInfo.set( 'population', 391006 );
city.set( 'New Orleans', NewOrleansCityInfo );
}
touch src/run.ts
src/run.ts
and add the following code.
import { city, iterate } from './lib/city.js';
import { editing_city } from './edit.js';
// iterate over the city object
iterate();
// pass the city Map and it will be modified adding a new city of New Orleans
editing_city( city );
// add a new city of Maracaibo
city.set( 'Maracaibo', new Map() );
city.get( 'Maracaibo' ).set( 'name', 'Maracaibo' );
city.get( 'Maracaibo' ).set( 'gps-coordinates', '10.6585° N, 71.6372° W' );
city.get( 'Maracaibo' ).set( 'country', 'Venezuela' );
city.get( 'Maracaibo' ).set( 'population', 1943901 );
console.log( '\n', 'After editing the city object:', '\n' );
iterate();
node run.js
the output is a bit long, take your time to read it.
touch src/mod_city.ts
src/mod_city.ts
and add the following code.
import type { CityInfo } from './lib/city.js';
import { city } from './lib/city.js';
export function return_new_orleans(): CityInfo {
return city.get( 'New Orleans' );
}
src/run.ts
and add the following code at the end.
// working over the CityInfo Map for New Orleans
const new_orleans = return_new_orleans();
new_orleans.set( 'name', 'New Orleans is the city of music' );
console.log( '\n', 'After editing the New Orleans city object:', '\n' );
iterate();
any modification to the city
Map will be reflected in all the references to it like Magic.
run the code again.
node run.js
as you can see, the changes are reflected in all the references to the city
Map.
Why did the Map and Set break up?
Because they couldn't find a common key to their relationship!
The next tutorial will be about TypeScript Sets & Types.
shhhh, don't tell anyone.
maybe about Rust, who knows.
maybe about the weather, who knows.