The source code for this blog is available on GitHub.
Note
Top

JavaScript Basics: Essential Knowledge for Every Developer

Cover Image for JavaScript Basics: Essential Knowledge for Every Developer
Chen Han
Chen Han

Basics

What you must know in Javascript

Try Catch

try...catch block allows us to provide more informative error handling, such as logging the error to the console or returning a specific error message. This can be helpful in debugging or troubleshooting issues with the API or the code that uses it.

What is the difference between ?? and || in javascript

In JavaScript, the double question mark (??) is known as the nullish coalescing operator, and the double pipe (||) is known as the logical OR operator.

The nullish coalescing operator is a short-circuit operator that returns the right-hand operand if the left-hand operand is null or undefined, and the left-hand operand otherwise. It is often used as a way to provide a default value for a variable that might be null or undefined. Here's an example:

let userName = null;
let displayName = userName ?? 'Guest';
// displayName is 'Guest'

The logical OR operator, on the other hand, returns the first truthy value that it encounters. If both operands are falsy, it returns the last operand. Here's an example:

let userName = '';
let displayName = userName || 'Guest';
// displayName is 'Guest'

The logical OR operator is often used as a way to provide a default value for a variable that might be falsy (i.e., '', 0, false), whereas the nullish coalescing operator is used to provide a default value for a variable that might be null or undefined.

for in and for of differences

for...in and for...of are both used to loop through an iterable in JavaScript, but they have some key differences.

for...in is used to loop through the properties of an object. It loops through the enumerable properties of an object, including its prototype chain. Here's an example:

const obj = {a: 1, b: 2, c: 3};
for (const prop in obj) {
  console.log(`${prop}: ${obj[prop]}`);
}

This will log a: 1, b: 2, and c: 3 to the console.

for...of, on the other hand, is used to loop through the values of an iterable, such as an array or a string. Here's an example:

const arr = [1, 2, 3];
for (const val of arr) {
  console.log(val);
}

This will log 1, 2, and 3 to the console.

In summary, for...in is used to loop through the properties of an object, while for...of is used to loop through the values of an iterable.

Namespace in Javascript

After I saw there is a namespace in typescript, and the namespace works in typescript is awesome. I tempt to find ways to also use namespace in javascript

declare namespace Address {
  export interface AddressResponse {
    id: number;
    address1: string;
    address2: string;
    address_type: string;
    ...
  }
}

I use console.log and something likeCART_STATE.Provider a lot, and I don't even aware of that. My question then is How can I implement the namespace as console.log. It's not really hard, you can do that by defining a namespace function like the following

Define a namespace function:

const MyLibrary = {
  version: "1.0.0",
  sayHello: function() {
    console.log("Hello from MyLibrary!");
  },
  // ... other library functions ...
};

MyLibrary.sayHello(); // Output: "Hello from MyLibrary!"

In this example, we define a namespace function called MyLibrary that contains a sayHello function and a version property. We can then call the sayHello function using the MyLibrary object.

Define a nested namespace function:

const MyCompany = {
  products: {
    software: {
      MyProduct: {
        version: "1.0.0",
        sayHello: function() {
          console.log("Hello from MyProduct!");
        }
        // ... other product functions ...
      },
      // ... other software products ...
    },
    hardware: {
      // ... other hardware products ...
    }
  },
  // ... other company information ...
};

MyCompany.products.software.MyProduct.sayHello(); // Output: "Hello from MyProduct!"

In this example, we define a nested namespace function called MyCompany that contains a products object, which in turn contains a software object. The software object contains a MyProduct object, which has a sayHello function and a version property. We can then call the sayHello function using the MyCompany.products.software.MyProduct object.

Note that in both examples, we use objects to simulate namespaces, and we can access the functions and properties using dot notation. This helps to organize the code and avoid naming conflicts with other code and libraries.

Organizing files using the namespace technique

When organizing files in JavaScript, you can use the namespace technique to group related functions and variables together. One way to do this is by defining a namespace object, which can contain any number of functions and variables.

