4,452
edits
mNo edit summary |
add personal blogs to references |
||
(22 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 = | |||
<source lang="console"> | <source lang="console"> | ||
Line 84: | Line 86: | ||
== Arrow functions and scope == | == 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"> | <source lang="js"> | ||
Line 115: | Line 124: | ||
</source> | </source> | ||
== Destructing objects == | |||
== Destructing objects = | |||
<source lang="js"> | <source lang="js"> | ||
Line 257: | Line 265: | ||
== Spread operator == | == Spread operator == | ||
Reference: [https://github.com/tc39/proposal-object-rest-spread Object Rest/Spread Properties for ECMAScript] | |||
<source lang="js"> | <source lang="js"> | ||
Line 325: | Line 335: | ||
// } | // } | ||
</source> | </source> | ||
== Asynchronous requests == | == Asynchronous requests == | ||
Line 513: | Line 522: | ||
const { log, print } = require("./txt-helpers"); | const { log, print } = require("./txt-helpers"); | ||
</source> | </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] |