React is vandaag de dag een van de populairste JavaScript bibliotheken ter wereld. Het kan worden gebruikt om dynamische en responsieve applicaties te maken, maakt betere prestaties mogelijk, en kan gemakkelijk worden uitgebreid. De onderliggende logica is gebaseerd op componenten die in verschillende contexten kunnen worden hergebruikt, waardoor je minder vaak dezelfde code hoeft te schrijven. Met React kun je dus efficiënte en krachtige applicaties maken.

Er is dus nooit een beter moment geweest om applicaties te leren maken met React dan nu.

Maar zonder een goed begrip van enkele belangrijke JavaScript features kan het bouwen van React applicaties lastig of zelfs onmogelijk zijn.

Daarom hebben we een lijst samengesteld van JavaScript eigenschappen en concepten die je moet kennen voordat je aan de slag gaat met React. Hoe beter je deze concepten begrijpt, hoe gemakkelijker het voor je zal zijn om professionele React applicaties te bouwen.

JavaScript en ECMAScript

JavaScript is een populaire scripttaal die samen met HTML en CSS wordt gebruikt om dynamische webpagina’s te maken. Waar HTML vooral wordt gebruikt om de structuur van een webpagina te maken en CSS om de stijl en opmaak van de elementen te maken, is JavaScript de taal die wordt gebruikt om gedrag aan de pagina toe te voegen, dus om functionaliteit en interactiviteit te creëren.

De taal is sindsdien overgenomen door de grote browsers en er is een document geschreven om te beschrijven hoe JavaScript zou moeten werken: de ECMAScript standaard.

Sinds 2015 wordt jaarlijks een update van de ECMAScript standaard uitgebracht, en dus worden er elk jaar nieuwe mogelijkheden aan JavaScript toegevoegd.

ECMAScript 2015 was de zesde release van de standaard en wordt daarom ook wel ES6 genoemd. De versies erna krijgen allemaal een hoger nummer, dus verwijzen we naar ECMAScript 2016 als ES7, ECMAScript 2017 als ES8, enzovoort.

Door de frequentie waarmee nieuwe features aan de standaard worden toegevoegd, is het mogelijk dat sommige niet in alle browsers worden ondersteund. Dus hoe kan je ervoor zorgen dat de nieuwste JavaScript features die je aan je JS app hebt toegevoegd in alle webbrowsers naar verwachting zouden werken?

Je hebt drie opties:

  1. Wacht tot alle grote browsers ondersteuning bieden voor de nieuwe features. Maar als je die geweldige nieuwe JS feature absoluut nodig hebt voor je app, is dit geen optie.
  2. Gebruik een Polyfill, dat is “een stukje code (meestal JavaScript op het web) dat gebruikt wordt om moderne functionaliteit te bieden op oudere browsers die deze niet van nature ondersteunen” (zie ook mdn web docs).
  3. Gebruik een JavaScript transpiler zoals Babel of Traceur, die ECMAScript 2015+ code omzetten in een JavaScript versie die door alle browsers wordt ondersteund.

Statements vs expressions

Het begrijpen van het verschil tussen statements en expressions is essentieel bij het bouwen van React applicaties. Laten we daarom even teruggaan naar de basisconcepten van programmeren.

Een computerprogramma is een lijst van instructies die door een computer moeten worden uitgevoerd. Deze instructies worden statements genoemd.

In tegenstelling tot statements zijn expressions stukjes code die een waarde opleveren. In een statement is een expression een deel dat een waarde teruggeeft en we zien het meestal rechts van een gelijkheidsteken.

En:

JavaScript statements kunnen blokken of regels code zijn die meestal eindigen met puntkomma’s of omsloten zijn door accolades.

Hier is een eenvoudig voorbeeld van een statement in JavaScript:

document.getElementById("hello").innerHTML = "Hello World!";

Het bovenstaande statement schrijft "Hello World!" in een DOM element met id="hello".

Zoals we al vermeldden, produceren expressions een waarde of zijn ze zelf een waarde. Kijk eens naar het volgende voorbeeld:

msg = document.getElementById("hello").value;

document.getElementById("hello").value is een expression die een waarde oplevert.

Een extra voorbeeld moet helpen het verschil tussen expressions en statements te verduidelijken:

const msg = "Hello World!";
function sayHello( msg ) {
	console.log( msg );
}

In het bovenstaande voorbeeld

  • is de eerste regel een statement, waarbij "Hello World!" een expression is,
  • is de functiedeclaration een statement, waarbij de parameter msg die aan de functie wordt doorgegeven een expression is,
  • is de regel die het bericht in de console print een statement, waarbij wederom de parameter msg een expression is.

Waarom expressions belangrijk zijn in React

Bij het bouwen van een React applicatie kun je JavaScript expressions injecteren in je JSX code. Je kunt bijvoorbeeld een variabele doorgeven, een event handler of een voorwaarde schrijven. Daarvoor moet je je JS code tussen accolades zetten.

Je kunt bijvoorbeeld een variabele doorgeven:

const Message = () => {
	const name = "Carlo";
	return <p>Welcome {name}!</p>;
}

De accolades vertellen je transpiler dat hij de code tussen accolades moet verwerken als JS code. Alles wat voor de openende <p> tag en na de sluitende </p> tag komt is normale JavaScript code. Alles binnen de openende <p> en sluitende </p> tags wordt verwerkt als JSX code.

Hier is nog een voorbeeld:

const Message = () => {	
	const name = "Ann";
	const heading = <h3>Welcome {name}</h3>;
	return (
		<div>
			{heading}
			<p>This is your dashboard.</p>
		</div>
	);
}

