React: Difference between revisions
functional programming with JavaScript heading |
|||
Line 518: | Line 518: | ||
= Functional Programming with JavaScript = | = 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> |
Revision as of 08:22, 1 October 2019
Notes from Learning React, 2nd Edition by Porcello & Banks
Installation
// initialize a nodejs project (creates package.json)
$ npm init -y
// package manager
$ npm install yarn
$ yarn install packagename
$ yarn remove packagename
JavaScript
Three ways to declare variables are
- const
- var
- let
Template string
console.log(`${lastName}, ${firstName} ${middleName}`);
document.body.innerHTML = `
<section>
<header>
<h1>The React Blog</h1>
</header>
<article>
<h2>${article.title}</h2>
${article.body}
</article>
<footer>
<p>copyright ${new Date().getYear()} | The React Blog</p>
</footer>
</section>
`;
Function declaration vs function expression
declarations are hoisted
const f = function() {
};
Arrow functions
const lordify = function(firstName) {
return `${firstName} of Canterbury`;
};
// equals
const lordify = firstName => `${firstName} of Canterbury`;
Returning an object
DON'T FORGET PARENTHESES!
const person = (firstName, lastName) => ({
first: firstName,
last: lastName
});
console.log(person("Flad", "Hanson"));
Arrow functions and scope
const tahoe = {
mountains: ["Freel", "Rose", "Tallac", "Rubicon", "Silver"],
print: function(delay = 1000) {
setTimeout(function() {
console.log(this.mountains.join(", "));
}, delay);
}
};
tahoe.print(); // Uncaught TypeError: Cannot read property 'join' of undefined
console.log(this); // Window {}
To solve this problem:
const tahoe = {
mountains: ["Freel", "Rose", "Tallac", "Rubicon", "Silver"],
print: function(delay = 1000) {
setTimeout(() => {
console.log(this.mountains.join(", "));
}, delay);
}
};
tahoe.print(); // Freel, Rose, Tallac, Rubicon, Silver
Destructing objects
const sandwich = {
bread: "dutch crunch",
meat: "tuna",
cheese: "swiss",
toppings: ["lettuce", "tomato", "mustard"]
};
const { bread, meat } = sandwich;
console.log(bread, meat); // dutch crunch tuna
const sandwich = {
bread: "dutch crunch",
meat: "tuna",
cheese: "swiss",
toppings: ["lettuce", "tomato", "mustard"]
};
let { bread, meat } = sandwich;
bread = "garlic";
meat = "turkey";
console.log(bread); // garlic
console.log(meat); // turkey
console.log(sandwich.bread, sandwich.meat); // dutch crunch tuna
destructuring parameter
const lordify = ({ firstname }) => {
console.log(`${firstname} of Canterbury`);
};
const regularPerson = {
firstname: "Bill",
lastname: "Wilson"
};
lordify(regularPerson); // Bill of Canterbury
const regularPerson = {
firstname: "Bill",
lastname: "Wilson",
spouse: {
firstname: "Phil",
lastname: "Wilson"
}
};
const lordify = ({ spouse: { firstname } }) => {
console.log(`${firstname} of Canterbury`);
};
lordify(regularPerson); // Phil of Canterbury
Destructuring arrays
const [firstAnimal] = ["Horse", "Mouse", "Cat"];
console.log(firstAnimal); // Horse
const [, , thirdAnimal] = ["Horse", "Mouse", "Cat"];
console.log(thirdAnimal); // Cat
Object literal enhancement
const name = "Tallac";
const elevation = 9738;
const funHike = { name, elevation };
console.log(funHike); // {name: "Tallac", elevation: 9738}
const name = "Tallac";
const elevation = 9738;
const print = function() {
console.log(`Mt. ${this.name} is ${this.elevation} feet tall`);
};
const funHike = { name, elevation, print };
funHike.print(); // Mt. Tallac is 9738 feet tall
old vs. new: object syntax
// Old
var skier = {
name: name,
sound: sound,
powderYell: function() {
var yell = this.sound.toUpperCase();
console.log(`${yell} ${yell} ${yell}!!!`);
},
speed: function(mph) {
this.speed = mph;
console.log("speed:", mph);
}
};
// New
const skier = {
name,
sound,
powderYell() {
let yell = this.sound.toUpperCase();
console.log(`${yell} ${yell} ${yell}!!!`);
},
speed(mph) {
this.speed = mph;
console.log("speed:", mph);
}
};
Spread operator
const peaks = ["Tallac", "Ralston", "Rose"];
const canyons = ["Ward", "Blackwood"];
const tahoe = [...peaks, ...canyons];
console.log(tahoe.join(", ")); // Tallac, Ralston, Rose, Ward, Blackwood
Getting last element:
const peaks = ["Tallac", "Ralston", "Rose"];
const [last] = peaks.reverse();
console.log(last); // Rose
console.log(peaks.join(", ")); // Rose, Ralston, Tallac
Getting the rest of elements:
const lakes = ["Donner", "Marlette", "Fallen Leaf", "Cascade"];
const [first, ...others] = lakes;
console.log(others.join(", ")); // Marlette, Fallen Leaf, Cascade
function parameters:
function directions(...args) {
let [start, ...remaining] = args;
let [finish, ...stops] = remaining.reverse();
console.log(`drive through ${args.length} towns`);
console.log(`start in ${start}`);
console.log(`the destination is ${finish}`);
console.log(`stopping ${stops.length} times in between`);
}
directions("Truckee", "Tahoe City", "Sunnyside", "Homewood", "Tahoma");
combining objects:
const morning = {
breakfast: "oatmeal",
lunch: "peanut butter and jelly"
};
const dinner = "mac and cheese";
const backpackingMeals = {
...morning,
dinner
};
console.log(backpackingMeals);
// {
// breakfast: "oatmeal",
// lunch: "peanut butter and jelly",
// dinner: "mac and cheese"
// }
Asynchronous requests
Simple promises with fetch
fetch("https://api.randomuser.me/?nat=US&results=1")
.then(res => res.json())
.then(json => json.results)
.then(console.log)
.catch(console.error);
Async/Await
const getFakePerson = async () => {
try {
let res = await fetch("https://api.randomuser.me/?nat=US&results=1");
let { results } = res.json();
console.log(results);
} catch (error) {
console.error(error);
}
};
getFakePerson();
Building Promises
const getPeople = count =>
new Promise((resolves, rejects) => {
const api = `https://api.randomuser.me/?nat=US&results=${count}`;
const request = new XMLHttpRequest();
request.open("GET", api);
request.onload = () =>
request.status === 200
? resolves(JSON.parse(request.response).results)
: reject(Error(request.statusText));
request.onerror = err => rejects(err);
request.send();
});
getPeople(5)
.then(members => console.log(members))
.catch(error => console.error(`getPeople failed: ${error.message}`))
);
Classes
prototypical inheritance
function Vacation(destination, length) {
this.destination = destination;
this.length = length;
}
Vacation.prototype.print = function() {
console.log(this.destination + " | " + this.length + " days");
};
const maui = new Vacation("Maui", 7);
maui.print(); // Maui | 7 days
ES2015 way of declaring class
class Vacation {
constructor(destination, length) {
this.destination = destination;
this.length = length;
}
print() {
console.log(`${this.destination} will take ${this.length} days.`);
}
}
const trip = new Vacation("Santiago, Chile", 7);
trip.print(); // Chile will take 7 days.
Simple inheritance
class Expedition extends Vacation {
constructor(destination, length, gear) {
super(destination, length);
this.gear = gear;
}
print() {
super.print();
console.log(`Bring your ${this.gear.join(" and your ")}`);
}
}
const trip = new Expedition("Mt. Whitney", 3, [
"sunglasses",
"prayer flags",
"camera"
]);
trip.print();
// Mt. Whitney will take 3 days.
// Bring your sunglasses and your prayer flags and your camera
ES6 Modules
A module is a peice of reusable code that can easily be incorporated into other JavaScript files without causing variable collisions.
In text-helpers.js, two functions are exported:
export const print(message) => log(message, new Date())
export const log(message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`)
Exporting only one variable from a module using export default
export default new Expedition("Mt. Freel", 2, ['water","snack"]);
Consuming using the import command
import { print, log } from "./text-helpers";
import freel from "./mt-freel";
print("printing a message");
log("logging a message");
freel.print();
scoping under different name:
import { print as p, log as l } from "./text-helpers";
p("printing a message");
l("logging a message");
import everything
import * as fns from './text-helpers`
CommonJS
the module pattern that is supported by all versions of Node, "Modules"
e.g.
const print(message) => log(message, new Date())
const log(message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`}
module.exports = {print, log}
CommonJS does not support an import statement; modules are mported with the require function
const { log, print } = require("./txt-helpers");
Functional Programming with JavaScript
What it means to be functional
JavaScript functions are first-class citizens -- they can do the same things that variables can do. Functions can represent data. e.g.
var log = function(msg) {
console.log(msg);
}
log("In JS functions are variables");
// equivalent to
const log = msg => { console.log(msg); };
we can add them to objects:
const obj = {
msg: "they can be added to objects like variables",
log(msg) { console.log(msg); }
};
obj.log(obj.msg);
add them to arrays:
const messages = [
"They can be inserted into arrays",
message => console.log(message),
"like variables",
message => console.log(message)
];
messages[1](messages[0]); // They can be inserted into arrays
messages[3](messages[2]); // like variables
send to other functions as arguments:
const insideFn = logger => {
logger("They can be sent to other functions as arguments");
};
insideFn(message => console.log(message));
// They can be sent to other functions as arguments
returned from other functions:
const createScream = function(logger) {
return function(message) {
logger(message.toUpperCase() + "!!!");
};
};
const scream = createScream(message => console.log(message));
scream("functions can be returned from other functions");
scream("createScream returns a function");
scream("scream invokes that returned function");
// FUNCTIONS CAN BE RETURNED FROM OTHER FUNCTIONS!!!
// CREATESCREAM RETURNS A FUNCTION!!!
// SCREAM INVOKES THAT RETURNED FUNCTION!!!
// equivalent to (using arrows)
const createScream = logger => message => {
logger(message.toUpperCase() + "!!!");
};
// when more than one arrows exist, there's a higher-order function
</source>
Imperative versus declarative
// making a string URL friendly
// IMPERATIVE way
const string = "Restaurants in Hanalei";
const urlFriendly = "";
for (var i = 0; i < string.length; i++) {
if (string[i] === " ") {
urlFriendly += "-";
} else {
urlFriendly += string[i];
}
}
console.log(urlFriendly); // "Restaurants-in-Hanalei"
// DECLARATIVE way
const string = "Restaurants in Hanalei";
const urlFriendly = string.replace(/ /g, "-");
console.log(urlFriendly);
Reference: Declarative Programming wiki -- more about d/p paradigm
Declaring DOM example
// imperative approach
const target = document.getElementById("target");
const wrapper = document.createElement("div");
const headline = document.createElement("h1");
wrapper.id = "welcome";
headline.innerText = "Hello World";
wrapper.appendChild(headline);
target.appendChild(wrapper);
// declarative approach using a React component
const { render } = ReactDOM;
const Welcome = () => (
<div id="welcome">
<h1>Hello World</h1>
</div>
);
render(<Welcome />, document.getElementById("target"));
Functional concepts
core concepts: immutability, purity, data transformation, higher-order functions, and recursion
Immutability
data mutation
let color_lawn = {
title: "lawn",
color: "#00ff00",
rating: 0
};
function rateColor(color, rating) {
color.rating = rating;
return color;
}
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 5
in JS, function arguments are references to the actual data; we can rewrite so it does not harm the original data
const rateColor = function(color, rating) {
return Object.assign({}, color, { rating: rating }); // take a blank object, copy copy to that object, and overwrite rating on the copy
};
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 4
// use the spread operator to copy the color into a new object and then overwrite its rating:
// equivalent to
const rateColor = (color, rating) => ({
...color,
rating
});
adding elements to an array
let list = [{ title: "Rad Red" }, { title: "Lawn" }, { title: "Party Pink" }];
const addColor = function(title, colors) {
colors.push({ title: title });
return colors;
};
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 4
// use Array.concat to keep immutable
const addColor = (title, array) => array.concat({ title });
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 3
// equivalent using spread operator
// copies the original list to a new array and then adds a new object containing the color's title to that copy.
const addColor = (title, list) => [...list, { title }];
Pure Functions
function that returns a value that is computed based on its argument; treats arguments as immutable data, so nothing else is changed about the application
e.g. impure function
const frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
function selfEducate() {
frederick.canRead = true;
frederick.canWrite = true;
return frederick;
}
selfEducate();
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// still the same result
const selfEducate = person => {
person.canRead = true;
person.canWrite = true;
return person;
};
console.log(selfEducate(frederick));
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// {name: "Frederick Douglass", canRead: true, canWrite: true}
have this function take an argument (now pure)
const frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
const selfEducate = person => ({
...person,
canRead: true,
canWrite: true
});
console.log(selfEducate(frederick));
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// {name: "Frederick Douglass", canRead: false, canWrite: false}