React: Difference between revisions
Line 796: | Line 796: | ||
// {name: "Frederick Douglass", canRead: false, canWrite: false} | // {name: "Frederick Douglass", canRead: false, canWrite: false} | ||
</source> | </source> | ||
an impure function --it changes DOM | |||
<source lang="js"> | |||
function Header(text) { | |||
let h1 = document.createElement("h1"); | |||
h1.innerText = text; | |||
document.body.appendChild(h1); | |||
} | |||
Header("Header() caused side effects"); | |||
</source> | |||
In React, UI is expressed with pure functions. e.g. | |||
<source lang="js"> | |||
const Header = props => <h1>{props.title}</h1>; | |||
</source> | |||
==== Guideline for writing pure functions ==== | |||
# at least one argument | |||
# return a value or another function | |||
# should not change or mutate any of its arguments | |||
=== Data Transformations === | |||
via Array.map and Array.reduce | |||
using Array.join | |||
<source lang="js"> | |||
const schools = ["Yorktown", "Washington & Lee", "Wakefield"]; | |||
console.log(schools.join(", ")); | |||
// "Yorktown, Washington & Lee, Wakefield" | |||
</source> | |||
create a new array of the schools that begin with the letter "W" | |||
<source lang="js"> | |||
const wSchools = schools.filter(school => school[0] === "W"); | |||
console.log(wSchools); | |||
// ["Washington & Lee", "Wakefield"] | |||
</source> | |||
when removing an item from an array, use Array.filter over .pop() or .splice() because .filter() is immutable | |||
<source lang="js"> | |||
const cutSchool = (cut, list) => list.filter(school => school !== cut); | |||
console.log(cutSchool("Washington & Lee", schools).join(", ")); | |||
// "Yorktown, Wakefield" | |||
console.log(schools.join("\n")); | |||
// Yorktown | |||
// Washington & Lee | |||
// Wakefield | |||
</source> | |||
Array.map takes a function as its argument which will be invoked once for every item in the array, and whatever it returns will be added to the new array | |||
<source lang="js"> | |||
const highSchools = schools.map(school => `${school} High School`); | |||
console.log(highSchools.join("\n")); | |||
// Yorktown High School | |||
// Washington & Lee High School | |||
// Wakefield High School | |||
console.log(schools.join("\n")); | |||
// Yorktown | |||
// Washington & Lee | |||
// Wakefield | |||
</source> | |||
e.g. return an object for every school | |||
<source lang="js"> | |||
const highSchools = schools.map(school => ({ name: school })); | |||
console.log(highSchools); | |||
// [ | |||
// { name: "Yorktown" }, | |||
// { name: "Washington & Lee" }, | |||
// { name: "Wakefield" } | |||
// ] | |||
</source> | |||
pure f(x) that changes one object in an array of objects | |||
<source lang="js"> | |||
let schools = [ | |||
{ name: "Yorktown" }, | |||
{ name: "Stratford" }, | |||
{ name: "Washington & Lee" }, | |||
{ name: "Wakefield" } | |||
]; | |||
const editName = (oldName, name, arr) => | |||
arr.map(item => { | |||
if (item.name === oldName) { | |||
return { | |||
...item, | |||
name | |||
}; | |||
} else { | |||
return item; | |||
} | |||
}); | |||
let updatedSchools = editName("Stratford", "HB Woodlawn", schools); | |||
console.log(updatedSchools[1]); // { name: "HB Woodlawn" } | |||
console.log(schools[1]); // { name: "Stratford" } | |||
// equivalent to | |||
const editName = (oldName, name, arr) => | |||
arr.map(item => (item.name === oldName ? { ...item, name } : item)); | |||
</source> | |||
transform schools object into an array of schools: | |||
<source lang="js"> | |||
const schools = { | |||
Yorktown: 10, | |||
"Washington & Lee": 2, | |||
Wakefield: 5 | |||
}; | |||
const schoolArray = Object.keys(schools).map(key => ({ | |||
name: key, | |||
wins: schools[key] | |||
})); | |||
console.log(schoolArray); | |||
// [ | |||
// { | |||
// name: "Yorktown", | |||
// wins: 10 | |||
// }, | |||
// { | |||
// name: "Washington & Lee", | |||
// wins: 2 | |||
// }, | |||
// { | |||
// name: "Wakefield", | |||
// wins: 5 | |||
// } | |||
// ] | |||
</source> | |||
<code>reduce</code> and <code>reduceRight</code> are used to trasnform an array into any value, including a number, string, boolean, object, or even a function | |||
maximum number in an array of numbers | |||
<source lang="js"> | |||
const ages = [21, 18, 42, 40, 64, 63, 34]; | |||
const maxAge = ages.reduce((max, age) => { | |||
console.log(`${age} > ${max} = ${age > max}`); | |||
if (age > max) { | |||
return age; | |||
} else { | |||
return max; | |||
} | |||
}, 0); | |||
console.log("maxAge", maxAge); | |||
// 21 > 0 = true | |||
// 18 > 21 = false | |||
// 42 > 21 = true | |||
// 40 > 42 = false | |||
// 64 > 42 = true | |||
// 63 > 64 = false | |||
// 34 > 64 = false | |||
// maxAge 64 | |||
// equivalent to | |||
const max = ages.reduce((max, value) => (value > max ? value : max), 0); | |||
</source> | |||
reduce takes two arguments: a callback function & an original value | |||
reduceRight simply starts reducing from the end of the array rather than the beginning |
Revision as of 08:56, 1 October 2019
Notes from Learning React, 2nd Edition by Porcello & Banks
Installation
// initialize a nodejs project (creates package.json)
$ npm init -y
// package manager
$ npm install yarn
$ yarn install packagename
$ yarn remove packagename
JavaScript
Three ways to declare variables are
- const
- var
- let
Template string
console.log(`${lastName}, ${firstName} ${middleName}`);
document.body.innerHTML = `
<section>
<header>
<h1>The React Blog</h1>
</header>
<article>
<h2>${article.title}</h2>
${article.body}
</article>
<footer>
<p>copyright ${new Date().getYear()} | The React Blog</p>
</footer>
</section>
`;
Function declaration vs function expression
declarations are hoisted
const f = function() {
};
Arrow functions
const lordify = function(firstName) {
return `${firstName} of Canterbury`;
};
// equals
const lordify = firstName => `${firstName} of Canterbury`;
Returning an object
DON'T FORGET PARENTHESES!
const person = (firstName, lastName) => ({
first: firstName,
last: lastName
});
console.log(person("Flad", "Hanson"));
Arrow functions and scope
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
console.log(this); // Window {}
To solve this problem:
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
Destructing objects
const sandwich = {
bread: "dutch crunch",
meat: "tuna",
cheese: "swiss",
toppings: ["lettuce", "tomato", "mustard"]
};
const { bread, meat } = sandwich;
console.log(bread, meat); // dutch crunch tuna
const sandwich = {
bread: "dutch crunch",
meat: "tuna",
cheese: "swiss",
toppings: ["lettuce", "tomato", "mustard"]
};
let { bread, meat } = sandwich;
bread = "garlic";
meat = "turkey";
console.log(bread); // garlic
console.log(meat); // turkey
console.log(sandwich.bread, sandwich.meat); // dutch crunch tuna
destructuring parameter
const lordify = ({ firstname }) => {
console.log(`${firstname} of Canterbury`);
};
const regularPerson = {
firstname: "Bill",
lastname: "Wilson"
};
lordify(regularPerson); // Bill of Canterbury
const regularPerson = {
firstname: "Bill",
lastname: "Wilson",
spouse: {
firstname: "Phil",
lastname: "Wilson"
}
};
const lordify = ({ spouse: { firstname } }) => {
console.log(`${firstname} of Canterbury`);
};
lordify(regularPerson); // Phil of Canterbury
Destructuring arrays
const [firstAnimal] = ["Horse", "Mouse", "Cat"];
console.log(firstAnimal); // Horse
const [, , thirdAnimal] = ["Horse", "Mouse", "Cat"];
console.log(thirdAnimal); // Cat
Object literal enhancement
const name = "Tallac";
const elevation = 9738;
const funHike = { name, elevation };
console.log(funHike); // {name: "Tallac", elevation: 9738}
const name = "Tallac";
const elevation = 9738;
const print = function() {
console.log(`Mt. ${this.name} is ${this.elevation} feet tall`);
};
const funHike = { name, elevation, print };
funHike.print(); // Mt. Tallac is 9738 feet tall
old vs. new: object syntax
// Old
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);
}
};
// New
const skier = {
name,
sound,
powderYell() {
let yell = this.sound.toUpperCase();
console.log(`${yell} ${yell} ${yell}!!!`);
},
speed(mph) {
this.speed = mph;
console.log("speed:", mph);
}
};
Spread operator
const peaks = ["Tallac", "Ralston", "Rose"];
const canyons = ["Ward", "Blackwood"];
const tahoe = [...peaks, ...canyons];
console.log(tahoe.join(", ")); // Tallac, Ralston, Rose, Ward, Blackwood
Getting last element:
const peaks = ["Tallac", "Ralston", "Rose"];
const [last] = peaks.reverse();
console.log(last); // Rose
console.log(peaks.join(", ")); // Rose, Ralston, Tallac
Getting the rest of elements:
const lakes = ["Donner", "Marlette", "Fallen Leaf", "Cascade"];
const [first, ...others] = lakes;
console.log(others.join(", ")); // Marlette, Fallen Leaf, Cascade
function parameters:
function directions(...args) {
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`);
}
directions("Truckee", "Tahoe City", "Sunnyside", "Homewood", "Tahoma");
combining objects:
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"
// }
Asynchronous requests
Simple promises with fetch
fetch("https://api.randomuser.me/?nat=US&results=1")
.then(res => res.json())
.then(json => json.results)
.then(console.log)
.catch(console.error);
Async/Await
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();
Building Promises
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);
request.onload = () =>
request.status === 200
? resolves(JSON.parse(request.response).results)
: reject(Error(request.statusText));
request.onerror = err => rejects(err);
request.send();
});
getPeople(5)
.then(members => console.log(members))
.catch(error => console.error(`getPeople failed: ${error.message}`))
);
Classes
prototypical inheritance
function Vacation(destination, length) {
this.destination = destination;
this.length = length;
}
Vacation.prototype.print = function() {
console.log(this.destination + " | " + this.length + " days");
};
const maui = new Vacation("Maui", 7);
maui.print(); // Maui | 7 days
ES2015 way of declaring class
class Vacation {
constructor(destination, length) {
this.destination = destination;
this.length = length;
}
print() {
console.log(`${this.destination} will take ${this.length} days.`);
}
}
const trip = new Vacation("Santiago, Chile", 7);
trip.print(); // Chile will take 7 days.
Simple inheritance
class Expedition extends Vacation {
constructor(destination, length, gear) {
super(destination, length);
this.gear = gear;
}
print() {
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
ES6 Modules
A module is a peice of reusable code that can easily be incorporated into other JavaScript files without causing variable collisions.
In text-helpers.js, two functions are exported:
export const print(message) => log(message, new Date())
export const log(message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`)
Exporting only one variable from a module using export default
export default new Expedition("Mt. Freel", 2, ['water","snack"]);
Consuming using the import command
import { print, log } from "./text-helpers";
import freel from "./mt-freel";
print("printing a message");
log("logging a message");
freel.print();
scoping under different name:
import { print as p, log as l } from "./text-helpers";
p("printing a message");
l("logging a message");
import everything
import * as fns from './text-helpers`
CommonJS
the module pattern that is supported by all versions of Node, "Modules"
e.g.
const print(message) => log(message, new Date())
const log(message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`}
module.exports = {print, log}
CommonJS does not support an import statement; modules are mported with the require function
const { log, print } = require("./txt-helpers");
Functional Programming with JavaScript
What it means to be functional
JavaScript functions are first-class citizens -- they can do the same things that variables can do. Functions can represent data. e.g.
var log = function(msg) {
console.log(msg);
}
log("In JS functions are variables");
// equivalent to
const log = msg => { console.log(msg); };
we can add them to objects:
const obj = {
msg: "they can be added to objects like variables",
log(msg) { console.log(msg); }
};
obj.log(obj.msg);
add them to arrays:
const messages = [
"They can be inserted into arrays",
message => console.log(message),
"like variables",
message => console.log(message)
];
messages[1](messages[0]); // They can be inserted into arrays
messages[3](messages[2]); // like variables
send to other functions as arguments:
const insideFn = logger => {
logger("They can be sent to other functions as arguments");
};
insideFn(message => console.log(message));
// They can be sent to other functions as arguments
returned from other functions:
const createScream = function(logger) {
return function(message) {
logger(message.toUpperCase() + "!!!");
};
};
const scream = createScream(message => console.log(message));
scream("functions can be returned from other functions");
scream("createScream returns a function");
scream("scream invokes that returned function");
// FUNCTIONS CAN BE RETURNED FROM OTHER FUNCTIONS!!!
// CREATESCREAM RETURNS A FUNCTION!!!
// SCREAM INVOKES THAT RETURNED FUNCTION!!!
// equivalent to (using arrows)
const createScream = logger => message => {
logger(message.toUpperCase() + "!!!");
};
// when more than one arrows exist, there's a higher-order function
</source>
Imperative versus declarative
// making a string URL friendly
// IMPERATIVE way
const string = "Restaurants in Hanalei";
const urlFriendly = "";
for (var i = 0; i < string.length; i++) {
if (string[i] === " ") {
urlFriendly += "-";
} else {
urlFriendly += string[i];
}
}
console.log(urlFriendly); // "Restaurants-in-Hanalei"
// DECLARATIVE way
const string = "Restaurants in Hanalei";
const urlFriendly = string.replace(/ /g, "-");
console.log(urlFriendly);
Reference: Declarative Programming wiki -- more about d/p paradigm
Declaring DOM example
// imperative approach
const target = document.getElementById("target");
const wrapper = document.createElement("div");
const headline = document.createElement("h1");
wrapper.id = "welcome";
headline.innerText = "Hello World";
wrapper.appendChild(headline);
target.appendChild(wrapper);
// declarative approach using a React component
const { render } = ReactDOM;
const Welcome = () => (
<div id="welcome">
<h1>Hello World</h1>
</div>
);
render(<Welcome />, document.getElementById("target"));
Functional concepts
core concepts: immutability, purity, data transformation, higher-order functions, and recursion
Immutability
data mutation
let color_lawn = {
title: "lawn",
color: "#00ff00",
rating: 0
};
function rateColor(color, rating) {
color.rating = rating;
return color;
}
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 5
in JS, function arguments are references to the actual data; we can rewrite so it does not harm the original data
const rateColor = function(color, rating) {
return Object.assign({}, color, { rating: rating }); // take a blank object, copy copy to that object, and overwrite rating on the copy
};
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 4
// use the spread operator to copy the color into a new object and then overwrite its rating:
// equivalent to
const rateColor = (color, rating) => ({
...color,
rating
});
adding elements to an array
let list = [{ title: "Rad Red" }, { title: "Lawn" }, { title: "Party Pink" }];
const addColor = function(title, colors) {
colors.push({ title: title });
return colors;
};
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 4
// use Array.concat to keep immutable
const addColor = (title, array) => array.concat({ title });
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 3
// equivalent using spread operator
// copies the original list to a new array and then adds a new object containing the color's title to that copy.
const addColor = (title, list) => [...list, { title }];
Pure Functions
function that returns a value that is computed based on its argument; treats arguments as immutable data, so nothing else is changed about the application
e.g. impure function
const frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
function selfEducate() {
frederick.canRead = true;
frederick.canWrite = true;
return frederick;
}
selfEducate();
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// still the same result
const selfEducate = person => {
person.canRead = true;
person.canWrite = true;
return person;
};
console.log(selfEducate(frederick));
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// {name: "Frederick Douglass", canRead: true, canWrite: true}
have this function take an argument (now pure)
const frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
const selfEducate = person => ({
...person,
canRead: true,
canWrite: true
});
console.log(selfEducate(frederick));
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// {name: "Frederick Douglass", canRead: false, canWrite: false}
an impure function --it changes DOM
function Header(text) {
let h1 = document.createElement("h1");
h1.innerText = text;
document.body.appendChild(h1);
}
Header("Header() caused side effects");
In React, UI is expressed with pure functions. e.g.
const Header = props => <h1>{props.title}</h1>;
Guideline for writing pure functions
- at least one argument
- return a value or another function
- should not change or mutate any of its arguments
Data Transformations
via Array.map and Array.reduce
using Array.join
const schools = ["Yorktown", "Washington & Lee", "Wakefield"];
console.log(schools.join(", "));
// "Yorktown, Washington & Lee, Wakefield"
create a new array of the schools that begin with the letter "W"
const wSchools = schools.filter(school => school[0] === "W");
console.log(wSchools);
// ["Washington & Lee", "Wakefield"]
when removing an item from an array, use Array.filter over .pop() or .splice() because .filter() is immutable
const cutSchool = (cut, list) => list.filter(school => school !== cut);
console.log(cutSchool("Washington & Lee", schools).join(", "));
// "Yorktown, Wakefield"
console.log(schools.join("\n"));
// Yorktown
// Washington & Lee
// Wakefield
Array.map takes a function as its argument which will be invoked once for every item in the array, and whatever it returns will be added to the new array
const highSchools = schools.map(school => `${school} High School`);
console.log(highSchools.join("\n"));
// Yorktown High School
// Washington & Lee High School
// Wakefield High School
console.log(schools.join("\n"));
// Yorktown
// Washington & Lee
// Wakefield
e.g. return an object for every school
const highSchools = schools.map(school => ({ name: school }));
console.log(highSchools);
// [
// { name: "Yorktown" },
// { name: "Washington & Lee" },
// { name: "Wakefield" }
// ]
pure f(x) that changes one object in an array of objects
let schools = [
{ name: "Yorktown" },
{ name: "Stratford" },
{ name: "Washington & Lee" },
{ name: "Wakefield" }
];
const editName = (oldName, name, arr) =>
arr.map(item => {
if (item.name === oldName) {
return {
...item,
name
};
} else {
return item;
}
});
let updatedSchools = editName("Stratford", "HB Woodlawn", schools);
console.log(updatedSchools[1]); // { name: "HB Woodlawn" }
console.log(schools[1]); // { name: "Stratford" }
// equivalent to
const editName = (oldName, name, arr) =>
arr.map(item => (item.name === oldName ? { ...item, name } : item));
transform schools object into an array of schools:
const schools = {
Yorktown: 10,
"Washington & Lee": 2,
Wakefield: 5
};
const schoolArray = Object.keys(schools).map(key => ({
name: key,
wins: schools[key]
}));
console.log(schoolArray);
// [
// {
// name: "Yorktown",
// wins: 10
// },
// {
// name: "Washington & Lee",
// wins: 2
// },
// {
// name: "Wakefield",
// wins: 5
// }
// ]
reduce
and reduceRight
are used to trasnform an array into any value, including a number, string, boolean, object, or even a function
maximum number in an array of numbers
const ages = [21, 18, 42, 40, 64, 63, 34];
const maxAge = ages.reduce((max, age) => {
console.log(`${age} > ${max} = ${age > max}`);
if (age > max) {
return age;
} else {
return max;
}
}, 0);
console.log("maxAge", maxAge);
// 21 > 0 = true
// 18 > 21 = false
// 42 > 21 = true
// 40 > 42 = false
// 64 > 42 = true
// 63 > 64 = false
// 34 > 64 = false
// maxAge 64
// equivalent to
const max = ages.reduce((max, value) => (value > max ? value : max), 0);
reduce takes two arguments: a callback function & an original value
reduceRight simply starts reducing from the end of the array rather than the beginning