1. Componenten
Deze les bespreken we hoe een React app aangemaakt kan worden, hoe de mappenstructuur van een React app in elkaar zit en hoe we eenvoudige componenten kunnen bouwen en gebruiken.
Project aanmaken
Een nieuw React project wordt aangemaakt via onderstaand commando.
Waarschuwing
Voer dit commando niet uit in een map die met de cloud gesynchroniseerd wordt. Voor één project zijn er duizenden kleine files nodig. Dit vertraagt je cloud storage enorm en kan eventueel leiden tot andere problemen als je op verschillende computers werkt. Indien je code wil synchroniseren tussen verschillende computers moet je git gebruiken.
pnpm create vite
Dit commando zal een aantal vragen stellen over de technologieën waarmee het nieuwe project gebouwd wordt, natuurlijk moeten we eerste de naam van het project ingeven.
? Project name: › projectNaam
Hierin wordt projectNaam logischerwijs vervangen met de naam van het nieuwe project.
Package name
Als je een project name gebruikt hebt waar spaties of andere ongeldige karakters in staan, wordt er gevraagd naar een package name. Dit is de naam die gebruikt wordt om je project te identificeren als je het online zou publiceren of willen toevoegen als een onderdeel van een ander project.
Als je een project name gebruikt hebt zonder spaties of andere ongeldige karakters, wordt deze vraag overgeslagen en is de package name gelijk aan de project name.
? Package name: › projectnaam
Vervolgens wordt er gevraagd welk framework er gebruik moet worden, wij kiezen natuurlijk voor React.
? Select a framework: › - Use arrow-keys. Return to submit.
Vanilla
Vue
❯ React
Preact
Lit
Svelte
Solid
Qwik
Others
Tenslotte moeten we aanduiden of we met TypeScript of JavaScript willen werken en welke bundler we willen gebruiken. De bundler is verantwoordelijk voor het samenvoegen van alle afzonderlijke bestanden in een formaat dat begrijpbaar is voor een browser. Wij kiezen voor TypeScript + SWC. SWC is een relatief nieuwe bundler die sneller is dan het alternatief, maar nog niet door alle frameworks ondersteund wordt. Aangezien SWC alles ondersteund dat we in deze cursus nodig hebben, kiezen we voor de snelste optie.
? Select a variant: › - Use arrow-keys. Return to submit.
TypeScript
❯ TypeScript + SWC
JavaScript
JavaScript + SWC
Remix ↗
Het commando produceert uiteindelijk een nieuwe map projectNaam die het React project bevat. Deze map wordt aangemaakt als submap van de locatie waar het commando uitgevoerd wordt. Als de terminal zich in ~/projects bevindt, dan zal het commando pnpm create vite projectNaam --template react-swc-ts
een nieuwe map ~/projects/projectNaam aanmaken.
Bovenstaand commando genereert enkel de mappenstructuur, maar installeert React en alle andere nodige pakketten nog niet, hiervoor moet je, zoals in de terminal te zien is, nog 2 extra commando's uitvoeren.
cd projectNaam
pnpm install
Mappenstructuur
De mappenstructuur van een React project is relatief eenvoudig. Hieronder bespreken we elke map en elk bestand.