Je kunt ook een object doorgeven:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22',
		description: 'Content Writer'
	}

	return (
		<div>
			<h2>Welcome {person.name}</h2>
			<img
				className="card"
				src={person.avatar}
				alt={person.name}
			/>
			<p>Description: {person.description}.</p>
		</div>
	);
}

En hieronder staat een uitgebreider voorbeeld:

render(){
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: {
			boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', width: '200px'
		}
	}

	return (
		<div style={person.theme}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

Let op de dubbele accolades in de style attributen in de elementen img en div. We hebben dubbele haakjes gebruikt om twee objecten door te geven die kaart- en beeldstijlen bevatten.

Een voorbeeldkaart gebouwd met React
Een voorbeeldkaart gebouwd met React

Het moet je misschien wel zijn opgevallen dat we in alle bovenstaande voorbeelden JavaScript expressions hebben opgenomen in JSX.

Immunitability in React

Mutability en immutability zijn twee sleutelbegrippen in objectgeoriënteerd en functioneel programmeren.

Immutability betekent dat een waarde niet kan worden veranderd nadat hij is gecreëerd. Mutability betekent natuurlijk het tegenovergestelde.

In Javascript zijn primitive waarden immutable, wat betekent dat als een primitive waarde eenmaal gemaakt is, hij niet meer veranderd kan worden. Andersom zijn arrays en objecten mutable, omdat hun properties en elementen kunnen worden veranderd zonder een nieuwe waarde toe te kennen.

Er zijn verschillende redenen voor het gebruik van immutable objecten in JavaScript:

  • Verbeterde prestaties
  • Minder geheugengebruik
  • Veiligheid van threads
  • Eenvoudiger coderen en debuggen

Volgens het patroon van immutability kan een variabele of object dat eenmaal is toegekend, niet opnieuw worden toegekend of gewijzigd. Als je gegevens moet wijzigen, moet je er een kopie van maken en de inhoud ervan wijzigen, terwijl je de oorspronkelijke inhoud ongewijzigd laat.

Immutability is ook een sleutelbegrip in React.

De React documentatie stelt:

De state van een klassecomponent is beschikbaar als this.state. Het state veld moet een object zijn. Verander de state niet rechtstreeks. Als je de state wilt veranderen, call dan setState met de nieuwe state.

Telkens als de state van een component verandert, berekent React of de component opnieuw moet worden weergegeven en de Virtuele DOM moet worden bijgewerkt. Als React de vorige state niet bijhield, zou het niet kunnen bepalen of de component opnieuw moet worden weergegeven of niet. De React documentatie geeft hiervan een uitstekend voorbeeld.

Welke JavaScript properties kunnen we gebruiken om de immutability van het state object in React te garanderen? Laten we het uitzoeken!

Variabelen declaren

Je hebt drie manieren om een variabele te declaren in JavaScript: var, let, en const.

Het var statement bestaat al sinds het begin van JavaScript. Het wordt gebruikt om een functie-scoped of globaal-scoped variabele te declaren, en optioneel te initialiseren naar een waarde.

Wanneer je een variabele declaret met var, kun je die variabele opnieuw declaren en bijwerken in zowel het globale als het lokale bereik. De volgende code is toegestaan:

// Declare a variable
var msg = "Hello!";

// Redeclare the same variable
var msg = "Goodbye!"

// Update the variable
msg = "Hello again!"

var declarations worden verwerkt voordat enige code wordt uitgevoerd. Daardoor staat het declaren van een variabele waar dan ook in de code gelijk aan het declaren bovenaan. Dit gedrag heet hoisting.

Opmerkelijk is dat alleen de declaration van de variabele wordt gehoist, niet de initialisatie, wat pas gebeurt als de controlflow het assignment statement bereikt. Tot dat moment is de variabele undefined:

console.log(msg); // undefined
var msg = "Hello!";
console.log(msg); // Hello!

De scope van een var wat wordt gedeclared in een JS functie is de hele body van die functie.

Dit betekent dat de variabele niet op blokniveau wordt gedefinieerd, maar op het niveau van de hele functie. Dit leidt tot een aantal problemen die je JavaScript code buggy en moeilijk te onderhouden kunnen maken.

Om deze problemen op te lossen introduceerde ES6 het keyword let.

Het let statement declaret een block-scoped lokale variabele en initialiseert die optioneel op een waarde.

Wat zijn de voordelen van let boven var? Hier zijn er enkele:

  • let declaret een variabele voor de scope van een blokstatement, terwijl var een variabele globaal of lokaal declaret voor een hele functie, ongeacht de scope van het blok.
  • Globale let variabelen zijn geen properties van het window object. Je kunt ze niet gebruiken met window.variableName.
  • let is alleen toegankelijk nadat de declaration ervan is bereikt. De variabele wordt niet geïnitialiseerd totdat de controlflow de regel code bereikt waar hij is gedeclared (let declarations zijn non-hoisted).
  • Het opnieuw declaren van een variabele met let geeft een SyntaxError.

Omdat variabelen gedeclared met var niet block-scoped kunnen worden, kan een variabele die je definieert met var in een lus of in een if statement, van buiten het blok benaderd worden, wat kan leiden tot buggy code.

De code in het eerste voorbeeld wordt zonder fouten uitgevoerd. Vervang nu var door let in het blok code dat je hierboven ziet:

console.log(msg);
let msg = "Hello!";
console.log(msg);

In het tweede voorbeeld levert het gebruik van let in plaats van var een Uncaught ReferenceError op:

Uncaught ReferenceError in Chrome
Uncaught ReferenceError in Chrome

ES6 introduceert ook een derde keyword: const.

const is redelijk vergelijkbaar met let, maar met een belangrijk verschil:

Kijk eens naar het volgende voorbeeld:

const MAX_VALUE = 1000;
MAX_VALUE = 2000;

Bovenstaande code zou de volgende TypeError genereren:

Uncaught TypeError: Toewijzing aan constante variabele in Google Chrome
Uncaught TypeError: Toewijzing aan constante variabele in Google Chrome

Bovendien:

Een const declaren zonder het een waarde te geven zou het volgende opleveren SyntaxError (zie ook ES6 In Depth: let en const):

Uncaught SyntaxError: Missing initializer in const declaration in Chrome
Uncaught SyntaxError: Ontbrekende initialisator in const declaration in Chrome

Maar als een constante een array of een object is, kun je properties of items binnen die array of dat object wijzigen.

Je kunt bijvoorbeeld array-items wijzigen, toevoegen en verwijderen:

// Declare a constant array
const cities = ["London", "New York", "Sydney"];

// Change an item
cities[0] = "Madrid";

// Add an item
cities.push("Paris");

// Remove an item
cities.pop();

console.log(cities);

// Array(3)
// 0: "Madrid"
// 1: "New York"
// 2: "Sydney"

Maar je mag de array niet opnieuw toewijzen (assignen):

const cities = ["London", "New York", "Sydney"];

cities = ["Athens", "Barcelona", "Naples"];

Bovenstaande code zou een TypeError opleveren.

Uncaught TypeError: Assignment to constant variable.
Uncaught TypeError: Assignment to constant variable in Chrome

Je kunt objectproperties en methoden toevoegen, opnieuw toewijzen en verwijderen:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language',
	content: 'JavaScript is a scripting language that enables you to create dynamically updating content.'
};

