2 chap02
Jason Zhu edited this page 2021-12-30 13:21:16 +11:00

Chapter 2. JavaScript for React

This chapter introduce necessary JS syntax for working with React.

2.1 Declaring Variables

Before ES2015, var is the only way to declare variable. Now we have more ways in ES6.

2.1.1 The const Keyword

A lot of variables we created in JS should not be overwritten, we'll use const

const pizza = true;
pizza = false; // Error

Error message in browser: Uncaught TypeError: Assignment to constant variable.

2.1.2 The let Keyword

JS (since JS6) has now lexical (relating to the words or vocabulary of a language) variable scope, by create code blocks with {}

  • var is scoped in function block, but not for other blocks (e.g. if/else, for).
  • let scope a variable to any code block

2.1.3 Template Strings

Template String:

  • provide a second way of string concatenation (shown below)
  • Allow us to insert variables into a string
  • aka template strings, template literals, string templates

Traditional string concatenation:

console.log(lastName + ", " + firstName + " " + middlename);

String concatenation using template (insert variable values by surrounding them with ${}):

console.log('${lastName}, ${firstName} ${middleName}');

Hence, template string is useful for long text

2.2 Creating Functions

2.2.1 Function Declarations

Function declaration and function definition in JavaScript is the same thing.

  • How to create function using function declaration/definition: starts with function, followed by name of the function
  • Use function by call it
// Function declaration/definition
function logCompliment() {
    console.log("You're doing great!");
}

// invoke
logCompliment();

Where:

  • logCompliment is the function name.

2.2.2 Function Expressions

Function expression = another option of define function

  • How to create function using function expression: creating function as a variable
const logCompliment = function() {
    console.log("You're doing great!");
};

logCompliment(); // use function expression

Hoisting (悬浮):

  • Function declaration are hoisted: you can invoke a function before the function declaration is written
  • Function expressions are not: you cannot invoke a function before the expression is made.
hey(); // TypeError: hey is not a function

const hey = function() {
    alert("hey!");
};

Passing arguments

Passing arguments by: adding named parameters into function parentheses ()

const logCompliment = function(firstName, message) {
    console.log(`You're doing great, ${firstName}: ${message}`);
};

logCompliment("Molly", "You're so cool");

Function returns

Use return statement to specify values returned by function

const createCompliment = function(firstName, message) {
    return `${firstName}: ${message}`;
};

createCompliment("Molly", "You're so cool");

2.2.3 Default Parameters

New feature included in ES6

function logActivity(name = "Shane McConkey", activity = "skiing") {
    console.log(`${name} loves ${activity}`);
}

2.2.4 Arrow Functions

New feature included in ES6.

Arrow function allow us to

  • create functions without using function keyword.
  • no need to use return keyword
  • If function only takes one argument, parentheses around argument can be removed
  • create multi-line function easily
// function declaration
const lordify = function(firstName) {
    return `${firstName} of Cantebury`;
}

// simplified arrow function
const lordify = firstName => `${firstName} of Canteberry`;

Where:

  • parenthese of arrow function arround parameter (firstName) can be removed

Arrow function with multiple arguments:

const lordify = (firstName, land) => `${firstName} of ${land}`;

Multi-line arrow function using curely brace {}:

const lordify = (firstName, land) => {
    if (!firstName) {
        throw new Error("A firstName is required to lordify");
    }

    if (!land) {
        throw new Error("A lord must have a land");
    }

    return `${firstName} of ${land}`;
}

console.log(lordify("Kelly", "Sonoma"));

Returning objects

If we want to specify that we want to return an object, wrap the returned object with (). Otherwise, we will see error Uncaught SyntaxError: Unexpected token

const person = (firstName, lastName) => {
    ({
        first: firstName, last:lastName
    })
};

Where:

  • Outest {} is arrow function block
  • () is for returned object
  • Inner {} is object constructor

Arrow functions and scope

Regular functions (function declaration/definition, expressions) do not block this (i.e. global object is referred). Solution is using arrow function syntax to protect the scope of this

