4,452
edits
add personal blogs to references |
|||
(18 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
Notes from Learning React, 2nd Edition by Porcello & Banks | Notes from '''Learning React, 2nd Edition''' by Porcello & Banks and other sources | ||
= Installation = | = Installation = | ||
Line 86: | 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 116: | Line 123: | ||
tahoe.print(); // Freel, Rose, Tallac, Rubicon, Silver | tahoe.print(); // Freel, Rose, Tallac, Rubicon, Silver | ||
</source> | </source> | ||
== Destructing objects == | == Destructing objects == | ||
Line 259: | 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 327: | Line 335: | ||
// } | // } | ||
</source> | </source> | ||
== Asynchronous requests == | == Asynchronous requests == | ||
Line 997: | Line 1,004: | ||
reduceRight simply starts reducing from the end of the array rather than the beginning | 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] |