// add a new property
post.slug = "javascript-is-awesome";

// Reassign property
post.id = 5;

// Delete a property
delete post.excerpt;

console.log(post);

// {id: 5, name: 'JavaScript is awesome', content: 'JavaScript is a scripting language that enables you to create dynamically updating content.', slug: 'javascript-is-awesome'}

Maar je mag het object zelf niet opnieuw toewijzen. De volgende code zou door een Uncaught TypeError:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};

post = {
	id: 1,
	name: 'React is powerful',
	excerpt: 'React lets you build user interfaces'
};

Object.freeze()

We zijn het er nu over eens dat het gebruik van const niet altijd sterke immutability garandeert (vooral bij het werken met objecten en arrays). Hoe kun je dan het immutabilitypatroon implementeren in je React applicaties?

Ten eerste, wanneer je wilt voorkomen dat de elementen van een array of properties van een object worden gewijzigd, kun je de statische methode Object.freeze() gebruiken.

Het freezen van een object voorkomt uitbreidingen en maakt bestaande properties niet-schrijfbaar en niet-configureerbaar. Een frozen object kan niet meer worden gewijzigd: er kunnen geen nieuwe properties worden toegevoegd, bestaande properties kunnen niet worden verwijderd, hun enumerability, configureerbaarheid, beschrijfbaarheid of waarde kan niet worden gewijzigd, en het prototype van het object kan niet opnieuw worden toegekend. freeze() retourneert hetzelfde object dat werd meegegeven.

Elke poging om een property toe te voegen, te wijzigen of te verwijderen zal mislukken, hetzij stilletjes, hetzij door het geven van een TypeError, meestal in strict mode.

Je kunt Object.freeze() op deze manier gebruiken:

'use strict'
// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};
// Freeze the object
Object.freeze(post);

Als je nu probeert een property toe te voegen, krijg je een Uncaught TypeError:

// Add a new property
post.slug = "javascript-is-awesome"; // Uncaught TypeError
Uncaught TypeError: can't define property
Uncaught TypeError: can’t define property “slug”: Object is not extensible in Firefox

Als je een property opnieuw probeert toe te wijzen, krijg je een ander soort TypeError:

// Reassign property
post.id = 5; // Uncaught TypeError
Het opnieuw toewijzen van een alleen-lezen property gooit een Uncaught TypeError
Het opnieuw toewijzen van een alleen-lezen property geeft een Uncaught TypeError
Uncaught TypeError: Cannot assign to read only property in Chrome
Uncaught TypeError: Cannot assign to read only property in Chrome ‘id’ of object ‘#<Object>’ in Google Chrome

Je kunt ook proberen een property te verwijderen. Het resultaat zal een andere TypeError zijn:

// Delete a property
delete post.excerpt; // Uncaught TypeError
Uncaught TypeError: property
Uncaught TypeError: property “excerpt” is non-configurable and can’t be deleted in Firefox

Template literals

Wanneer je in JavaScript strings moet combineren met de uitvoer van expressions, gebruik je meestal de opteloperator +. Je kunt echter ook een JavaScript feature gebruiken waarmee je expressions binnen strings kunt opnemen zonder de opteloperator te gebruiken: template literals.