// Declare tahoe object
const tahoe = {
    mountains: ["Freel", "Rose", "Tallac", "Rubicon", "Silver"],
    print: function(delay = 1000) {
        setTimeout(function() {
            console.log(this.mountains.join(", "));
        }, delay);
    }
};

tahoe.print(); // Uncaught TypeError: Cannot read property 'join' of undefined

Where:

  • console.log(this); will show that this this refer to Window object, as function does not block this
const tahoe = {
    mountains: ["Freel", "Rose", "Tallac", "Rubicon", "Silver"],
    print: function (delay = 1000) {
        setTimeout(() => {
            console.log(this.mountains.join(", "));
        }, delay);
    }
};

tahoe.print(); // Freel, Rose, Tallac, Rubicon, Silver

Where:

  • print is a function in which setTimeout take arrow function as parameter

Notice: arrow functions do not block off the scope of this

const tahoe = {
  mountains: ["Freel", "Rose", "Tallac", "Rubicon", "Silver"],
  print: (delay = 1000) => {
    setTimeout(() => {
      console.log(this.mountains.join(", "));
    }, delay);
  }
};

tahoe.print(); // Uncaught TypeError: Cannot read property 'join' of undefined

Where:

  • Changing print function to an arrow function menas that this is actually window.

2.3 Compiling JavaScript

Compiling = convert JS to more widely compatible code, so new features can be run on old browsers. Babel

2.4 Objects and Arrays

2.4.1 Destructuring Objects

  • Destructring assignment = locally scope (isolate) fields within an object and to declare which values will be used.
    • Destructed variables using let can be changed.
const sandwich = {
    bread: "dutch crunch",
    meat: "tuna",
    cheese: "swiss",
    toppings: ["lettus", "tomato", "mustard"]
};

// Destructing assignment using const
const { bread, meat } = sandwich; 

// Destructed variable using let can be changed
let { bread, meat } = sandwich; 
bread = "garlic";
meat = "turkey";
console.log(sandwich.bread, sandwich.meat); // dutch crunch tuna
  • Incoming function arguments can be destructed, so no need to parse entire object
const lordify = ({ firstname }) => {
    console.log(`${firstname} of Cantebury`);
}

const regularPerson = {
    firstname: "Bill",
    lastname: "Wilson"
};

lordify(regularPerson); // Bill of Cantebury
  • Using colon : and nested curly braces {}, we can destruct nested object
const lordify = ({ spouse: {firstname} }) => {
    console.log(`${firstname} of Cantebury`);
};

const regularPerson = {
    firstname: "Bill",
    lastname: "Wilson",
    spouse: {
        firstname: "Phil",
        lastname: "Wilson"
    }
};

lordify(regularPerson); // Phil of Cantebury

2.4.2 Destructuring Arrays

  • Values can be destructured from arrays using Destructor
    • We can skip unnecessary values with list matching using , (comma).
const [firstAnimal] = ["Horse", "Mouse", "Cat"];
console.log(firstAnimal); // Horse

const [, , thirdAnimal] = ["Horse", "Mouse", "Cat"];
console.log(thirdAnimal); // Cat

2.4.3 Object Literal Enhancement

  • Object literal enhancement = process of restructuring or putting object back together.
    • original variable name become keys of the constructed object.
const name = "Tallac";
const elevantion = 9738;
const print = function() {
    console.log(`Mt. ${this.name} is ${this.elevation} feet tall`); // use `this` to access object keys.
};

const funHike = { name, elevation, print }; // Object literal enhancement

funHike.print(); // Mt. Tallac is 9738 feet tall
  • ES6 feature: object literal enhancement allow us to pull global variables into objects and reduces typing by making function keyword unnecessary.
const name = "Tallac";
const elevantion = 9738;

// Pre ES6
var skier = {
    name: name,
    sound: sound,
    powderYell: function() {
        var yell = this.sound.toUpperCase();
        console.log(`${yell} ${yell} ${yell}!!!`);
    },
    speed: function(mph) {
        this.speed = mph;
        console.log("speed:", mph);
    }
};