node_modules
De node_modules map bevat alle bibliotheken en tools die nodig zijn om een React applicatie te ontwikkelen en publiceren. Daarnaast komen ook alle extra pakketten die jij, als ontwikkelaar, installeert in deze map te staan. Omdat we pnpm gebruiken bevat deze map enkel symbolic links naar een algemene library cache op jouw machine. Dit betekent dus dat deze map overbodig is als je code uploadt, in een git repository plaatst, ... Iedereen die de package.json file heeft, kan de node_modules map dupliceren. Voeg deze map dus altijd toe aan je .gitignore file.
Info
De node_modules map kan eenvoudig gereproduceerd worden met onderstaand commando.
pnpm install
public
De map public bevat statische resources die niet mee gecompileerd, gebundeld of minified moeten worden. Deze map bevat standaard enkel het Vite logo. Normaliter moet er niet veel toegevoegd worden aan deze folder, behalve een favicon en eventuele statische bestanden (bijvoorbeeld een CV (pdf) op een portfolio). Alle code, stylesheets en images komen in de src map te staan. Zo kunnen assets die uiteindelijk niet gebruikt worden, uitgesloten worden uit de production bundle.
src
Deze map bevat de eigenlijke code van de React applicatie, standaard bevat deze map een hele reeks bestanden. In deze cursus vertrekken we steeds van een leeg project, de inhoud van de src map mag je dus weggooien als je een nieuw project aanmaakt.
Waarschuwing
Er is één uitzondering op bovenstaande regel, de vite-env.d.ts file blijft best wel bestaan. Deze file wordt gebruikt om correcte autocompletion te voorzien als we met omgevingsvariabelen werken in één van de komende lessen.
.eslintrc.cjs
Een basisconfiguratie voor de linter ESLint. Via ESLint kunnen we de code kwaliteit van onze applicatie controleren en verbeteren, foutieve code wordt aangegeven in de editor en kan automatisch verbeterd worden.
We gebruiken deze file niet in deze cursus, maar vervangen deze met een uitgebreidere configuratie in het nieuwe ESLint formaat. Een uitgebreide beschrijven en installatie-instructies zijn terug te vinden in de appendix.
.gitignore
Elk React project wordt standaard geïnitialiseerd als een git project. Het .gitignore bestand bevat een opsomming van de bestanden die niet in version control geplaatst mogen worden, bijvoorbeeld de node_modules en dist mappen.
package.json
Binnen package.json worden alle geïnstalleerde pakketten opgesomd. Hiervoor worden 2 attributen gebruikt binnen het JSON-object. Het eerste attribuut dependencies bevat een lijst van alle geïnstalleerde pakketten die relevant zijn voor de eindgebruiker.
Het tweede attribuut devDependencies bevat een lijst van alle geïnstalleerde pakketten die enkel relevant zijn tijdens de ontwikkeling van de applicatie. Zaken zoals linters, transpilers, build-tools, en testing libraries, horen hier thuis. Tijdens het compilatieproces worden enkel de dependencies gekopieerd naar de productie-build. De devDependencies worden gebruikt om de productie-build te genereren en zullen dus niet aanwezig zijn in een productiebuild.
pnpm-lock.yaml
Deze file beschrijft de exacte versies van elke library die in de node_modules map geïnstalleerd is. Verder kan je hier ook de dependencies terugvinden van libraries die je zelf geïnstalleerd hebt in package.json. Ook de dependencies van de dependencies en zo verder worden hier opgesomd.
README.md
Installatie en uitvoeringsinstructies voor de applicatie.
tsconfig.json, tsconfig.app.json en tsconfig.node.json
Tsconfig.json beschrijft hoe TypeScript onze applicatie moet compileren naar JavaScript en bepaald welke dingen toegestaan zijn in de code en welke niet. Omdat de code gecompileerd wordt in Node en uitgevoerd wordt in een browser, zijn er 2 extra configuratiebestanden voorzien. Deze worden allebei ingeladen in de basis die in tsconfig.json geconfigureerd is en bevatten de extra opties die specifiek zijn voor Node en de browser. Af en toe zullen we iets aanpassen aan de configuratie in deze bestanden, maar normaliter is de standaardconfiguratie voldoende voor onze projecten.
vite.config.ts
Dit bestand beschrijft hoe de applicatie gecompileerd moet worden door de bundler. De opties voor de bundler zijn zeer uitgebreid en complex, we gebruiken dan ook de standaard configuratie voor alle projecten. Af en toe gebruiken we deze file wel om bundler plugins te activeren.
index.html
Dit is de enige pure HTML-file die we gebruiken in een React project. Enkel de inhoud van het <head> tag aangepast worden, bijvoorbeeld om het favicon te wijzigen, aan SEO te doen of iets van een CDN in te laden.
De HTML-pagina bevat standaard volgende code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Het gemarkeerde <div> element vormt de root van onze pagina, React zal hier de volledige pagina in laden via JavaScript.
Renderen
Zoals eerder gezegd, bestaat index.html uit zeer weinig code. De body bevat slechts een div met het ID root. Dit element vormt de kern van de applicatie, alle componenten worden hierin gerenderd door React.
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Net zoals er voor een statische website steeds een index.html nodig is, moeten we voor een React website ook steeds een bestand toevoegen dat als ingangspunt dient voor de applicatie. Dit bestand krijgt de naam main.tsx en wordt natuurlijk in de src map geplaatst.
In dit bestand leggen we de link met index.html. Het <div> element kan eenvoudig opgehaald worden via de standaard JavaScript methode document.getElementById.
We gebruiken dit element vervolgens om de root (de plaats waarin de volledige website door React gerenderd zal worden) te initialiseren. Omdat we met TypeScript werken, moeten we een cast of non-null-assertion toevoegen waarmee we aangeven dat het root-element gegarandeerd bestaat.
import {createRoot} from 'react-dom/client'
const root = createRoot(
document.getElementById('root') as HTMLElement
)
Om vervolgens een React component te tonen moet render methode gebruikt worden. Deze methode heeft één parameter, de JSX-code die gerenderd moet worden.
// Importeren van react-dom, de bibliotheek die het renderen mogelijk maakt.
import {createRoot} from 'react-dom/client'
// Aanmaken van de root voor de React applicatie.
const root = createRoot(
document.getElementById('root') as HTMLElement
)
root.render(
<h1>Hello World!</h1>
)
Bovenstaande code produceert een pagina waarop de tekst "Hello World!" getoond wordt.
Om deze code uit te testen moet de development server gestart worden, dit kan met het commando:
pnpm dev
Info
Als je een andere package manager gebruikt zoals npm of yarn gebruikt kan het, afhankelijk van de versie, nodig zijn om het commando aan te passen naar npm run dev
of yarn run dev
.
Nadat je dit commando uitgevoerd hebt, zie je in de terminal een opsomming van de IP-adressen waarop de development server beschikbaar is. Kopieer de URL naar je browser of klik op het adres om automatisch naar de browser te gaan.