Template literals zijn een speciaal soort strings die begrensd worden door backtick (`) tekens.

Binnen template literals kun je placeholders opnemen, die ingesloten expressions zijn, begrensd door een dollarteken en tussen accolades gewikkeld.

Hier is een voorbeeld:

const align = 'left';
console.log(`This string is ${ align }-aligned`);

De strings en placeholders worden doorgegeven aan een standaardfunctie die stringinterpolatie uitvoert om de placeholders te vervangen en de delen samen te voegen tot een enkele string. Je kunt de standaardfunctie ook vervangen door een eigen functie.

Je kunt template literals gebruiken voor:

Meerregelige strings: newline-tekens zijn hierbij onderdeel van de template literal.

console.log(`Twinkle, twinkle, little bat!
How I wonder what you’re at!`);

Interpolatie van strings: Zonder template literals kun je alleen de opteloperator gebruiken om de uitvoer van expressions te combineren met strings. Bekijk het volgende voorbeeld:

const a = 3;
const b = 7;
console.log("The result of " + a + " + " + b + " is " + (a + b));

Ik snap dat dit een beetje verwarrend is. Je kunt deze code op een beter leesbare en onderhoudbare manier schrijven met behulp van template literals:

const a = 3;
const b = 7;
console.log(`The result of ${ a } + ${ b } is ${ a + b }`);

Maar bedenk wel dat er een verschil is tussen de twee syntaxen:

Template literals lenen zich voor verschillende toepassingen. In het volgende voorbeeld gebruiken we een ternary operator om een waarde toe te kennen aan een class attribuut.

const page = 'archive';
console.log(`class=${ page === 'archive' ? 'archive' : 'single' }`);

Hieronder voeren we een eenvoudige berekening uit:

const price = 100;
const VAT = 0.22;

console.log(`Total price: ${ (price * (1 + VAT)).toFixed(2) }`);

Het is ook mogelijk template literals te nesten door ze op te nemen binnen een ${expression} placeholder (maar gebruik nested templates met voorzichtigheid, omdat complexe stringstructuren moeilijk te lezen en te onderhouden kunnen zijn).

Tagged templates: Zoals we hierboven al vermeldden, is het ook mogelijk om een custom functie te definiëren om string concatenation uit te voeren. Dit soort template literal heet tagged template.

Met tags kun je template literals parsen met een functie. Het eerste argument van een tag-functie bevat een array van string-waarden. De overige argumenten hebben betrekking op de expressions.

Met tags kun je template literals parsen met een custom functie. Het eerste argument van deze functie is een array van de string die in de template literal grep zijn opgenomen, de andere argumenten zijn de expressions.

Je kunt een custom functie maken om elke gewenste bewerking op de template argumenten uit te voeren en de bewerkte string terug te geven. Hier is een heel eenvoudig voorbeeld van een tagged template:

const name = "Carlo";
const role = "student";
const organization = "North Pole University";
const age = 25;

function customFunc(strings, ...tags) {
	console.log(strings); // ['My name is ', ", I'm ", ', and I am ', ' at ', '', raw: Array(5)]
	console.log(tags); // ['Carlo', 25, 'student', 'North Pole University']
	let string = '';
	for ( let i = 0; i < strings.length - 1; i++ ){
		console.log(i + "" + strings[i] + "" + tags[i]);
		string += strings[i] + tags[i];
	}
	return string.toUpperCase();
}

const output = customFunc`My name is ${name}, I'm ${age}, and I am ${role} at ${organization}`;
console.log(output);

De code hierboven drukt de array-elementen strings en tags af en zet dan de tekenreeksen in hoofdletters voordat de uitvoer in de browserconsole wordt geprint.

Arrow functies

Arrow zijn een alternatief voor anonieme functies (functies zonder naam) in JavaScript, maar met enkele verschillen en beperkingen.

De volgende declarations zijn allemaal geldige voorbeelden van arrow functies:

// Arrow function without parameters
const myFunction = () => expression;

// Arrow function with one parameter
const myFunction = param => expression;

// Arrow function with one parameter
const myFunction = (param) => expression;

// Arrow function with more parameters
const myFunction = (param1, param2) => expression;

// Arrow function without parameters
const myFunction = () => {
	statements
}

// Arrow function with one parameter
const myFunction = param => {
	statements
}

// Arrow function with more parameters
const myFunction = (param1, param2) => {
	statements
}

Je mag de ronde haakjes weglaten als je maar één parameter aan de functie doorgeeft. Als je twee of meer parameters doorgeeft, moet je ze tussen haakjes zetten. Hier volgt een voorbeeld:

const render = ( id, title, category ) => `${id}: ${title} - ${category}`;
console.log( render ( 5, 'Hello World!', "JavaScript" ) );

Functies met één regel geven standaard een waarde terug. Als je de syntaxis van meerdere regels gebruikt, moet je handmatig een waarde teruggeven:

const render = ( id, title, category ) => {
	console.log( `Post title: ${ title }` );
	return `${ id }: ${ title } - ${ category }`;
}
console.log( `Post details: ${ render ( 5, 'Hello World!', "JavaScript" ) }` );

Een belangrijk verschil tussen normale functies en arrow functies om in gedachten te houden is dat arrow functies geen eigen bindingen hebben met het keyword this. Als je this probeert te gebruiken in een arrow functie, zal het buiten het bereik van de functie vallen.

Lees voor een meer diepgaande beschrijving van arrow functies en gebruiksvoorbeelden ook de mdn web docs.

Klassen

Klassen in JavaScript zijn een speciaal type functie voor het maken van objecten die het prototypische inheritance mechanisme gebruiken.

Volgens mdn web docs,

Wat betreft inheritance kent JavaScript maar één constructie: objecten. Elk object heeft een private property eigenschap die een link bevat naar een ander object dat zijn prototype wordt genoemd. Dat prototype object heeft een eigen prototype, enzovoort totdat een object wordt bereikt met null als prototype.

Net als bij functies heb je twee manieren om een klasse te definiëren:

  • Een klasse-expression
  • Een klasse-declaration

Je kunt het keyword class gebruiken om een klasse te definiëren binnen een expression, zoals in het volgende voorbeeld:

const Circle = class {
	constructor(radius) {
		this.radius = Number(radius);
	}
	area() {
		return Math.PI * Math.pow(this.radius, 2);
	}
	circumference() {
		return Math.PI * this.radius * 2;
	}
}
console.log('Circumference: ' + new Circle(10).circumference()); // 62.83185307179586
console.log('Area: ' + new Circle(10).area()); // 314.1592653589793

Een klasse heeft een body, dat is de code die in krulhaken is opgenomen. Hier definieer je constructor en methoden, die ook wel class members worden genoemd. De body van de klasse wordt uitgevoerd in strict mode, zelfs zonder gebruik te maken van de directive 'strict mode'.

De methode constructor wordt gebruikt voor het maken en initialiseren van een object van een klasse en wordt automatisch uitgevoerd als de klasse wordt geïnstantieerd. Als je geen constructormethode definieert in je klasse, zal JavaScript automatisch een standaard constructor gebruiken.

Een klasse kan worden uitgebreid met het keyword extends.

class Book {
	constructor(title, author) {
		this.booktitle = title;
		this.authorname = author;
	}
	present() {
		return this.booktitle + ' is a great book from ' + this.authorname;
	}
}

class BookDetails extends Book {
	constructor(title, author, cat) {
		super(title, author);
		this.category = cat;
	}
	show() {
		return this.present() + ', it is a ' + this.category + ' book';
	}
}

const bookInfo = new BookDetails("The Fellowship of the Ring", "J. R. R. Tolkien", "Fantasy");
console.log(bookInfo.show());

Een constructor kan het keyword super gebruiken om de bovenliggende constructor te callen. Als je een argument doorgeeft aan de methode super(), zal dit argument ook beschikbaar zijn in de parent constructorklasse.

Voor een uitgebreidere bespreking van JavaScript klassen en diverse voorbeelden van gebruik, zie ook de mdn web docs.

Klassen worden vaak gebruikt om React componenten te maken. Meestal zul je geen eigen klassen maken, maar eerder ingebouwde React klassen uitbreiden.

Alle klassen in React hebben eenrender() methode die een React element teruggeeft:

class Animal extends React.Component {
	render() {
		return <h2>Hey, I am a {this.props.name}!</h2>;
	}
}

In het bovenstaande voorbeeld is Animal een klasse-element. Bedenk dat

  • De naam van de component moet beginnen met een hoofdletter
  • De component moet de expression extends React.Component bevatten. Dit geeft toegang tot de methoden van de React.Component.
  • De methode render() geeft de HTML terug en is vereist.

Als je eenmaal je klassecomponent hebt gemaakt, kun je de HTML op de pagina renderen:

const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Animal name="Rabbit" />;
root.render(element);

De afbeelding hieronder toont het resultaat op de pagina (Je kunt het in actie zien op CodePen).

A simple React class component
Een eenvoudige React klassecomponent

Let wel op dat het gebruik van klassecomponenten in React niet wordt aanbevolen en dat het de voorkeur verdient om componenten als functies te definiëren.

Het keyword ’this’

In JavaScript is het keyword this een algemene placeholder die gewoonlijk wordt gebruikt binnen objecten, klassen en functies, en het verwijst naar verschillende elementen, afhankelijk van de context of het applicatiegebied.

this kan worden gebruikt in de globale scope. Als je this digitaliseert in de console van je browser, krijg je:

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Je hebt toegang tot alle methoden en properties van het object Window. Als je dus this.location uitvoert in de console van je browser, krijg je de volgende output:

Location {ancestorOrigins: DOMStringList, href: 'https://kinsta.com/', origin: 'https://kinsta.com', protocol: 'https:', host: 'kinsta.com', ...}

Wanneer je this in een object gebruikt, verwijst het naar het object zelf. Zo kun je naar de waarden van een object verwijzen in de methoden van het object zelf:

const post = { 
	id: 5,
	getSlug: function(){
		return `post-${this.id}`;
	},
	title: 'Awesome post', 
	category: 'JavaScript' 
};
console.log( post.getSlug );

Laten we nu proberen this te gebruiken in een functie:

const useThis = function () {
	return this;
}
console.log( useThis() );

Als je niet in de strict mode zit, krijg je:

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Maar als je de strict mode callt, krijg je een ander resultaat:

const doSomething = function () {
	'use strict';
	return this;
}
console.log( doSomething() );

In dit geval geeft de functie undefined terug. Dat komt omdat this in een functie verwijst naar de expliciete waarde ervan.

Dus hoe stel je expliciet this in een functie in?

Ten eerste kun je handmatig properties en methoden aan de functie toekennen:

function doSomething( post ) {
	this.id = post.id;
	this.title = post.title;
	console.log( `${this.id} - ${this.title}` );
}
new doSomething( { id: 5, title: 'Awesome post' } );

Maar je kunt ook methoden call, apply, en bind gebruiken, naast arrow functies.

const doSomething = function() {
	console.log( `${this.id} - ${this.title}` );
}
doSomething.call( { id: 5, title: 'Awesome post' } );

De methode call() kan op elke functie worden gebruikt en doet precies wat het zegt: het callt de functie.

Verder accepteert call() elke andere parameter die in de functie is gedefinieerd:

const doSomething = function( cat ) {
	console.log( `${this.id} - ${this.title} - Category: ${cat}` );
}
doSomething.call( { id: 5, title: 'Awesome post' }, 'JavaScript' );
const doSomething = function( cat1, cat2 ) {
	console.log( `${this.id} - ${this.title} - Categories: ${cat1}, ${cat2}` );
}
doSomething.apply( { id: 5, title: 'Awesome post' }, ['JavaScript', 'React'] );
const post = { id: 5, title: 'Awesome post', category: 'JavaScript' };
const doSomething = function() {
	return `${this.id} - ${this.title} - ${this.category}`;
}
const bindRender = doSomething.bind( post );
console.log( bindRender() );

Een alternatief voor de hierboven besproken opties is het gebruik van arrow functies.

Arrow functie-expressions moeten alleen gebruikt worden voor niet-methodische functies, omdat ze geen eigen this hebben.

Dit maakt arrow functies bijzonder nuttig bij event handlers.

Dat komt omdat “wanneer de code wordt gecalld vanuit een inline event handler attribuut, zijn this wordt ingesteld op het DOM element waarop de listener is geplaatst” (zie mdn web docs).

Maar dingen veranderen met arrow functies omdat…

… arrow functies stellen this vast op basis van het bereik waarbinnen de arrow functie is gedefinieerd, en de this waarde verandert niet op basis van hoe de functie wordt gecalled.

‘This’ binden aan event handlers in React

Bij React heb je een paar manieren om ervoor te zorgen dat de event handler zijn context niet verliest:

1. Met behulp van bind() binnen de render methode:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage.bind( this ) }>Show message from state!</button> );
	}
}
export default MyComponent;