// ES6
const skier = {
    name,
    sound,
    powderYell() {
        let yell = this.sound.toUpperCase();
        console.log(`${yell} ${yell} ${yell}!!!`);
    }
    speed(mph) {
        this.speed = mph;
        console.log("speed:", mph);
    }
}

2.4.4 The Spread Operator

Spread Operator ... have multiple usages:

  1. copy contents of arrays for combining a new one: ...array will copy all objects as a new flat array to return.
  2. get remaining items in array
  3. collect function arguments as an array
  4. Use on object (similar as on arrays)

Usage 1: Combine/Copy content of arrays

const peaks = ["Tallac", "Ralston", "Rose"];
const canyons = ["Ward", "Blackwood"];

const tahoe = [...peaks, ...canyons]; // flat/spread and combine

console.log(tahoe.join(", ")); // Tallac, Ralston, Rose, Ward, Blackwood

As copied/spreaded array is a copy version, array operation on copied version means original array is immutable

const peaks = ["Tallac", "Ralston", "Rose"];
const [last] = [...peaks].reverse(); // spread operator to copy array + destructor

console.log(last); // Rose
console.log(peaks.join(", ")); // Tallac, Ralston, Rose

Usage 2: Get remaining items in array

const lakes = ["Donner", "Marlette", "Fallen Leaf", "Cascade"];
const [first, ...others] = lakes;

console.log(others.join(", ")); // Marlette, Fallen Leaf, Cascade

Usage 3: Collect function arguments as an array

Argument collected as an array are called Rest Parameters

function directions(...args) { // collect function arguments as an array
    let [start, ...remaining] = args;
    let [finish, ...stops] = remaining.reverse();
    console.log(`drive through ${args.length} towns`);
    console.log(`start in ${start}`);
    console.log(`the destination is ${finish}`);
    console.log(`stopping ${stops.length} times in between`);
}

Usage 4: Use spread operator on objects to combine properties.

const morning = {
  breakfast: "oatmeal",
  lunch: "peanut butter and jelly"
};

const dinner = "mac and cheese";

const backpackingMeals = {
  ...morning,
  dinner
};

console.log(backpackingMeals);

// {
//   breakfast: "oatmeal",
//   lunch: "peanut butter and jelly",
//   dinner: "mac and cheese"
// }

2.5 Asynchronous JavaScript

Async tasks: tasks have to wait for some work to finish before they can be completed.

  • e.g.
    • Access a database
    • Stream video or audio content.
    • Fetch data from an API.
  • Async JS's main thread can do others while waitinig for API to return data.

2.5.1 Simple Promises with Fetch

Steps of using fetch

  1. fetch(URL) to make a GET request to api, which returns a promise. Promise is an object that represent whether the async operation is pending, has been completed or failed.
  2. Pending promise will be chained with multiple .then() functions, which take in a callback function. .then() function will run callback if previous operation was successful.
  3. Use .catch() at end to invoke a callback if fetch did not resolve successfully
fetch("https://api.randomuser.me/?nat=US&results=1")
  .then(res => res.json())
  .then(json => json.results)
  .then(console.log)
  .catch(console.error);

2.5.2 Async/Await

Another way of handling promise: create an async function.

  • Instead of waiting promise to resolve and handling it with a chain of then function, async functions are told to wait for the promise to resolve before further execution
  • function declared with async are async functions, which can wait for promises to resolve before further execution.
    • await in async function tell the async functioin there is a promise to resolve.
const getFakePerson = async() => {
    try {
        let res = await fetch("https://api.randomuser.me/?nat=US&results=1");
        let { results } = res.json();
        console.log(results);
    } catch (error) {
        console.error(error);
    }
};

getFakePerson();
  • getFakePerson is a async function declared using async keyword, which makes it async so it can wait for promises to resolve before executing the code any further.
  • await tells fetch(...) is a promise to be resolved.

