4,460
edits
javascript |
add personal blogs to references |
||
(28 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
Notes from '''Learning React, 2nd Edition''' by Porcello & Banks and other sources | |||
= Installation = | = Installation = | ||
Line 17: | Line 19: | ||
[http://kangax.github.io/compat-table/esnext/ Kangax compatibility table] | [http://kangax.github.io/compat-table/esnext/ Kangax compatibility table] | ||
== Three ways to declare variables are == | |||
Three ways to declare variables are | |||
# const | # const | ||
Line 25: | Line 26: | ||
Template string | == Template string == | ||
<source lang="js"> | <source lang="js"> | ||
Line 47: | Line 48: | ||
Function declaration vs function expression | == Function declaration vs function expression == | ||
declarations are hoisted | |||
<source lang="js"> | <source lang="js"> | ||
Line 54: | Line 57: | ||
</source> | </source> | ||
Arrow functions | |||
== Arrow functions == | |||
<source lang="js"> | <source lang="js"> | ||
Line 65: | Line 69: | ||
const lordify = firstName => `${firstName} of Canterbury`; | const lordify = firstName => `${firstName} of Canterbury`; | ||
</source> | </source> | ||
== Returning an object == | |||
'''DON'T FORGET PARENTHESES!''' | |||
<source lang="js"> | |||
const person = (firstName, lastName) => ({ | |||
first: firstName, | |||
last: lastName | |||
}); | |||
console.log(person("Flad", "Hanson")); | |||
</source> | |||
== Arrow functions and scope == | |||
Reference | |||
* [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions Arrow functions] | |||
* [https://www.codementor.io/dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc Understanding "this" in javascript with arrow functions] | |||
<source lang="js"> | |||
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 {} | |||
</source> | |||
To solve this problem: | |||
<source lang="js"> | |||
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 | |||
</source> | |||
== Destructing objects == | |||
<source lang="js"> | |||
const sandwich = { | |||
bread: "dutch crunch", | |||
meat: "tuna", | |||
cheese: "swiss", | |||
toppings: ["lettuce", "tomato", "mustard"] | |||
}; | |||
const { bread, meat } = sandwich; | |||
console.log(bread, meat); // dutch crunch tuna | |||
</source> | |||
<source lang="js"> | |||
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 | |||
</source> | |||
destructuring parameter | |||
<source lang="js"> | |||
const lordify = ({ firstname }) => { | |||
console.log(`${firstname} of Canterbury`); | |||
}; | |||
const regularPerson = { | |||
firstname: "Bill", | |||
lastname: "Wilson" | |||
}; | |||
lordify(regularPerson); // Bill of Canterbury | |||
</source> | |||
<source lang="js"> | |||
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 | |||
</source> | |||
== Destructuring arrays == | |||
<source lang="js"> | |||
const [firstAnimal] = ["Horse", "Mouse", "Cat"]; | |||
console.log(firstAnimal); // Horse | |||
const [, , thirdAnimal] = ["Horse", "Mouse", "Cat"]; | |||
console.log(thirdAnimal); // Cat | |||
</source> | |||
== Object literal enhancement == | |||
<source lang="js"> | |||
const name = "Tallac"; | |||
const elevation = 9738; | |||
const funHike = { name, elevation }; | |||
console.log(funHike); // {name: "Tallac", elevation: 9738} | |||
</source> | |||
<source lang="js"> | |||
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 | |||
</source> | |||
== old vs. new: object syntax == | |||
<source lang="js"> | |||
// 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); | |||
} | |||
}; | |||
</source> | |||
== Spread operator == | |||
Reference: [https://github.com/tc39/proposal-object-rest-spread Object Rest/Spread Properties for ECMAScript] | |||
<source lang="js"> | |||
const peaks = ["Tallac", "Ralston", "Rose"]; | |||
const canyons = ["Ward", "Blackwood"]; | |||
const tahoe = [...peaks, ...canyons]; | |||
console.log(tahoe.join(", ")); // Tallac, Ralston, Rose, Ward, Blackwood | |||
</source> | |||
Getting last element: | |||
<source lang="js"> | |||
const peaks = ["Tallac", "Ralston", "Rose"]; | |||
const [last] = peaks.reverse(); | |||
console.log(last); // Rose | |||
console.log(peaks.join(", ")); // Rose, Ralston, Tallac | |||
</source> | |||
Getting the rest of elements: | |||
<source lang="js"> | |||
const lakes = ["Donner", "Marlette", "Fallen Leaf", "Cascade"]; | |||
const [first, ...others] = lakes; | |||
console.log(others.join(", ")); // Marlette, Fallen Leaf, Cascade | |||
</source> | |||
function parameters: | |||
<source lang="js"> | |||
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"); | |||
</source> | |||
combining objects: | |||
<source lang="js"> | |||
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" | |||
// } | |||
</source> | |||
== Asynchronous requests == | |||
=== Simple promises with fetch === | |||
<source lang="js"> | |||
fetch("https://api.randomuser.me/?nat=US&results=1") | |||
.then(res => res.json()) | |||
.then(json => json.results) | |||
.then(console.log) | |||
.catch(console.error); | |||
</source> | |||
=== Async/Await === | |||
<source lang="js"> | |||
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(); | |||
</source> | |||
=== Building Promises === | |||
<source lang="js"> | |||
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}`)) | |||
); | |||
</source> | |||
== Classes == | |||
=== prototypical inheritance === | |||
<source lang="js"> | |||
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 | |||
</source> | |||
=== ES2015 way of declaring class === | |||
<source lang="js"> | |||
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. | |||
</source> | |||
Simple inheritance | |||
<source lang="js"> | |||
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 | |||
</source> | |||
== 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: | |||
<source lang="js"> | |||
export const print(message) => log(message, new Date()) | |||
export const log(message, timestamp) => | |||
console.log(`${timestamp.toString()}: ${message}`) | |||
</source> | |||
Exporting only one variable from a module using ''export default'' | |||
<source lang="js"> | |||
export default new Expedition("Mt. Freel", 2, ['water","snack"]); | |||
</source> | |||
=== Consuming using the '''import''' command === | |||
<source lang="js"> | |||
import { print, log } from "./text-helpers"; | |||
import freel from "./mt-freel"; | |||
print("printing a message"); | |||
log("logging a message"); | |||
freel.print(); | |||
</source> | |||
scoping under different name: | |||
<source lang="js"> | |||
import { print as p, log as l } from "./text-helpers"; | |||
p("printing a message"); | |||
l("logging a message"); | |||
</source> | |||
import everything | |||
<source lang="js"> | |||
import * as fns from './text-helpers` | |||
</source> | |||
=== CommonJS === | |||
the module pattern that is supported by all versions of Node, "Modules" | |||
e.g. | |||
<source lang="js"> | |||
const print(message) => log(message, new Date()) | |||
const log(message, timestamp) => | |||
console.log(`${timestamp.toString()}: ${message}`} | |||
module.exports = {print, log} | |||
</source> | |||
CommonJS does not support an '''import''' statement; modules are mported with the '''require''' function | |||
<source lang="js"> | |||
const { log, print } = require("./txt-helpers"); | |||
</source> | |||
= 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. | |||
<source lang="js"> | |||
var log = function(msg) { | |||
console.log(msg); | |||
} | |||
log("In JS functions are variables"); | |||
// equivalent to | |||
const log = msg => { console.log(msg); }; | |||
</source> | |||
we can add them to objects: | |||
<source lang="js"> | |||
const obj = { | |||
msg: "they can be added to objects like variables", | |||
log(msg) { console.log(msg); } | |||
}; | |||
obj.log(obj.msg); | |||
</source> | |||
add them to arrays: | |||
<source lang="js"> | |||
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 | |||
</source> | |||
send to other functions as arguments: | |||
<source lang="js"> | |||
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 | |||
</source> | |||
returned from other functions: | |||
<source lang="js"> | |||
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> | |||
</source> | |||
== Imperative versus declarative == | |||
<source lang="js"> | |||
// 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); | |||
</source> | |||
Reference: [http://c2.com/cgi/wiki?DeclarativeProgramming Declarative Programming wiki -- more about d/p paradigm] | |||
Declaring DOM example | |||
<source lang="js"> | |||
// 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")); | |||
</source> | |||
== Functional concepts == | |||
core concepts: immutability, purity, data transformation, higher-order functions, and recursion | |||
=== Immutability === | |||
data mutation | |||
<source lang="js"> | |||
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 | |||
</source> | |||
in JS, function arguments are references to the actual data; we can rewrite so it does not harm the original data | |||
<source lang="js"> | |||
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 | |||
}); | |||
</source> | |||
adding elements to an array | |||
<source lang="js"> | |||
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 }]; | |||
</source> | |||
=== 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 | |||
<source lang="js"> | |||
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} | |||
</source> | |||
have this function take an argument (now pure) | |||
<source lang="js"> | |||
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} | |||
</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 | |||
transform an array into an object | |||
<source lang="js"> | |||
const colors = [ | |||
{ | |||
id: "xekare", | |||
title: "rad red", | |||
rating: 3 | |||
}, | |||
{ | |||
id: "jbwsof", | |||
title: "big blue", | |||
rating: 2 | |||
}, | |||
{ | |||
id: "prigbj", | |||
title: "grizzly grey", | |||
rating: 5 | |||
}, | |||
{ | |||
id: "ryhbhsl", | |||
title: "banana", | |||
rating: 1 | |||
} | |||
]; | |||
const hashColors = colors.reduce((hash, { id, title, rating }) => { | |||
hash[id] = { title, rating }; | |||
return hash; | |||
}, {}); | |||
console.log(hashColors); | |||
// { | |||
// "xekare": { | |||
// title:"rad red", | |||
// rating:3 | |||
// }, | |||
// "jbwsof": { | |||
// title:"big blue", | |||
// rating:2 | |||
// }, | |||
// "prigbj": { | |||
// title:"grizzly grey", | |||
// rating:5 | |||
// }, | |||
// "ryhbhsl": { | |||
// title:"banana", | |||
// rating:1 | |||
// } | |||
// } | |||
</source> | |||
reducing an array with multiple instances of the same value to an array of unique values | |||
<source lang="js"> | |||
const colors = ["red", "red", "green", "blue", "green"]; | |||
const uniqueColors = colors.reduce( | |||
(unique, color) => | |||
unique.indexOf(color) !== -1 ? unique : [...unique, color], | |||
[] | |||
); | |||
console.log(uniqueColors); | |||
// ["red", "green", "blue"] | |||
</source> | |||
=== Higher-Order Functions === | |||
functions that can manipulate other functions; can take functions in as arguments, or return functions, or both | |||
e.g. | |||
<source lang="js"> | |||
const invokeIf = (condition, fnTrue, fnFalse) => | |||
condition ? fnTrue() : fnFalse(); | |||
const showWelcome = () => console.log("Welcome!!!"); | |||
const showUnauthorized = () => console.log("Unauthorized!!!"); | |||
invokeIf(true, showWelcome, showUnauthorized); // "Welcome!!!" | |||
invokeIf(false, showWelcome, showUnauthorized); // "Unauthorized!!!" | |||
</source> | |||
e.g. currying | |||
<source lang="js"> | |||
const userLogs = userName => message => | |||
console.log(`${userName} -> ${message}`); | |||
const log = userLogs("grandpa23"); | |||
log("attempted to load 20 fake members"); | |||
getFakeMembers(20).then( | |||
members => log(`successfully loaded ${members.length} members`), | |||
error => log("encountered an error loading members") | |||
); | |||
// grandpa23 -> attempted to load 20 fake members | |||
// grandpa23 -> successfully loaded 20 members | |||
// grandpa23 -> attempted to load 20 fake members | |||
// grandpa23 -> encountered an error loading members | |||
</source> | |||
=== Recursion === | |||
<source lang="js"> | |||
const countdown = (value, fn) => { | |||
fn(value); | |||
return value > 0 ? countdown(value - 1, fn) : value; | |||
}; | |||
countdown(10, value => console.log(value)); | |||
// 10 | |||
// 9 | |||
// 8 | |||
// 7 | |||
// 6 | |||
// 5 | |||
// 4 | |||
// 3 | |||
// 2 | |||
// 1 | |||
// 0 | |||
</source> | |||
recursion is well-suited for searching through data structures | |||
<source lang="js"> | |||
const dan = { | |||
type: "person", | |||
data: { | |||
gender: "male", | |||
info: { | |||
id: 22, | |||
fullname: { | |||
first: "Dan", | |||
last: "Deacon" | |||
} | |||
} | |||
} | |||
}; | |||
const deepPick = (fields, object = {}) => { | |||
const [first, ...remaining] = fields.split("."); | |||
return remaining.length | |||
? deepPick(remaining.join("."), object[first]) | |||
: object[first]; | |||
}; | |||
deepPick("type", dan); // "person" | |||
deepPick("data.info.fullname.first", dan); // "Dan" | |||
deepPick("data.info.fullname.first", dan); // "Deacon" | |||
// First Iteration | |||
// first = "data" | |||
// remaining.join(".") = "info.fullname.first" | |||
// object[first] = { gender: "male", {info} } | |||
// Second Iteration | |||
// first = "info" | |||
// remaining.join(".") = "fullname.first" | |||
// object[first] = {id: 22, {fullname}} | |||
// Third Iteration | |||
// first = "fullname" | |||
// remaining.join("." = "first" | |||
// object[first] = {first: "Dan", last: "Deacon" } | |||
// Finally... | |||
// first = "first" | |||
// remaining.length = 0 | |||
// object[first] = "Deacon" | |||
</source> | |||
=== Composition === | |||
chaining | |||
<source lang="js"> | |||
const template = "hh:mm:ss tt"; | |||
const clockTime = template | |||
.replace("hh", "03") | |||
.replace("mm", "33") | |||
.replace("ss", "33") | |||
.replace("tt", "PM"); | |||
console.log(clockTime); | |||
// "03:33:33 PM" | |||
</source> | |||
<code>both</code> function pipes a value through two separate functions. | |||
<source lang="js"> | |||
const both = date => appendAMPM(civilianHours(date)); | |||
// but hard to read, so better approach is to use higher order function | |||
const compose = (...fns) => arg => | |||
fns.reduce((composed, f) => f(composed), arg); | |||
const both = compose( | |||
civilianHours, | |||
appendAMPM | |||
); | |||
both(new Date()); | |||
</source> | |||
== Putting it all together == | |||
ticking time | |||
<source lang="js"> | |||
// Log Clock Time every Second | |||
setInterval(logClockTime, 1000); | |||
function logClockTime() { | |||
// Get Time string as civilian time | |||
let time = getClockTime(); | |||
// Clear the Console and log the time | |||
console.clear(); | |||
console.log(time); | |||
} | |||
function getClockTime() { | |||
// Get the Current Time | |||
let date = new Date(); | |||
let time = ""; | |||
// Serialize clock time | |||
let time = { | |||
hours: date.getHours(), | |||
minutes: date.getMinutes(), | |||
seconds: date.getSeconds(), | |||
ampm: "AM" | |||
}; | |||
// Convert to civilian time | |||
if (time.hours == 12) { | |||
time.ampm = "PM"; | |||
} else if (time.hours > 12) { | |||
time.ampm = "PM"; | |||
time.hours -= 12; | |||
} | |||
// Prepend a 0 on the hours to make double digits | |||
if (time.hours < 10) { | |||
time.hours = "0" + time.hours; | |||
} | |||
// prepend a 0 on the minutes to make double digits | |||
if (time.minutes < 10) { | |||
time.minutes = "0" + time.minutes; | |||
} | |||
// prepend a 0 on the seconds to make double digits | |||
if (time.seconds < 10) { | |||
time.seconds = "0" + time.seconds; | |||
} | |||
// Format the clock time as a string "hh:mm:ss tt" | |||
return time.hours + ":" + time.minutes + ":" + time.seconds + " " + time.ampm; | |||
} | |||
</source> | |||
REFACTORING using what we learned | |||
in functional programs, use functions over values wherever possible; invoke the function to obtain the value when needed | |||
<source lang="js"> | |||
const oneSecond = () => 1000; | |||
const getCurrentTime = () => new Date(); | |||
const clear = () => console.clear(); | |||
const log = message => console.log(message); | |||
</source> | |||
three functions will be used to mutate the clock | |||
# serializeClockTime - take a date object and returns a object for clock time that contains hours minutes, and seconds. | |||
# civilianHours - takes the clock time object and returns an object where hours are converted to civilian time (1300 becomes 1:00) | |||
# appendAMPM - take the clock time object and appends time of day, AM or PM, to that object. | |||
<source lang="js"> | |||
const serializeClockTime = date => ({ | |||
hours: date.getHours(), | |||
minutes: date.getMinutes(), | |||
seconds: date.getSeconds() | |||
}); | |||
const civilianHours = clockTime => ({ | |||
...clockTime, | |||
hours: clockTime.hours > 12 ? clockTime.hours - 12 : clockTime.hours | |||
}); | |||
const appendAMPM = clockTime => ({ | |||
...clockTime, | |||
ampm: clockTime.hours >= 12 ? "PM" : "AM" | |||
}); | |||
</source> | |||
higher order functions needed | |||
# display - takes a target function and returns a function that will send a time to the target. In this eample the target will be console.log | |||
# formatClock - takes a template string and uses it to return clock time formatted based upon the criteria from the string. | |||
# prependZero - | |||
<source lang="js"> | |||
const display = target => time => target(time); | |||
const formatClock = format => time => | |||
format | |||
.replace("hh", time.hours) | |||
.replace("mm", time.minutes) | |||
.replace("ss", time.seconds) | |||
.replace("tt", time.ampm); | |||
const prependZero = key => clockTime => ({ | |||
...clockTime, | |||
[key]: clockTime[key] < 10 ? "0" + clockTime[key] : clockTime[key] | |||
}); | |||
</source> | |||
compose | |||
<source lang="js"> | |||
const convertToCivilianTime = clockTime => | |||
compose( | |||
appendAMPM, | |||
civilianHours | |||
)(clockTime); | |||
const doubleDigits = civilianTime => | |||
compose( | |||
prependZero("hours"), | |||
prependZero("minutes"), | |||
prependZero("seconds") | |||
)(civilianTime); | |||
const startTicking = () => | |||
setInterval( | |||
compose( | |||
clear, | |||
getCurrentTime, | |||
serializeClockTime, | |||
convertToCivilianTime, | |||
doubleDigits, | |||
formatClock("hh:mm:ss tt"), | |||
display(log) | |||
), | |||
oneSecond() | |||
); | |||
startTicking(); | |||
</source> | |||
= How React Works = | |||
== React elements == | |||
<source lang="js"> | |||
React.createElement("h1", { id: "recipe-0" }, "Baked Salmon"); | |||
// <h1 id="recipe-0">Baked Salmon</h1> | |||
// log: | |||
{ | |||
$$typeof: Symbol(React.element), | |||
"type": "h1", | |||
"key": null, | |||
"ref": null, | |||
"props": {id: "recipe-0", children: "Baked Salmon"}, | |||
"_owner": null, | |||
"_store": {} | |||
} | |||
</source> | |||
== ReactDOM == | |||
contains tools necessary to render React element in the browser via '''render''' method | |||
<source lang="js"> | |||
const dish = React.createElement("h1", null, "Baked Salmon"); | |||
ReactDOM.render(dish, document.getElementById("root")); | |||
</source> | |||
rendering array of elements | |||
<source lang="js"> | |||
const dish = React.createElement("h1", null, "Baked Salmon"); | |||
const dessert = React.createElement("h2", null, "Coconut Cream Pie"); | |||
ReactDOM.render([dish, dessert], document.getElementById("root")); | |||
</source> | |||
== Children == | |||
<source lang="js"> | |||
React.createElement( | |||
"ul", | |||
null, | |||
React.createElement("li", null, "2 lb salmon"), | |||
React.createElement("li", null, "5 sprigs fresh rosemary"), | |||
React.createElement("li", null, "2 tablespoons olive oil"), | |||
React.createElement("li", null, "2 small lemons"), | |||
React.createElement("li", null, "1 teaspoon kosher salt"), | |||
React.createElement("li", null, "4 cloves of chopped garlic") | |||
); | |||
// equivalent to | |||
const items = [ | |||
"2 lb salmon", | |||
"5 sprigs fresh rosemary", | |||
"2 tablespoons olive oil", | |||
"2 small lemons", | |||
"1 teaspoon kosher salt", | |||
"4 cloves of chopped garlic" | |||
]; | |||
React.createElement( | |||
"ul", | |||
{ className: "ingredients" }, | |||
items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient)) | |||
); | |||
</source> | |||
== React Components == | |||
<source lang="js"> | |||
function IngredientsList({ items }) { // destructure items from props | |||
return React.createElement( | |||
"ul", | |||
{ className: "ingredients" }, | |||
items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient) | |||
); | |||
} | |||
const items = [ | |||
"2 lb salmon", | |||
"5 sprigs fresh rosemary", | |||
"2 tablespoons olive oil", | |||
"2 small lemons", | |||
"1 teaspoon kosher salt", | |||
"4 cloves of chopped garlic" | |||
]; | |||
ReactDOM.render( | |||
React.createElement(IngredientsList, { items }, null), | |||
document.getElementById("root") | |||
); | |||
</source> | |||
use functions to create components because class syntax is being deprecated. | |||
= React with JSX = | |||
== React elements as JSX == | |||
=== Nested components === | |||
<source lang="html"> | |||
<IngredientsList> | |||
<Ingredient /> | |||
<Ingredient /> | |||
<Ingredient /> | |||
</IngredientsList> | |||
</source> | |||
=== ClassName === | |||
Since <code>class</code> is a reserved word in JavaScript, className is used to define the class attribute. | |||
=== JavaScript Expressions === | |||
<source lang="js"> | |||
<h1>{title}</h1> | |||
<input type="checkbox" defaultChecked="{false}" /> | |||
</source> | |||
=== Evaluation === | |||
<source lang="js"> | |||
<h1>{"Hello" + title}</h1> | |||
<h1>{title.toLowerCase().replace}</h1> | |||
function appendTitle({ title }) { | |||
console.log(`${title} is great!`); | |||
} | |||
</source> | |||
=== Mapping Arrays with JSX === | |||
<source lang="js"> | |||
<ul> | |||
{props.ingredients.map((ingredient, i) => ( | |||
<li key="{i}">{ingredient}</li> | |||
))} | |||
</ul> | |||
</source> | |||
== Babel == | |||
template file to use | |||
<source lang="html"> | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<title>React Examples</title> | |||
</head> | |||
<body> | |||
<div id="root"></div> | |||
<!-- React Library & React DOM --> | |||
<script src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> | |||
<script src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.js"></script> | |||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |||
<script type="text/babel"> | |||
// JSX code here. Or link to separate JavaScript file that contains JSX. | |||
</script> | |||
</body> | |||
</html> | |||
</source> | |||
== Recipes as JSX == | |||
<source lang="js"> | |||
const data = [ | |||
{ | |||
name: "Baked Salmon", | |||
ingredients: [ | |||
{ name: "Salmon", amount: 1, measurement: "l lb" }, | |||
{ name: "Pine Nuts", amount: 1, measurement: "cup" }, | |||
{ name: "Butter Lettuce", amount: 2, measurement: "cups" }, | |||
{ name: "Yellow Squash", amount: 1, measurement: "med" }, | |||
{ name: "Olive Oil", amount: 0.5, measurement: "cup" }, | |||
{ name: "Garlic", amount: 3, measurement: "cloves" } | |||
], | |||
steps: [ | |||
"Preheat the oven to 350 degrees.", | |||
"Spread the olive oil around a glass baking dish.", | |||
"Add the yellow squash and place in the oven for 30 mins.", | |||
"Add the salmon, garlic, and pine nuts to the dish.", | |||
"Bake for 15 minutes.", | |||
"Remove from oven. Add the lettuce and serve." | |||
] | |||
}, | |||
{ | |||
name: "Fish Tacos", | |||
ingredients: [ | |||
{ name: "Whitefish", amount: 1, measurement: "l lb" }, | |||
{ name: "Cheese", amount: 1, measurement: "cup" }, | |||
{ name: "Iceberg Lettuce", amount: 2, measurement: "cups" }, | |||
{ name: "Tomatoes", amount: 2, measurement: "large" }, | |||
{ name: "Tortillas", amount: 3, measurement: "med" } | |||
], | |||
steps: [ | |||
"Cook the fish on the grill until cooked through.", | |||
"Place the fish on the 3 tortillas.", | |||
"Top them with lettuce, tomatoes, and cheese." | |||
] | |||
} | |||
]; | |||
// A function component for an individual Recipe | |||
function Recipe (props) { | |||
... | |||
} | |||
// A function component for the Menu of Recipes | |||
function Menu (props) { | |||
return ( | |||
<article> | |||
<header> | |||
<h1>{props.title}</h1> | |||
</header> | |||
<div className="recipes"> | |||
{props.recipes.map((recipe, i) => ( | |||
<Recipe | |||
key={i} | |||
name={recipe.name} | |||
ingredients={recipe.ingredients} | |||
steps={recipe.steps} | |||
/> | |||
))} | |||
// equivalent to | |||
{ | |||
props.recipes.map((recipe, i) => <Recipe key={i} {...recipe} />); | |||
} | |||
</div> | |||
</article> | |||
); | |||
} | |||
// A call to ReactDOM.render to render our Menu into the current DOM | |||
ReactDOM.render( | |||
<Menu recipes={data} title="Delicious Recipes" />, | |||
document.getElementById("root") | |||
); | |||
</source> | |||
we can access title & recipes vars directly, so vars don't have to be prefixed with props: | |||
<source lang="js"> | |||
function Menu({ title, recipes }) { | |||
return ( | |||
<article> | |||
<header> | |||
<h1>{title}</h1> | |||
</header> | |||
<div className="recipes"> | |||
{recipes.map((recipe, i) => ( | |||
<Recipe key={i} {...recipe} /> | |||
))} | |||
</div> | |||
</article> | |||
); | |||
} | |||
</source> | |||
code for the component for each individual recipe: | |||
<source lang="js"> | |||
function Recipe({ name, ingredients, steps }) { | |||
return ( | |||
<section id={name.toLowerCase().replace(/ /g, "-")}> | |||
<h1>{name}</h1> | |||
<ul className="ingredients"> | |||
{ingredients.map((ingredient, i) => ( | |||
<li key={i}>{ingredient.name}</li> | |||
))} | |||
</ul> | |||
<section className="instructions"> | |||
<h2>Cooking Instructions</h2> | |||
{steps.map((step, i) => ( | |||
<p key={i}>{step}</p> | |||
))} | |||
</section> | |||
</section> | |||
); | |||
} | |||
</source> | |||
== React fragments == | |||
<source lang="js"> | |||
function Cat({ name }) { | |||
return ( | |||
<React.Fragment> | |||
<h1>The cat's name is {name}</h1> | |||
<p>He's good.</p> | |||
</React.Fragment> | |||
); | |||
} | |||
// equivalent to | |||
function Cat({ name }) { | |||
return ( | |||
<> | |||
<h1>The cat's name is {name}</h1> | |||
<p>He's good.</p> | |||
</> | |||
); | |||
} | |||
</source> | |||
== Intro to webpack == | |||
* Code splitting | |||
* Minification | |||
* Feature flagging | |||
* Hot module replacement (HMR) | |||
* Modularity | |||
* Composition | |||
* Speed | |||
* Consistency | |||
Installing webpack | |||
<source lang="console"> | |||
$ npm install --save-dev webpack@next webpack-cli | |||
</source> | |||
Installing Babel dependencies | |||
<source lang="console"> | |||
$ npm install babel-loader @babel/core --save-dev | |||
</source> | |||
Specify Babel presets | |||
<source lang="console"> | |||
$ npm install @babel/preset-env @babel/preset-react --save-dev | |||
</source> | |||
= React State Management = | |||
= Reference = | |||
* [https://react-icons.netlify.com React Icons] | |||
* Personal blogs | |||
** [https://www.leighhalliday.com Leigh Halliday] | |||
** [https://www.robinwieruch.de Robin Wieruch] |