2. De context binden aan de event handler in de constructor:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	constructor(props) {
		super(props);
		this.showMessage = this.showMessage.bind( this );
	}

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage }>Show message from state!</button> );
	}
}
export default MyComponent;

3. De event handler definiëren met behulp van arrow functies:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage = () => {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={this.showMessage}>Show message from state!</button> );
	}
}
export default MyComponent;

4. Arrow functies gebruiken in de rendermethode:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage() {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={()=>{this.showMessage()}}>Show message from state!</button> );
	}
}
export default MyComponent;

Welke methode je ook kiest, als je op de knop klikt, toont de browserconsole de volgende uitvoer:

This refers to:  MyComponent {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
The message is:  Hello World!

Ternary operator

Met de conditional operator (of ternary operator) kun je eenvoudige conditional (voorwaardelijke) expressions schrijven in JavaScript. Hij gebruikt drie operands:

  • een voorwaarde gevolgd door een vraagteken (?),
  • een expression om uit te voeren als de voorwaarde waar is, gevolgd door een dubbele punt (:),
  • een tweede expression om uit te voeren als de voorwaarde fout is.
const drink = personAge >= 18 ? "Wine" : "Juice";

Het is ook mogelijk om meerdere expressions te chainen:

const drink = personAge >= 18 ? "Wine" : personAge >= 6 ? "Juice" : "Milk";

Wees echter voorzichtig, want het chainen van meerdere expressions kan leiden tot rommelige code die moeilijk te onderhouden is.

De ternary operator is bijzonder nuttig in React, vooral in je JSX code, die alleen expressions tussen accolades accepteert.

Je kunt de ternary operator bijvoorbeeld gebruiken om de waarde van een attribuut in te stellen op basis van een specifieke voorwaarde:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/...',
		description: 'Content Writer',
		theme: 'light'
	}

	return (
		<div
			className='card' 
			style={
				person.theme === 'dark' ? 
				{ background: 'black', color: 'white' } : 
				{ background: 'white', color: 'black'} 
			}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

In bovenstaande code controleren we de voorwaarde person.theme === 'dark' om de waarde van het attribuut style van de container div in te stellen.

Short circuit evaluation

De logische AND (&&) operator evalueert operands van links naar rechts en geeft true terug als – en alleen als – alle operands true zijn.

De logische AND is een short circuit operator. Elke operand wordt omgezet in een boolean en als het resultaat van de omzetting false is, stopt de AND operator en geeft de oorspronkelijke waarde van de falsy operand terug. Als alle waarden true zijn, wordt de oorspronkelijke waarde van de laatste operand teruggegeven.



Short circuit evaluation is een JavaScript functie die vaak wordt gebruikt in React, omdat je hiermee blokken code kunt uitvoeren op basis van specifieke voorwaarden. Hier volgt een voorbeeld:

{
	displayExcerpt &&
	post.excerpt.rendered && (
		<p>
			<RawHTML>
				{ post.excerpt.rendered }
			</RawHTML>
		</p>
	)
}

In bovenstaande code, als displayExcerpt EN post.excerpt.rendered evalueren naar true, geeft React het laatste blok JSX terug.

Samengevat: “als de voorwaarde true is, verschijnt het element direct na && in de output. Als het false is, zal React het negeren en overslaan”.

Spread syntax

In JavaScript kun je met spread syntax een iterabel element, zoals een array of object, uitbreiden tot functie-argumenten, array-literals of object-literals.

In het volgende voorbeeld pakken we een array uit in een functiecall:

function doSomething( x, y, z ){
	return `First: ${x} - Second: ${y} - Third: ${z} - Sum: ${x+y+z}`;
}
const numbers = [3, 4, 7];
console.log( doSomething( ...numbers ) );

Je kunt de spread syntax gebruiken om een array te dupliceren (zelfs multidimensionale arrays) of om arrays aan elkaar te chainen. In de volgende voorbeelden voegen we twee arrays op twee verschillende manieren samen:

const firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray.push( ...secondArray );
console.log( firstArray );

Als alternatief:

let firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray = [ ...firstArray, ...secondArray];
console.log( firstArray );

Je kunt ook de spread syntax gebruiken om twee objecten te klonen of samen te voegen:

const firstObj = { id: '1', title: 'JS is awesome' };
const secondObj = { cat: 'React', description: 'React is easy' };

// clone object
const thirdObj = { ...firstObj };

// merge objects
const fourthObj = { ...firstObj, ...secondObj }

console.log( { ...thirdObj } );
console.log( { ...fourthObj } );

Destructuring assignment

Een andere syntactische structuur die je vaak in React gebruikt zult vinden is de destructuring assignment syntax.

In het volgende voorbeeld pakken we waarden uit een array uit:

const user = ['Carlo', 'Content writer', 'Kinsta'];
const [name, description, company] = user;
console.log( `${name} is ${description} at ${company}` );

En hier is een eenvoudig voorbeeld van destructuring assignment met een object:

const user = {
	name: 'Carlo',
	description: 'Content writer',
	company: 'Kinsta'
}
const { name, description, company } = user;
console.log( `${name} is ${description} at ${company}` );

Maar we kunnen nog meer doen. In het volgende voorbeeld pakken we enkele properties van een object uit en wijzen we de resterende properties toe aan een ander object met behulp van de spread syntaxis:

const user = {
	name: 'Carlo',
	family: 'Daniele',
	description: 'Content writer',
	company: 'Kinsta',
	power: 'swimming'
}
const { name, description, company, ...rest } = user;
console.log( rest ); // {family: 'Daniele', power: 'swimming'}

Je kunt ook waarden toewijzen aan een array:

const user = [];
const object = { name: 'Carlo', company: 'Kinsta' };
( { name: user[0], company: user[1] } = object );
console.log( user ); // (2) ['Carlo', 'Kinsta']

Let op dat de haakjes rond de assignment statement nodig zijn bij het gebruik van object-literal destructuring assignments zonder declaration.

Voor een meer diepgaande analyse van destructuring assignment, met diverse gebruiksvoorbeelden, kun je de mdn web docs raadplegen.

filter(), map() en reduce()

JavaScript biedt verschillende nuttige methoden die je vaak gebruikt in React.

filter()

In het volgende voorbeeld passen we het filter toe op de array numbers om een array te krijgen waarvan de elementen getallen groter dan 5 zijn:

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.filter( number => number > 5);
console.log(result); // (4) [6, 8, 9, 23]

In het volgende voorbeeld krijgen we een array van berichten met het woord ‘JavaScript’ in de titel:

const posts = [
	{id: 0, title: 'JavaScript is awesome', content: 'your content'},
	{id: 1, title: 'WordPress is easy', content: 'your content'},
	{id: 2, title: 'React is cool', content: 'your content'},
	{id: 3, title: 'With JavaScript to the moon', content: 'your content'},
];

const jsPosts = posts.filter( post => post.title.includes( 'JavaScript' ) );

console.log( jsPosts );
Een array van berichten waarin de titel 'JavaScript' bevat
Een array van berichten waarin de titel ‘JavaScript’ bevat

map()

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.map( number => number * 5 );
console.log(result); // (7) [10, 30, 40, 10, 25, 45, 115]

In een React component vind je de methode map() vaak gebruikt om lijsten op te bouwen. In het volgende voorbeeld mappen we het WordPress posts object om een lijst van berichten op te bouwen:

<ul>
	{ posts && posts.map( ( post ) => {
		return (
			<li key={ post.id }>
				<h5>
					<a href={ post.link }>
						{ 
							post.title.rendered ? 
							post.title.rendered :
							__( 'Default title', 'author-plugin' )
						}
					</a>
				</h5>
			</li>
		)
	})}
</ul>

reduce()

reduce() accepteert twee parameters:

  • Een callbackfunctie om uit te voeren voor elk element in de array. Deze geeft een waarde terug die bij de volgende call de waarde wordt van de accumulator-parameter. Bij de laatste call geeft de functie de waarde terug die de retourwaarde wordt van reduce().
  • Een beginwaarde die de eerste waarde is van de accumulator die aan de terugroepfunctie wordt doorgegeven.

De callback funtie gebruikt een paar parameters:

  • Een accumulator: De waarde die bij de vorige call van de callback functie is teruggekomen. Bij de eerste call wordt deze ingesteld op een beginwaarde, indien gespecificeerd. Anders neemt het de waarde van het eerste element van de array.
  • De waarde van het current element: De waarde wordt ingesteld op het eerste element van de array (array[0]) als een beginwaarde is ingesteld, anders neemt het de waarde van het tweede element (array[1]).
  • De current index is de indexpositie van het huidige element.

Een voorbeeld zal alles duidelijker maken.

const numbers = [1, 2, 3, 4, 5];
const initialValue = 0;
const sumElements = numbers.reduce(
	( accumulator, currentValue ) => accumulator + currentValue,
	initialValue
);
console.log( numbers ); // (5) [1, 2, 3, 4, 5]
console.log( sumElements ); // 15

Laten we in detail uitzoeken wat er bij elke iteratie gebeurt. Ga terug naar het vorige voorbeeld en verander de initialValue:

const numbers = [1, 2, 3, 4, 5];
const initialValue = 5;
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log('Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index);
		return accumulator + currentValue;
	},
	initialValue
);
console.log( sumElements );

