JavaScript Basics: Essential Knowledge for Every Developer
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 topipe
, 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 thethis
value of the enclosing context. This can be useful for avoiding thethis
keyword altogether, or for ensuring that the correctthis
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 thenew
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 thedisplayName
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 ofthis
will be the global object (e.g.,window
in a web browser) becausesomeFunction1
is a fat arrow function and itsthis
value is lexically bound. - In
someFunction2
, the value ofthis
will beobj
becausesomeFunction2
is a traditional function and itsthis
value is determined by how it is called (in this case, it is called as a method on theobj
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:
- 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. - 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. - 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. - 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. - 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:
- 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. - Ordering: The elements in a
Map
are ordered, which means that the order in which they were added to theMap
is preserved. This is not the case with objects, where the properties have no inherent order. - Iteration:
Map
objects have a built-inforEach
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 likekeys
,values
, andentries
. - Performance:
Map
objects can be faster than objects for certain operations, such as adding or removing elements from a large collection, becauseMap
objects have a faster algorithm for these operations. - Garbage collection:
Map
objects are equipped with garbage collection, which means that the memory used by aMap
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:
- 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.
- Fast Lookup: Maps provide fast lookup of values based on a key, making it ideal for indexing data structures like arrays or objects.
- Implementing Data Structures: Maps can be used to implement complex data structures like associative arrays, hash tables, and others.
- Grouping Data: Maps can be used to group data into categories, allowing for fast and efficient processing of large amounts of data.
- 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.
- 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"