2.5.3 Building Promises

Async request can fail due to multiple reasons. Customerized promise can help us to simplify these complicated output to a simple pass or fail.

Create Promise

const getPeople = count => 
    new Promise((resolves, rejects) => {
        const api = 'https://api.randomuser.me/?nat=US&results=${count}`;
        const request = new XMLHttpRequest();
        request.open("GET", api); // Promise makes a request to the API
        request.onload = () =>
            request.status === 200
                ? resolves(JSON.parse(request.response).results) // If the promise is successful, the data will load
                : reject(Error(request.statusText)); // If the promise is unsuccessful, an error will occur
        request.onerror = err => rejects(err);
        request.send();
    });

Calling getPeople

getPeople(5)
    .then(members => console.log(members))
    .catch(error => console.error(`getPeople failed: ${error.message}`));
  • This promise can be used by calling getPeople function and passing in the number of members that should be loaded.
  • .then function can be chained after getPeople to do sth once promise has been fulfilled.
  • When a promise is rejected, any details are passed to catch.

2.6 Classes

Before ES2015 (i.e. before classes), JS use prototypical inheritance to create structures that feel OOP

function Vacation(destination, length) {
    // Vaction has properties
    this.destination = destination;
    this.length = length;
}

// Vaction has method
Vaction.prototype.print = function() {
    console.log(this.destination + " | " + this.length + " days");
};

const maui = new Vaction("Maui", 7);
  • maui instance inherits print method through prototype

ES2015 introduced class declaration, it's just a wrapper over the prototype syntax shown above.

  • Functions are objects, inheritance is handled through prototype
// Define a class
class Vacation {
    constructor(destination, length) { // Define constructor of this class
        this.destination = destination;
        this.length = length;
    }
    print() {
        console.log(`${this.destination} will take ${this.length} days`);
    }
}

// Instantiate an object
const trip = new Vacation("Santiago, Chile", 7);
trip.print(); // Chile will take 7 days.

Inheritance of JS classes are done through extend:

  • When a class is extended, the subclass inherits all properites and methods of superclass.
class Expedition extends Vacation {
    constructor(destination, length, gear) {
        super(destination, length); // inherit superclass's constructor
        this.gear = gear;
    }

    print() { // add new properties/methods to subclasses
        super.print();
        console.log(`Bring your ${this.gear.join(" and your ")}`);
    }
}

const trip = new Expedition("Mt. Whitney", 3, [
    "sunglasses",
    "prayer flags",
    "camera"
]);

trip.print();
// Mt. Whitney will take 3 days.
// Bring your sunglasses and your prayer flags and your camera

2.7 ES6 Modules

JS module: reusable code import from other files

  • Stored in separate files, one file per module (i.e. each file is a separate module)
  • 2 options to export a module:
      1. Export multiple JS objects from a single module
      1. Export entire module as JS object

Export objects in module

  • export can be used to export any JS type that will be consumed in another module.
  • If module only export 1 single object, use export default
  • export can be used on any JS type: primitives, objects, arrays, and functions.

e.g. Assume a module text-helpers.js export 2 functions:

export const print = (message) => log(message, new Date())

export const log = (message, timestamp) => console.log(`${timestamp.toString()}: ${message}`)

Consume module in JS

  • import is used to import JS objects from a module
  • Module with multiple exports can use destructor to import multiple objects at onece
  • Module variable can be scoped/renamed locally using different name
  • Import everything using *
import { print, log } from "./text-helpers";
import { print as p, log as l } from "./text-helpers"; // scope locally
import * as fns from './text-helpers'

2.7.1 CommonJS

CommonJS is the module pattern supported by all versions of Node, hence Babel and webpack recognize it.

With CommonJS

  • JS objects are exported using module.exports
  • Import moduels using require function

e.g. export module

const print(message) => log(message, new Date(0))
const log(message, timestamp) => console.log(`${timestamp.toString()}: ${message}`)

module.exports = {print, log}

e.g. consume module

const { log, print } = require("./txt-helpers");