De volgende afbeelding toont de output in de browserconsole:

using reduce() with initial value set to 5
reduce() gebruiken met initiële waarde ingesteld op 5

Laten we nu eens kijken wat er gebeurt zonder de parameter initialValue:

const numbers = [1, 2, 3, 4, 5];
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log( 'Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index );
		return accumulator + currentValue;
	}
);
console.log( sumElements );
Using reduce() without initial value
Reductie() gebruiken zonder beginwaarde

Meer voorbeelden en use cases worden besproken op de mdn web docs website.

Exporteren en importeren

Vanaf ECMAScript 2015 (ES6) is het mogelijk om waarden uit een JavaScript module te exporteren en te importeren in een ander script. Je zult imports en exports veelvuldig gebruiken in je React applicaties en daarom is het belangrijk om goed te begrijpen hoe ze werken.

De volgende code maakt een functionele component. De eerste regel importeert de React bibliotheek:

import React from 'react';

function MyComponent() {
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: 'dark'
	}
 
	return (
		<div
			className = 'card'
			style = {
				person.theme === 'dark' ?
				{ background: 'black', color: 'white' } :
				{ background: 'white', color: 'black'}
			}>
			<img
				src = { person.avatar }
				alt = { person.name }
				style = { { width: '100%' } }
			/>
			<div
				style = { { padding: '2px 16px' } }
			>
				<h3>{ person.name }</h3>
				<p>{ person.description }.</p>
			</div>
		</div>
	);
}
export default MyComponent;