To illustrate how this works, let's say we have a library called "MyLibrary" that contains a function called sayHello(). We can define the sayHello function in a separate file called "sayHello.js" and export it using the export keyword, so it can be imported in other files.

sayHello.js:

export function sayHello() {
  console.log("Hello from MyLibrary!");
}

Next, we can create a namespace object for our library in a separate file called "myLibrary.js" and import the sayHello function using the import statement. We can then assign the sayHello function to a property of the namespace object, which we export using the export keyword.

myLibrary.js:

import { sayHello } from "./sayHello.js";

export const MyLibrary = {
  version: "1.0.0",
  sayHello: sayHello,
  // ... other library functions ...
};

Finally, in our main JavaScript file, we can import the namespace object from "myLibrary.js" and call the sayHello function using the property of the namespace object.

main.js:

import { MyLibrary } from "./myLibrary.js";

// Call function from MyLibrary
MyLibrary.sayHello(); // Output: "Hello from MyLibrary!"

In this example, we import the MyLibrary object from the myLibrary.js file and call the sayHello function using the MyLibrary.sayHello property.

By using the namespace technique to organize our files and functions, we can more easily manage large JavaScript projects and avoid naming collisions between different functions and variables.

TypeScript can be handy to use in this case

You can use in handy if you use typescript

You can make use of TypeScript's features with the following code snippet:

import { generic } from "@/utils/generic";

console.log("email validator:",
  generic.validate.isEmail("k445566778899k@gmail.com"),
  generic.validate.isEmail("k445566778899k"),
);

If you have set up your code as follows:

export module generic {
  export module validate {
    export function isEmail(email: string) {
      return emailFormat.test(String(email).toLowerCase());
    }
  }
}

You don't need to use the namespace function technique anymore.

async function with forEach and for loop

import httpService from "@/services/httpService.js";

import { transformBigCommerceProductToOmnisend } from "./bigCommerce/transformProducts";
export const updateProduct = async (productID) => {
  const { data: products } = await httpService.get("/api/bigCommerce/products");
  const transformedData = products.map((product) => {
    const transformation = transformBigCommerceProductToOmnisend(product);
    return transformation;
  });
  transformedData.forEach(async (product) => {
    if (product.productID === productID.toString()) {
      await httpService.put(`/api/omnisend/products/${productID}`, product);
    }
  });
};

There might be a potential issue with the code as the forEach loop is using an async function. While using an async function can be useful for making asynchronous calls, it can also lead to unexpected behavior when used with an array loop like forEach.

In this code, the forEach loop will start all the requests asynchronously and not wait for them to complete before moving on to the next iteration. This means that if there are multiple products to update, the updates may be executed concurrently and it might be difficult to know which update is successful or not.

To avoid this issue, you can use a for..of loop instead of forEach, which can await the result of each iteration before moving on to the next one. This ensures that each request is executed one after the other, and you can handle the result of each request before moving on to the next one.

Here is an example of how to use a for..of loop to update each product one at a time:

import httpService from "@/services/httpService";
import { transformBigCommerceProductToOmnisend } from "@/utils/bigCommerce/transformProducts";
export const updateProduct = async (productID: string) => {
  const { data: products }: { data: Product.GetProductResponse[] } = await httpService.get("/api/bigCommerce/products");
  const transformedData = products.map((product) => {
    const transformation = transformBigCommerceProductToOmnisend(product);
    return transformation;
  });
  
  for (const product of transformedData) {
    if (product.productID === productID) {
      await httpService.put(`/api/omnisend/products/${productID}`, product);
    }
  }
};

By using a for..of loop, each iteration will wait for the previous iteration to complete before starting the next one. This ensures that the requests are executed one after the other, which can make it easier to track the result of each request.

Object

The following are the usage of javascript object.

How to make an object iterable

Here are some ways to achieve that:

for (const item in items) {
  console.log(item)
}

Object.entries(items).map(item => {
  console.log(item)
})

Object.entries(items).forEach(item => {
  console.log(item)
})

for (const item of Object.entries(items)) {
  console.log(item)
}

Here are things you should avoid:

items.map(item => {})         // TypeError: items.map is not a function
items.forEach(item => {})     // TypeError: items.forEach is not a function
for (const item of items) {}  // TypeError: items is not iterable

Reference: Source

Destructing

// Data structure of `this.#fetchAccessToken` is like the fillowing
{
	"data": {
	    "id": "eyJh..."
	},
	"meta": {}
}

// How do i fetch the specific value
async accessToken() {
  const { data: { data: { id } } } = await this.#fetchAccessToken;

  return id;
}

another example

async function upsertCustomerAttribute(data) {
  const {
    data: {
      data: [{ attribute_value }],
    },
  } = await bigCommerceService.put(
    `${process.env.BIGCOMMERCE_STORE_API_URL}/v3/customers/attribute-values`,
    [data]
  );

  return attribute_value;
}

Functions

Pure Function

Some other advanced utility functions that may be worth including in an article could be:

  • memoize: A function that caches the results of a function and returns the cached result when called with the same arguments, improving performance by avoiding unnecessary function calls.
  • curry: A function that allows you to partially apply arguments to a function, creating a new function with the applied arguments fixed.
  • pipe: A function that combines multiple functions into a single function, allowing them to be composed and executed in a specific order.
  • compose: Similar to pipe, but executes the functions in the opposite order.

Arrow functions

The value of the this keyword inside an arrow function is determined by the context in which it is defined, rather than the context in which it is called. Arrow functions do not have their own this value, so the value of this is determined by the surrounding context. If the arrow function is not called as a method on an object, then the value of this will be the global object (e.g., window in a web browser). It is important to note that the value of this inside an arrow function is fixed and cannot be changed.

Arrow functions, also known as "fat arrow functions," are a shorter syntax for defining functions in JavaScript. They were introduced in ECMAScript 6 and have become a popular way to write concise, expressive code.

There are several key differences between arrow functions and traditional function declarations:

  • Arrow functions do not have their own this value. Instead, they inherit the this value of the enclosing context. This can be useful for avoiding the this keyword altogether, or for ensuring that the correct this value is used inside a callback function.
  • Arrow functions do not have a arguments object. If you need to access the arguments passed to a function, you can use the rest parameter syntax (...args).
  • Arrow functions do not have a prototype property. This means that you cannot use them as constructors, and you cannot use the new keyword to create instances of an arrow function.
  • Arrow functions do not have a name property. If you need to access the name of an arrow function, you can use the displayName property, which is a non-standard property that is supported by some JavaScript engines.

Here are some examples of how to use arrow functions:

// Traditional function declaration
function add(x, y) {
  return x + y;
}

// Arrow function with implicit return
const add = (x, y) => x + y;

// Arrow function with explicit return
const add = (x, y) => {
  return x + y;
};

// Arrow function with no arguments
const getMessage = () => 'Hello, world!';

// Arrow function with rest parameter
const addAll = (...numbers) => numbers.reduce((acc, num) => acc + num, 0);

Arrow functions are a useful tool for writing concise, expressive code in JavaScript. They can be used in a variety of contexts, including as callbacks, event handlers, and methods. However, it's important to understand their differences from traditional function declarations, so that you can use them effectively and avoid any potential pitfalls.

Promise

In JavaScript, a Promise is an object that represents the eventual completion or failure of an asynchronous operation. Promises provide a way to handle asynchronous tasks in a sequential, synchronous-like manner, making it easier to write and reason about asynchronous code.

A Promise can be in one of three states:

  • Pending: The initial state of a Promise, representing that the asynchronous operation has not yet completed.
  • Fulfilled: The state of a Promise after the asynchronous operation has completed successfully.
  • Rejected: The state of a Promise after the asynchronous operation has failed.

Here is an example of how to use a Promise:

const promise = new Promise((resolve, reject) => {
  // Perform some asynchronous operation, and call either resolve() or reject()
  // depending on the outcome.
});

promise.then(result => {
  // This function is called if the Promise is fulfilled
  console.log(result);
}).catch(error => {
  // This function is called if the Promise is rejected
  console.error(error);
});