JSX
React heeft ervoor gekozen om JavaScript en HTML te combineren in één bestand, een component. Zo'n component stelt één klein onderdeel van de UI voor, bijvoorbeeld een knop, een tekstveld, een formulier, ...
Klassieke JavaScript biedt geen bijzonder goede ondersteuning voor het schrijven van HTML-code, hiervoor zijn meestal een hele hoop string concatenaties nodig of moet je zeer veel document.createElement calls gebruiken. Dit heeft trage en moeilijk te lezen code als gevolg, JSX is een uitbreiding voor JavaScript (en TypeScript) die hier een oplossing voor biedt.
Begrip: JSX
JavaScript XML (JSX), laat toe om HTML-code in JavaScript te gebruiken zonder quotes of concatenaties. Het is natuurlijk onmogelijk voor een browser om zo'n code te lezen en uit te voeren. Daarom moet elke lijn JSX-code gecompileerd worden naar klassiek JavaScript code.
Een bestand waar JSX in gebruikt wordt, moet de extensie JSX of tsx hebben, afhankelijk van de taal die je gebruikt.
Via SWC wordt onderstaande TypeScript code gecompileerd naar de bijhorende JavaScript code.
const element = <h1>Hello, world!</h1>
var element=React.createElement("h1",null,"Hello, world!");
Syntax regels
TSX is een geweldige uitbreiding op TypeScript, maar er zijn enkele belangrijke syntax regels waarmee rekening gehouden moet worden.
Onderstaande (geldige) HTML-code kan niet zomaar aan een variabele toegekend worden in TSX.
<h1>Hello World!</h1>
<h1>Hello Universe!</h1>
const element = <h1>Hello World!</h1>
<h1>Hello Universe!</h1>;
De voorgaande code produceert volgende foutmelding.
Zoals de foutmelding zegt moeten 2 opeenvolgende JSX-elementen binnen een ander element geplaatst worden. In welk element de HTML-code geplaatst wordt speelt geen rol, dit kan een <div>, <span>, ... zijn.
const element = <div>
<h1>Hello World!</h1>
<h1>Hello Universe!</h1>
</div>
Bovenstaande code werkt en genereert geen foutmeldingen meer, maar de code is niet perfect uitgelijnd. Om dit op te lossen kan je de code omringen met ronde haken.
const element = (
<div>
<h1>Hello World!</h1>
<h1>Hello Universe!</h1>
</div>
)
Bovenstaande voorbeelden zijn relatief beperkt, we maken eigenlijk geen gebruik van TypeScript, alles wat er gebeurt, zou perfect met klassieke HTML kunnen. TSX wordt pas echt interessant als we TypeScript code integreren in HTML-code.
We kunnen TypeScript expressies gebruiken door deze te omringen met accolades. Boven- en onderstaande code produceren net hetzelfde resultaat, maar de onderstaande code is dynamischer, de tekst "World" en "Universe" komen nu uit een TypeScript object en zijn niet langer hardcoded.
Merk op dat bijna alle geldige JavaScript code gebruikt kan worden tussen de accolades. In de eerste titel maken we gebruik van string concatenatie, maar in de tweede is het uitroepteken buiten de accolades geplaatst en wordt het geïnterpreteerd als HTML-code. De code tussen de accolades kan eveneens een functie zijn die een string teruggeeft of nog iets compleet anders.
const greeting = {
greeting1: "World",
greeting2: "Universe"
}
const element = (
<div>
<h1>Hello {greeting.greeting1 + "!"}</h1>
<h1>Hello {greeting.greeting2}!</h1>
</div>
)
root.render(
element
)
Bovenstaande code werkt en rendered de gevraagde hoofdingen, maar we hebben een omringende <div> moeten toevoegen. Hier is niets mis mee, maar soms heb je een container nodig waaraan geen opmaak gebonden is (een <div> begint standaard een nieuwe lijn omdat het een block-element is), in dat geval je een fragment gebruiken.
Begrip: Fragment
Een fragment is een container element dat enkel in de React code bestaat en geen effect heeft op de uiteindelijke HTML-code in het afgewerkte product. Een fragment wordt niet gerenderd.
const element = (
<div>
<h1>Hello {greeting.greeting1 + "!"}</h1>
<h1>Hello {greeting.greeting2}!</h1>
</div>
)
const element = (
<>
<h1>Hello {greeting.greeting1 + "!"}</h1>
<h1>Hello {greeting.greeting2}!</h1>
</>
)
Componenten
React applicaties delen, net zoals de andere grote JavaScript frameworks, de UI op in kleine stukken of componenten. De bovenste component stelt een pagina voor, deze pagina bevat kleinere componenten die bijvoorbeeld een navigatiebalk, side-menu en content component bevatten. De navigatiebalk is op zijn beurt ingedeeld in een titel, navigatie en login-form component. Deze componenten kunnen op hun beurt weer ingedeeld worden in nog kleinere componenten.
Het doel van componenten is een herbruikbare, onderhoudbare UI. Idealiter staat elke component op zich en kan deze in de rest van de website, of op een andere website, herbruikt worden. In de praktijk zal dit echter eerder gelden voor componenten libraries dan voor echte applicaties.
Begrip: Component
Een component is een onderdeel van een React applicatie. Een applicatie bestaat uit tal van componenten. Een component staat op zich, kan herbruikt worden en gebruikt eventueel andere componenten.
Er is geen vast regel over hoe klein een component mag/moet zijn. Alles hangt af van wat je bent aan het bouwen. In een UI-componenten library heeft het zin om een aparte component te bouwen voor een titel of voor een paragraaf omdat hier bepaalde styling aan verbonden is die moet integreren met de rest van library. Als je een eigen CRUD-applicatie bent aan het ontwikkelen, ga je geen aparte component maken voor een titel of paragraaf, maar gewoon een header of paragraph tag gebruiken (of een component uit een library) in een grotere component die de pagina of een onderdeel daarvan weergeeft.