We hebben het keyword import gebruikt, gevolgd door de naam die we willen toekennen aan wat we importeren, gevolgd door de naam van het pakket dat we willen installeren zoals het wordt genoemd in het package.json bestand.

Let op dat we in de bovenstaande MyComponent() functie enkele van de in de vorige secties besproken JavaScript properties hebben gebruikt. We hebben waarden van properties opgenomen tussen accolades en de waarde van de style property toegekend met de syntax van de conditional operator.

Het script eindigt met het exporteren van onze custom component.

Nu we wat meer weten over importeren en exporteren, laten we eens beter bekijken hoe ze werken.

Export

Elke React module kan twee verschillende soorten export hebben: named export en default export.

Je kunt bijvoorbeeld meerdere functies tegelijk exporteren met een enkel export statement:

export { MyComponent, MyVariable };

Je kunt ook individuele functies exporteren (function, class, const, let):

export function MyComponent() { ... };
export let myVariable = x + y;

Maar je kunt maar één standaard export hebben:

export default MyComponent;

Je kunt ook standaardexport gebruiken voor afzonderlijke functies:

export default function() { ... }
export default class { ... }

Import

Als de component eenmaal geëxporteerd is, kan hij samen met andere modules geïmporteerd worden in een ander bestand, bijvoorbeeld een index.js bestand:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import MyComponent from './MyComponent';