In this example, we create a new Promise object and pass it a function that performs some asynchronous operation. The function takes two arguments: resolve and reject, which are functions that can be called to indicate the outcome of the operation. The Promise object has a then method, which takes a function that will be called if the Promise is fulfilled, and a catch method, which takes a function that will be called if the Promise is rejected.

Promises are often used to wrap asynchronous APIs that use callback functions, such as file I/O or network requests, to make them easier to work with. They are also used to compose asynchronous tasks in a more expressive way, using methods like then, catch, and finally.

Promise.all

Promise.all() resolves only when all given promises resolve, and will reject immediately if any of the promises reject (or non-promises throw an error). It is useful in cases when you have interdependent tasks, where it makes sense to reject immediately upon any of them rejecting.

// this will be counted as if the iterable passed is empty, so it gets fulfilled
var p = Promise.all([1,2,3]);
// this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled
var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
// this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected
var p3 = Promise.all([1,2,3, Promise.reject(555)]);
// this will be counted as if the iterable passed contains only the rejected promise with value "666", so it gets rejected
var p3 = Promise.all([1,2,Promise.reject(666), Promise.reject(555)]);

Promise.allSettled

Promise.allSettled() resolves when all of the given promises have either fulfilled or rejected. Unlike Promise.all(), it does not immediately reject upon any of the promises rejecting, instead it waits for all promises to complete, even if some of them fail. Therefore, it is useful in cases when you have multiple asynchronous tasks that are not interdependent, where you may want to know the result of each promise.

This

this is an important parameter in Javascript that refers to the context in which a function is called. It is essential for writing code, but it is not as difficult to understand as an algorithm as long as you know the current role of this. Questions related to this often appear in exams and interviews, so it is important to grasp it well in order to not miss out on valuable points. In ES5, this refers to the object that calls the function when it is called. For example, in the following code, this refers to the Fruit object because it is called by Fruit. However, if we use an inner function, this will no longer refer to the Fruit object, but can be specified using the bind or apply method. If we bind or apply an array, this will instead refer to that array.

let Fruit = {
  apple: function () {
    console.log("What is this:", this);
  }
}

Fruit.apple();

In the following example, this is undefined in the inner function because it is not called by an object:

let Fruit = {
  apple: function () {
    console.log("What is this:", this);

    function innerFunction () {
      console.log("What is this:", this)
    }

    innerFunction();
  }
}

Fruit.apple();

However, if we bind the inner function to Fruit, this will refer to the Fruit object:

let Fruit = {
  apple: function () {
    console.log("What is this:", this);

    function innerFunction () {
      console.log("What is this:", this)
    }

    const a = innerFunction.bind(this);
    a();
  }
}

Fruit.apple();

We can also use apply instead of bind if we want to execute the function immediately:

// From this
const a = innerFunction.bind(this);
a();

// To this
const a = innerFunction.apply(this);

What will be printed for the following scenario?

const someFunction1 = () => {
  console.log(this);
};

function someFunction2() {
  console.log(this);
}

const obj = {
  someFunction1: someFunction1,
  someFunction2: someFunction2
};

obj.someFunction1();
obj.someFunction2();
A: logs global object (window), logs global object (window)
B: logs obj, logs global object (window)
C: logs obj, logs obj
D: logs global object (window), logs obj

In this code snippet, there are two functions: someFunction1 and someFunction2. someFunction1 is defined using the fat arrow (=>) syntax, which creates a lexical binding for the this keyword. This means that the value of this inside the function will be determined by its surrounding context, rather than being determined by how the function is called.

On the other hand, someFunction2 is defined using the traditional function syntax, which means that the value of this inside the function will be determined by how the function is called.

There is also an object called obj which has two properties: someFunction1 and someFunction2. These properties are assigned the values of the two functions, so they are essentially just references to the functions.

When we call obj.someFunction1() and obj.someFunction2(), the value of this inside the functions will be determined as follows:

  • In someFunction1, the value of this will be the global object (e.g., window in a web browser) because someFunction1 is a fat arrow function and its this value is lexically bound.
  • In someFunction2, the value of this will be obj because someFunction2 is a traditional function and its this value is determined by how it is called (in this case, it is called as a method on the obj object).