Bron: https://react.dev/learn/thinking-in-react
Componenten definiëren
Om een component te definiëren, gebruiken we een JavaScript functie. De enige vereisten voor deze functie zijn dat:
- De functie een JSX-expressie teruggeeft
- De functie een naam heeft die begint met een hoofdletter
Het "Hello World/Universe" voorbeeld kan eenvoudig als een component geschreven worden, dit betekent dat de render-methode ook aangepast moet worden. Een component kan in een JSX-expressie opgeroepen worden alsof het een HTML-element is.
We gebruiken de FunctionComponent interface om aan te geven dat de HelloWorld variabele een FunctionComponent is, zo garanderen we dat we geldige TSX-code teruggeven en kunnen we later properties toevoegen.
import {FunctionComponent} from 'react';
const HelloWorld: FunctionComponent = () => {
const greeting = {
greeting1: "World",
greeting2: "Universe"
}
return (
<>
<h1>Hello {greeting.greeting1 + "!"}</h1>
<h1>Hello {greeting.greeting2}!</h1>
</>
)
}
root.render(
<HelloWorld/>
)
Tenslotte importeren we ook een door React aangereikte component, <StrictMode>. Deze component kan gebruikt worden om extra controles op fouten uit te voeren tijdens het ontwikkelproces. In een production build heeft deze component geen effect, verder is er aan deze component ook geen zichtbare UI gekoppeld.
import {StrictMode} from 'react'
// Niet relevante code weggelaten.
root.render(
<StrictMode>
<HelloWorld/>
</StrictMode>
)
Begrip: Functie component
Een functie component is een functie die één herbruikbaar onderdeel van de user interface definieert en JSX-code teruggeeft. De naam van zo'n functie begint steeds met een hoofdletter. Een functie component kan elders in de code gebruikt worden op dezelfde manier als je een HTML-element zou gebruiken.
De twee onderstaande voorbeelden zijn gelijkwaardig, de FunctionComponent en FC interfaces zijn aliassen van elkaar.
import {FunctionComponent} from 'react';
const FunctionComponent: FunctionComponent = () => {
return (
<p>
Dis is een functie component,
de return waarde moet JSX-code zijn.
</p>
)
}
const exampleUsage = <FunctionComponent/>;
import {FC} from 'react';
const FunctionComponent: FC = () => {
return (
<p>
Dis is een functie component,
de return waarde moet JSX-code zijn.
</p>
)
}
const exampleUsage = <FunctionComponent/>;
Importeren en exporteren
We kunnen natuurlijk niet alle componenten in één .tsx bestand plaatsten, dit zorgt snel voor onnodig grote bestanden en slecht onderhoudbare code. We importeren componenten daarom uit individuele bestanden waaruit de component geëxporteerd wordt.
Begrip: Export & export default
In deze cursus gebruiken we voor componenten steeds een default export, dit wil zeggen dat de componenten geïmporteerd kunnen worden zonder accolades te moeten schrijven in het import-statement.
Als we de default modifier weglaten, wordt de component nog steeds geëxporteerd, maar maakt deze deel uit van een object. We spreken in dit geval van een named export.
Alle named exports worden verzameld in een object, via accolades in het import-statement kunnen we aangeven dat we slechts één element uit dat object willen importeren.
Een bestand kan maximaal één default export hebben, maar kan meerdere named exports bevatten. De twee kunnen ook gecombineerd worden binnen één bestand.
import {FunctionComponent} from 'react';
export const HelloWorld: FunctionComponent = () => {
const greeting = {
greeting1: 'World',
greeting2: 'Universe'
}
return (
<>
<h1>Hello {greeting.greeting1 + '!'}</h1>
<h1>Hello {greeting.greeting2}!</h1>
</>
)
}
const HelloWorlDefault = HelloWorld;
export default HelloWorlDefault;
import {HelloWorld} from './helloWorld.tsx';
import HelloWorlDefault from './helloWorld.tsx';
Properties
Stel we willen een lijst van enkele belangrijke grondleggers van de computerwetenschappen renderen. De eenvoudigste optie is natuurlijk via een component die opgeroepen wordt in main.tsx en waar al deze personen in hardcoded zijn.
import type {FunctionComponent} from 'react'
const ComputerScientistListV1: FunctionComponent = () => {
return (
<div>
<h1>Famous computer scientists</h1>
<ul>
<li>
Charles Babbage (1791 - 1871):
<p>Originated the concept of a programmable general-purpose computer. Designed the Analytical Engine
and built a prototype for a less powerful mechanical calculator.</p>
</li>
<li>
Ada Lovelace (1815 - 1852):
<p>An English mathematician and writer, chiefly known for her work on Charles
Babbage's proposed mechanical general-purpose computer, the Analytical Engine. She was the first
to recognize that the machine had applications beyond pure calculation, and created the first
algorithm intended to be carried out by such a machine. As a result, she is often regarded as
the first to recognize the full potential of a "computing machine" and the first computer
programmer.</p>
</li>
<li>
Alan Turing (1912 - 1954):
<p>Made several fundamental contributions to theoretical computer science,
including the Turing machine computational model, the conceiving of the stored program concept
and the designing of the high-speed ACE design. Independently of Alonzo Church, he formulated
the Church-Turing thesis and proved that first-order logic is undecidable. He also explored the
philosophical issues concerning artificial intelligence, proposing what is now known as Turing
test.</p>
</li>
</ul>
</div>
)
}
export default ComputerScientistListV1
import {createRoot} from 'react-dom/client'
import {StrictMode} from 'react'
import ComputerScientistListV1 from './computerScientistListV1.tsx';
const root = createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<StrictMode>
<ComputerScientistListV1/>
</StrictMode>
)
Dit is natuurlijk helemaal niet efficient, elk element in de lijst heeft dezelfde structuur, zodra iets meerdere keren voorkomt in de applicatie is het best hier een aparte component van te maken. Zelf als dit niet het geval is, kan je nog steeds een extra component gebruiken om de leesbaarheid te bevorderen.
We kunnen eenvoudig 3 componenten maken, één voor elk van 3 computerwetenschappers, dit is natuurlijk niet veel beter. Een component moet herbruikbaar zijn, het is dus nodig om de naam en voornaam, het geboorte- en sterftejaar, en de bijdragen door te geven aan de nieuwe component. Hiervoor kunnen we properties gebruiken.
We definiëren een nieuwe component ComputerScientist die de eigenschappen van één bekende computerwetenschapper doorgegeven krijgt via de properties. Properties (of props) is een object dat informatie bevat die nodig is om de component correct te renderen, dit object wordt doorgegeven als parameter aan een component.
Om type checking, autocompletion en intellisense toe te voegen aan de component, definiëren we eerst een TypeScript interface die de properties beschrijft. Vervolgens geven we deze interface door als generische parameter aan de FunctionComponent interface. Tenslotte gebruiken we deconstructing om de properties toe te kennen aan een variabele.
In dit geval maken we gebruik van de IComputerScientist interface uit de models map. Als een component meer properties nodig heeft dan diegene die rechtstreeks uit een model komen, dan kunnen we een extra interface bouwen en deze meegeven als parameter aan de FunctionComponent interface. Die interface krijgt dan de naam ComponentNameProps, in dit geval zou zo'n interface dus ComputerScientistProps heten en overerven van IComputerScientist.
export interface IComputerScientist {
firstName: string
lastName: string
birth: number
death: number
accomplishments: string
}
import {IComputerScientist} from './models/IComputerScientist.ts'
const ComputerScientist: FunctionComponent<IComputerScientist> = (props) => {
const {firstName, lastName, birth, death, accomplishments} = props
return (
<li>
{firstName} {lastName} ({birth} - {death}):
<p>{accomplishments}</p>
</li>
)
}
import type {FunctionComponent} from 'react'
const ComputerScientistListV2: FunctionComponent = () => {
return (
<div>
<h1>Famous computer scientists</h1>
<ul>
<ComputerScientist
firstName={'Charles'} lastName={'Babbage'}
birth={1791} death={1871}
accomplishments={'Originated the concept of a programmable general-purpose computer. Designed the Analytical Engine and built a prototype for a less powerful mechanical calculator. '}
/>
<ComputerScientist
firstName={'Ada'} lastName={'Lovelace'}
birth={1814} death={1852}
accomplishments={"An English mathematician and writer, chiefly known for her work on Charles Babbage's proposed mechanical general-purpose computer, the Analytical Engine. She was the first to recognize that the machine had applications beyond pure calculation, and created the first algorithm intended to be carried out by such a machine. As a result, she is often regarded as the first to recognize the full potential of a \"computing machine\" and the first computer programmer."}
/>
<ComputerScientist
firstName={'Alan'} lastName={'Turing'}
birth={1912} death={1954}
accomplishments={'Made several fundamental contributions to theoretical computer science, including the Turing machine computational model, the conceiving of the stored programs concept and the designing of the high-speed ACE design. Independently of Alonzo Church, he formulated the Church-Turing thesis and proved that first-order logic is undecidable. He also explored the philosophical issues concerning artificial intelligence, proposing what is now known as Turing test.'}
/>
</ul>
</div>
)
}
export default ComputerScientistListV2
import {createRoot} from 'react-dom/client'
import {StrictMode} from 'react'
import ComputerScientistListV2 from './computerScientistListV2.tsx';
const root = createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<StrictMode>
<ComputerScientistListV2/>
</StrictMode>
)
Begrip: Properties
De properties of props zijn beschikbaar op elke component en worden meegegeven via een parameter in de functie die de component definieert. Deze parameter is steeds een object. Via een TypeScript interface wordt gedefinieerd welke properties verwacht worden.
In JSX-code kunnen properties doorgegeven worden als HTML-attributen.
import {FunctionComponent} from 'react';
interface ExampleComponentProps {
example: string;
}
const ExampleComponent: FunctionComponent<ExampleComponentProps> = (props) => {
return <p>{props.example}</p>;
}
const exampleUse = <FunctionComponent
example={'This is an example value for the property example.'}/>
Lussen in JSX
In de meeste gevallen zal data, zoals de computerwetenschappers, geladen worden vanuit een API of database en bewaard worden in een array. We simuleren dit hieronder door een array met computerwetenschapper hard te coderen in onze code.
import IComputerScientist from '../models/IComputerScientist.ts';
const computerScientists: IComputerScientist[] = [
{
firstName: 'Charles',
lastName: 'Babbage',
birth: 1791,
death: 1871,
accomplishments: 'Originated the concept of a programmable general-purpose computer. Designed the Analytical Engine and built a prototype for a less powerful mechanical calculator. ',
},
{
firstName: 'Ada',
lastName: 'Lovelace',
birth: 1814,
death: 1852,
accomplishments: "An English mathematician and writer, chiefly known for her work on Charles Babbage's proposed mechanical general-purpose computer, the Analytical Engine. She was the first to recognize that the machine had applications beyond pure calculation, and created the first algorithm intended to be carried out by such a machine. As a result, she is often regarded as the first to recognize the full potential of a \"computing machine\" and the first computer programmer.",
},
{
firstName: 'Alan',
lastName: 'Turing',
birth: 1912,
death: 1954,
accomplishments: 'Made several fundamental contributions to theoretical computer science, including the Turing machine computational model, the conceiving of the stored programs concept and the designing of the high-speed ACE design. Independently of Alonzo Church, he formulated the Church-Turing thesis and proved that first-order logic is undecidable. He also explored the philosophical issues concerning artificial intelligence, proposing what is now known as Turing test.',
},
]
export default computerScientists
Via een lus kunnen we door deze array itereren en de nodige componenten aanmaken, let op, dit moet gebeuren voor het return keyword omdat een lus niet geldig is in een JSX-expressie. Elk van de ComputerScientist componenten moet bewaard worden in een array die we vervolgens kunnen uitprinten in de JSX-code.
import type {FunctionComponent} from 'react'
interface Props {
scientists: IComputerScientist[]
}
const ComputerScientistListV3: FunctionComponent<Props> = ({scientists}) => {
const output: ReactNode[] = []
for (const scientist of scientists) {
output.push(
<ComputerScientist
firstName={scientist.firstName}
lastName={scientist.lastName}
birth={scientist.birth}
death={scientist.death}
accomplishments={scientist.accomplishments}
/>,
)
}
return (
<div>
<h1>Famous computer scientists</h1>
<ul>
{output}
</ul>
</div>
)
}
export default ComputerScientistListV3
Bovenstaande code kan nog heel wat korter geschreven worden. De spread operator kopieert alle attributen van een object en behoudt dezelfde namen voor deze properties. Voor een React component worden de attributen van het object doorgegeven als properties aan de component, opnieuw onder dezelfde naam. Onderstaande en bovenstaande code produceert dus exact hetzelfde resultaat.
interface Props {
scientists: IComputerScientist[]
}
const ComputerScientistListV3: FunctionComponent<ComputerScientistListV3Props> = ({scientists}) => {
const output: ReactNode[] = []
for (const scientist of scientists) {
output.push(<ComputerScientist {...scientist} />)
}
return (
<div>
<h1>Famous computer scientists</h1>
<ul>{output}</ul>
</div>
)
}
import {createRoot} from 'react-dom/client'
import {StrictMode} from 'react'
import ComputerScientistListV3 from './computerScientistListV3.tsx';
import computerScientists from './data/computerScientists.ts';
const root = createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<StrictMode>
<ComputerScientistListV3 scientists={computerScientists}/>
</StrictMode>
)
Geneste JSX-code
Elke functiecomponent kan gebruikt worden als zelfsluitend HTML-element. Dit is niet altijd voldoende, in sommige gevallen is het nodig om kinderen te definiëren voor een functiecomponent.
Stel, we willen een lijst van letters uitprinten, elke letter krijgt dezelfde opmaak. De component LetterAlternative1 zorgt dus enkel voor de opmaak van de letter en voegt verder niets toe. Dit betekent dat we op de een of andere manier de letter moeten doorgeven aan de LetterAlternative1 component. We kunnen natuurlijk een property letterValue toevoegen en de inhoud van deze property uitprinten in de LetterAlternative1 component.
Dit is echter niet de beste manier om dit te doen omdat het moeilijk schaalbaar is. Stel dat je LetterAlternative1 wilt kunnen gebruiken om één letter, een reeks paragrafen of een opsomming uit te printen met een bepaalde stijl. In dit geval wordt snel moeilijk om duidelijke properties mee te geven, we in dit geval een array van string nodig die de letter, de paragrafen of de elementen in de opsomming bevat. Daarnaast is er ook een property nodig die aangeeft hoe de data gerenderd moet worden (één <p> tag, meerdere <p> tags of een combinatie van <ul> en <li> tags). Daarnaast moeten we in LetterAlternative1 dan ook nog een if-else if-else structuur gebruiken om de data op de juiste manier te printen.
Het is duidelijk dat zo'n oplossing niet schaalbaar is, het is beter om de kinderen van een component te definiëren als een array van React componenten. Op deze manier kunnen we, wanneer we de component oproepen, de data al geformatteerd doorgeven (genest tussen de <LetterAlternative1> en </LetterAlternative1> tags) en wordt deze gewoon uitgeprint binnen de component. We kunnen hiervoor de PropsWithChildren interface gebruiken om de children property toe te voegen aan de properties van een component. Of, we kunnen deze eventueel ook zelf definiëren in de bijhorende properties interface, gebruik in dat geval het ReactNode datatype.
import {FunctionComponent, PropsWithChildren} from 'react';
const LetterAlternative1: FunctionComponent<PropsWithChildren> = ({children}) => {
return (
<div>
{/* Render de kinderen van deze component.*/}
{children}
</div>
)
}
root.render(
<StrictMode>
{/* A is een kind van de component LetterAlternative1*/}
<LetterAlternative1>A</LetterAlternative1>
<LetterAlternative1>E</LetterAlternative1>
<LetterAlternative1>I</LetterAlternative1>
<LetterAlternative1>O</LetterAlternative1>
<LetterAlternative1>U</LetterAlternative1>
</StrictMode>
)
Als we LetterAlternative1 gebruiken zonder de children op te vullen wordt er niets getoond.
CSS
CSS kan op een aantal verschillende manieren toegevoegd worden aan een React applicatie. Natuurlijk kan een stylesheet (.css bestand) nog steeds gekoppeld worden aan de applicatie. Daarnaast kunnen we de CSS-code ook bij in een component plaatsen.
CSS via stylesheets
De eerste optie is de meest eenvoudige, we plaatsen een CSS-bestand in een map assets in de src directory. Het is belangrijk om stylesheets in de src map te plaatsen, zo worden deze geminimaliseerd tijdens het maken van een production build en worden eventuele ongebruikte lijnen verwijderd. We voegen onderstaande code toe in main.css en importeren dit in main.tsx.
.letter {
padding: 10px;
margin: 10px;
background-color: #ffde00;
color: #333;
display: inline-block;
font-family: monospace;
font-size: 32px;
text-align: center;
}
import './assets/main.css';
De CSS-code verwacht een klasse, deze toevoegen aan de component LetterAlternative1 is niet bijzonder moeilijk. Het is wel belangrijk om op te merken dat we attribuut class niet kunnen gebruiken, dit is namelijk een keyword in JavaScript. React gebruikt het attribuut className om een CSS-klasse mee te geven in JSX-code.
const LetterAlternative1: FunctionComponent<LetterProps> = ({children}) => {
return (
<div className={'letter'}>
{children}
</div>
)
}
CSS in JavaScript
Het HTML-attribuut style kan gebruikt worden om inline CSS toe te voegen, in React kan dit ook. Het enige verschil is dat we in JSX-code gebruik moeten maken van een JavaScript object om de stijl toe te voegen, in p laats van een puntkomma-seperated string in HTML. Dit object heeft het type CSSProperties.
Een belangrijke opmerking is dat CSS-eigenschappen niet rechtstreeks vertaald kunnen worden naar JavaScript. Een attribuut als font-family wordt fontFamily, background-color wordt backgroundColor, ...
Als we de CSS in de componenten schrijven, kunnen we properties gebruiken om bepaalde eigenschappen te overschrijven, zoals gedemonstreerd in onderstaand voorbeeld.
Merk op dat we de PropsWithChildren interface dit keer niet rechtstreeks doorgegeven hebben aan de FunctionComponent interface, maar dat we overerving gebruikt hebben om de children property toe te voegen aan de LetterAlternative2Props interface.
Verder hebben we ook een vraagteken gebruikt om aan te geven dat de property bgColor optioneel is, aangezien deze property optioneel is, controleren we op lijn 12, via de nullish coalescing operator, of we al dan niet gebruik moeten maken van de default waarde.
interface LetterAlternative2Props extends PropsWithChildren {
bgColor?: string
}
const LetterAlternative2: FunctionComponent<LetterAlternative2Props> = (props) => {
const {children, bgColor} = props
const letterStyle: CSSProperties = {
padding: 10,
margin: 10,
backgroundColor: bgColor ?? '#ffdeEE',
color: '#333',
display: 'inline-block',
fontFamily: 'monospace',
fontSize: 32,
textAlign: 'center',
}
return (
<div style={letterStyle}>
{children}
</div>
)
}
root.render(
<StrictMode>
<LetterAlternative2 bgColor="#FFF">A</LetterAlternative2>
<LetterAlternative2>E</LetterAlternative2>
<LetterAlternative2>I</LetterAlternative2>
<LetterAlternative2>O</LetterAlternative2>
<LetterAlternative2>U</LetterAlternative2>
</StrictMode>
)