const root = ReactDOM.createRoot( document.getElementById( 'root' ) );
root.render(
	<React.StrictMode>
		<MyComponent />
	</React.StrictMode>
);

In bovenstaande code hebben we de importstatement op verschillende manieren gebruikt.

In de eerste twee regels hebben we een naam toegekend aan de geïmporteerde bronnen, in de derde regel hebben we geen naam toegekend, maar simpelweg het ./index.css bestand geïmporteerd. Het laatste import statement importeert het ./MyComponent bestand en kent een naam toe.

Laten we eens kijken wat de verschillen zijn tussen deze imports.

In totaal zijn er vier soorten imports:

Named import

import { MyFunction, MyVariable } from "./my-module";

Standaard import

import MyComponent from "./MyComponent";

Namespace import

import * as name from "my-module";

Side effect import

import "module-name";

Als je een paar stijlen hebt toegevoegd in je index.css, zou je kaart eruit moeten zien als in de afbeelding hieronder, waar je ook de bijbehorende HTML code kunt zien:

A simple React component
Een eenvoudig React component

Let op dat import declarations alleen kunnen worden gebruikt in modules op het hoogste niveau (niet binnen functies, klassen, enz.).

Voor een uitgebreider overzicht van import en export statements kun je misschien ook de volgende bronnen bekijken:

Samenvatting

React is een van de populairste JavaScript bibliotheken van dit moment en is een van de meest gevraagde vaardigheden in de wereld van webdevelopment.

Met React is het mogelijk om dynamische webapplicaties en geavanceerde interfaces te maken. Het maken van grote, dynamische en interactieve applicaties kan eenvoudig zijn dankzij de herbruikbare componenten.

Maar React is nog altijd een bibliotheek voor JavaScript en een goed begrip van de belangrijkste functies van JavaScript is essentieel om je reis met React te beginnen. Daarom hebben we op één plaats enkele van de JavaScript functies verzameld die je het meest gebruikt in React. Het beheersen van deze functies geeft je een voorsprong op je React leerreis.

En als het gaat om webdevelopment kost de overstap van JS/React naar WordPress weinig moeite.

Nu is het jouw beurt, welke JavaScript functies zijn volgens jou het nuttigst bij React ontwikkeling? Hebben we iets belangrijks gemist dat je graag op onze lijst had gezien? Deel je gedachten met ons in de comments hieronder.

Carlo Daniele Kinsta

Carlo is a passionate lover of webdesign and front-end development. He has been playing with WordPress for more than 20 years, also in collaboration with Italian and European universities and educational institutions. He has written hundreds of articles and guides about WordPress, published both on Italian and international websites, as well as on printed magazines. You can find him on LinkedIn.