Therefore, when we call obj.someFunction1() and obj.someFunction2(), the console will log the global object and obj respectively.

Map

JavaScript's Map object is used to store key-value pairs and is similar to objects but with a few key differences. Maps are ordered, which means the order in which elements were added to the Map is preserved, whereas the properties in objects have no order. Additionally, the keys in Maps can be of any type, including objects, whereas object keys can only be strings or symbols.

Here are some common use cases for JavaScript's Map object:

  1. Storing key-value pairs: One of the main use cases for the Map object is to store key-value pairs, where the keys can be of any type and the values can be of any type as well.
  2. Creating a cache: Map objects can be used to create a simple cache, where values are stored against keys and can be looked up later.
  3. Grouping data: You can use Map objects to group data based on some key. For example, you could group a list of products by category.
  4. Keeping track of elements: The Map object can be used to keep track of elements in a collection, such as checking if an element already exists in the collection.
  5. Storing data for a specific item: If you want to store data for a specific item, you can use the item as a key in the Map object and store the data as the value.

These are just a few examples of how you might use the JavaScript Map object. It's a flexible data structure that can be used in a variety of ways to store and organize data.

JavaScript's Map object is different from a plain object in a few key ways:

  1. Key data type: The keys in a Map can be of any data type, including objects, whereas the keys in an object can only be strings or symbols.
  2. Ordering: The elements in a Map are ordered, which means that the order in which they were added to the Map is preserved. This is not the case with objects, where the properties have no inherent order.
  3. Iteration: Map objects have a built-in forEach method for iterating over their key-value pairs, whereas you have to manually write a loop for an object to iterate over its properties. Map objects also have other useful iteration methods like keys, values, and entries.
  4. Performance: Map objects can be faster than objects for certain operations, such as adding or removing elements from a large collection, because Map objects have a faster algorithm for these operations.
  5. Garbage collection: Map objects are equipped with garbage collection, which means that the memory used by a Map can be automatically freed when it is no longer needed. This can help prevent memory leaks.

These differences make the Map object a more suitable choice in certain use cases, such as when you need to store key-value pairs with keys of any data type or when you need to preserve the order of the elements in a collection. However, in many cases, a plain object is still a suitable choice and may be more straightforward to use in certain situations.

USECASE: Number as key

This markdown describes a solution to transform a data structure of products into a map where the keys are category IDs and the values are arrays of products that belong to each category.

Here is the original data structure of products in JSON format:

[
    {
        "id": 118,
        "name": "Revolt 3",
        "categories": [
            23,
            24,
            29
        ]
    },
    {
        "id": 123,
        "name": "HYTE Desk Pad DP900",
        "categories": [
            23,
            33,
            42
        ]
    },
    {
        "id": 129,
        "name": "Y60",
        "categories": [
            23,
            24,
            30
        ]
    },
]

The goal is to transform this data into the following structure. The desired output is a map with the categories as the keys, and arrays of products associated with those categories as the values:

{
  23: [
    {
      "id": 118,
      "name": "Revolt 3",
      "categories": [
        23,
        24,
        29      
      ]
    },
    {
      "id": 123,
      "name": "HYTE Desk Pad DP900",
      "categories": [
        23,
        33,
        42      
      ]
    },
    // ...and so on  ],
  24: [
    {
      "id": 118,
      "name": "Revolt 3",
      "categories": [
        23,
        24,
        29      
       ]
    },
    {
      "id": 129,
      "name": "Y60",
      "categories": [
        23,
        24,
        30      
      ]
    },
    // ...and so on   
  ],
  // ...and so on 
}

This is achieved using the following code:

function categoryProductMap(products: Product.GetProductResponseWithCategories[]) {
  return products.reduce((map, product) => {
    product.categories.forEach((category) => {
      if (!map.has(category)) {
        map.set(category, []);
      }
    
      map.get(category).push(product);
    });
    return map;
  }, new Map<number, Product.GetProductResponseWithCategories[]>());
}

The function uses the reduce method to loop through the products array and create a map with categories as the keys and arrays of products as the values. If a category does not yet exist in the map, it is added with an empty array as its value. The product is then pushed to the array associated with that category.

Does anyone really use object as key in map

Yes, it's possible to use objects as keys in a Map. In JavaScript, objects can be used as keys because they are reference types and have a unique identity. The Map object uses the identity of the object as the key, not its contents. When you add an object to a Map, the Map will store a reference to the object, not a copy of the object's contents.

However, it's important to keep in mind that object keys can be compared by reference, not by value. This means that two objects with the same contents will not be considered equal as keys unless they are the exact same object.

Here's an example of using an object as a key in a Map:

let user = { name: "John Doe" };

let userMap = new Map();
userMap.set(user, "User Information");

console.log(userMap.get({ name: "John Doe" })); // undefined
console.log(userMap.get(user)); // "User Information"

In this example, user is an object with the property name. We use this object as a key in the Map and set its value to "User Information". When we try to retrieve the value using a newly created object with the same properties, userMap.get({ name: "John Doe" }), it returns undefined because the object used as the key in Map must be the same object, not just an object with the same properties. But when we use the original user object as the key, userMap.get(user), it returns "User Information".

Can you give me some examples of how people using Map to improve the efficiency?

Maps can be used to improve efficiency in various ways. Here are a few examples:

  1. Caching Results: Maps can be used to cache the results of expensive function calls, so that the results can be easily retrieved the next time the function is called with the same arguments.
  2. Fast Lookup: Maps provide fast lookup of values based on a key, making it ideal for indexing data structures like arrays or objects.
  3. Implementing Data Structures: Maps can be used to implement complex data structures like associative arrays, hash tables, and others.
  4. Grouping Data: Maps can be used to group data into categories, allowing for fast and efficient processing of large amounts of data.
  5. Counting Items: Maps can be used to count the number of times an item appears in an array, providing a more efficient solution than traditional looping and counting methods.
  6. Storing Key/Value Pairs: Maps can be used to store key/value pairs, allowing for fast and efficient lookups based on the key.

These are just a few examples of how Maps can be used to improve efficiency. The key is to choose the right data structure for the task at hand, and Maps can provide a fast and efficient solution in many cases.

Here's an example in JavaScript that demonstrates the use of a Map to improve efficiency.

Consider a scenario where you want to count the frequency of elements in an array. One approach to solving this problem is to use an object to store the frequency of each element. Here's an example:

cCopy code
const array = [1, 2, 3, 1, 2, 3, 4, 5, 4, 5, 6];

const frequency = {};

for (const element of array) {
  if (frequency[element]) {
    frequency[element]++;
  } else {
    frequency[element] = 1;
  }
}

console.log(frequency);
// Output: { 1: 2, 2: 2, 3: 2, 4: 2, 5: 2, 6: 1 }

While this approach works, it has some disadvantages. For example, the keys of the object are always strings, even if they represent numbers. This can lead to unexpected results if you try to perform mathematical operations on the values stored in the object.

In this case, a Map can provide a more efficient and safer solution. Here's an example:

const frequency = new Map<string, number>();
const words = ["one", "two", "three", "one", "two", "three", "three"];

for (const word of words) {
  frequency.has(word) ? frequency.set(word, frequency.get(word) + 1) : frequency.set(word, 1);
}

console.log(frequency);

// Output: Map { 'one' => 2, 'two' => 2, 'three' => 3 }

This example uses a Map to keep track of the frequency of each word in the words array. The for loop iterates through each word in the array, and for each word, it checks if the word already exists in the Map as a key. If it does, the value associated with that key is incremented. If it doesn't, a new key-value pair is added to the Map with the word as the key and the value of 1.

Module Import and Export

Module import and export is a way to share and reuse code in TypeScript. A module can contain functions, classes, interfaces, and variables and can be exported using the export keyword. Other modules can then import this exported module using the import keyword. The import statement allows you to use the exported members in another module. This makes it possible to write more maintainable and modular code that can be organized into separate files and reused across different parts of your application. Here's an example of using the import and export method in TypeScript:

File: utils.ts

export const add = (a: number, b: number): number => a + b;
export const subtract = (a: number, b: number): number => a - b;

File: index.ts

import { add, subtract } from './utils';

console.log(add(1, 2));      // 3
console.log(subtract(5, 3)); // 2

In this example, the utils.ts file exports two functions, add and subtract. The index.ts file then imports these functions and uses them in the code. The import statement uses the from keyword to specify the file location, followed by the name of the functions in braces.

Why javascript can use import and export syntax?

Javascript uses the import and export syntax because they are part of the ECMAScript (ES) module system, which is a standard for organizing and sharing JavaScript code. The import and export statements allow you to include and share code between different JavaScript files.

import statement allows you to import values, functions, and objects from other JavaScript files, so that you can use them in your code. This makes it easy to split your code into smaller, reusable parts, making your code more organized, maintainable and scalable.

export statement allows you to make values, functions, and objects available for use in other JavaScript files by other parts of your application. This way, you can write code in one file and use it in another file, making it easier to share code and maintain a modular codebase.

Bulk Import and export

In a programming language, the import and export statements allow you to include exports from one module into another module. Importing and exporting is useful for organizing code into small, reusable, and maintainable pieces.

Multiple imports and exports refer to the ability to import and export multiple values, functions, objects, etc. from and to a module.

For example, in TypeScript you can bulk export multiple values or functions from a module by using the export keyword before each value or function definition. You can then bulk import all of these exports into another module using the import * as syntax.

Here's an example of bulk exporting:

export const value1 = "value1";
export const value2 = "value2";

// moduleB.ts
import * as values from "./moduleA";
console.log(values.value1); // outputs "value1"
console.log(values.value2); // outputs "value2"

In the above example, both value1 and value2 are exported from moduleA.ts. They can then be bulk imported into moduleB.ts using the import * as syntax.

You can also import specific values or functions from a module using the import { value1, value2 } from syntax:

// moduleB.ts
import { value1, value2 } from "./moduleA";
console.log(value1); // outputs "value1"
console.log(value2); // outputs "value2"

This way, you only import the values or functions that you need, making your code more efficient and readable.

The following code is written in TypeScript and demonstrates bulk exports and imports in the code. The first code block exports different modules from different files in the ./ directory using the export * from syntax. The exports are as follows:

export * from "./address";
export * from "./checked";
export * from "./checkoutSteps";
export * from "./contactDetails";
export * from "./cookies";
export * from "./createAccount";
export * from "./csrfToken";
export * from "./error";
export * from "./login";
export * from "./modal";
export * from "./order";
export * from "./provider";
export * from "./shipping";
export * from "./currentUser";
export * from "./test/user";
export * from "./test/cart";

The second code block uses the export keyword to define and export several state atoms using the recoil library. The atoms exported are:

  • emailState
  • passwordState
  • firstNameState
  • lastNameState
  • forgotPassState
import { atom } from "recoil";

export const emailState = atom({
	key: "emailState",
	default: { value: "", error: false },
});

export const passwordState = atom({
	key: "passwordState",
	default: { value: "", error: false },
});

export const firstNameState = atom({
	key: "firstNameState",
	default: { value: "", error: false },
});

export const lastNameState = atom({
	key: "lastNameState",
	default: { value: "", error: false },
});

export const forgotPassState = atom({
	key: "forgotPassState",
	default: false
});

The third code block imports several exports from the @/recoil directory. The imports are:

  • firstNameState
  • lastNameState
  • checkState
  • forgotPassState
  • contactCreateAccountMarkState
  • contactEmailState
  • orderItemState
  • addressBookState
  • currentSnackbarState
  • countryState
  • orderBCState
import {
  firstNameState,
  lastNameState,
  checkState,
  forgotPassState,
  contactCreateAccountMarkState,
  contactEmailState,
  orderItemState,
  addressBookState,
  currentSnackbarState,
  countryState,
  orderBCState,
} from "@/recoil";

These exports likely contain pieces of state or UI-related data that are used in a React component or another part of the application. The bulk imports from the @/recoil directory allow the code to access and use all of these exports in a convenient and organized manner.

Explain import * as yup from "yup"

import * as yup from "yup"; statement is using the ECMAScript module import syntax to import the entire library named yup from the module located at "yup". The as yup part of the statement is using the as keyword to create an alias for the imported library, so that instead of referring to it as _module, it can be referred to as yup.

Dynamic imports

Export and import statements that we covered in previous chapters are called “static”. The syntax is very simple and strict.

import/export aim to provide a backbone for the code structure. That’s a good thing, as code structure can be analyzed, modules can be gathered and bundled into one file by special tools, unused exports can be removed (“tree-shaken”). That’s possible only because the structure of imports/exports is simple and fixed.

The import(module) expression loads the module and returns a promise that resolves into a module object that contains all its exports. It can be called from any place in the code. reference

According to my memory, the markdown quotation method used by the Hanting Studio is to use dynamic import.

/**
 * Reference:
 * 1. https://flaviocopes.com/react-router-data-from-route/
 * 2. https://reactrouter.com/web/api/Hooks
 *
 * @param props
 * @returns {JSX.Element}
 */
export default function (props) {
  const {id} = props.match.params
  const [post, setPost] = useState(null);
  /* Detects Url change */
  const location = useLocation();

  useEffect(() => {
    /*==== Variables related to routing ====*/
    const currentPath = location.pathname;
    const searchParams = new URLSearchParams(location.search);
    /*==== Variables related to routing ====*/

    import(`markdowns/${id}.md`)
      .then((module) => {
        fetch(module.default)
          .then((res) => res.text())
          .then((post) => setPost(post))
          .catch((err) => console.error(err));
      })
      .catch((err) => console.error(err));
  }, [location]);

  return <Base post={post} markdown={id}/>;
}

The code you posted is written in TypeScript and is related to bulk import and export of modules in a TypeScript project.

The first code block (export * from ...) is an example of bulk exporting. The code exports all exports from multiple files into a single module. The export * from syntax exports all exports from a source file. In this case, exports from various files such as address, checked, checkoutSteps, etc. are all being exported into a single module.

The second code block (import ... from) is an example of bulk importing. The code imports multiple exports from a single module and assigns them to local variables. The import {...} from syntax is used to import specific exports from a module and the imported exports can be accessed using the local variable names defined in the curly braces.

For example, the line import { firstNameState, lastNameState, forgotPassState } from "@/recoil"; is importing the firstNameState, lastNameState, and forgotPassState exports from the module @/recoil. These imported exports can be accessed in the code using the variable names firstNameState, lastNameState, and forgotPassState.

Dynamic Imports with React.lazy

React allows us to dynamically import components using its React.lazy API. This function accepts a callback that’s expected to return a dynamically imported component. Here’s an example of what its usage might look like:

// app.jsx
import { lazy, Suspense } from 'react';
const Tab1 = lazy(() => import('./components/Tab1'));
const Tab2 = lazy(() => import('./components/Tab2'));
const Tab3 = lazy(() => import('./components/Tab3'));
const Tab4 = lazy(() => import('./components/Tab4'));

const renderTabPanel = () => {
  const TabPanel = tabs[currentTabId].component;
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <TabPanel />
    </Suspense>
  );
};

Useful Packages

Topic
.net core Mark
JavaScript Han
Typescript Rex
CSS (Tailwind), SCSS Anne
React, NEXT.JS Jamie
WASM Jimmy
GA4+GTM Wayne, Anne
UI-TEST (Playwright) Kevin H
DevOps Mark
JEST+TEST Jamie
SEO Daniel
A-Frame Mark, Kevin
Three.js Wayne, Haley
GSAP
Yup & Zod
react-query
tRPC

Vocabulary

The vocabulary pertaining to this article

rephrased version

here's a rephrased version of the content that is more focused on the title "Organizing files using the namespace technique"

Reference

© 2024 WOOTHINK. All Rights Reserved.
Site MapTerms and ConditionsPrivacy PolicyCookie Policy