Ich nehme derzeit an einer Akademie zum Java Fullstack Software Engineer teil. In den kommenden Wochen möchte ich hier meine Mitschrift, so gut es geht, aufzeichnen und mitteilen. Hier ist das, was ich vom fünften Tag (mit etwas Verspätung) in Block 10 gelernt und behalten habe:
Flux, Redux, AJAX
In React wollen wir Daten von einem Objekt zum anderen übertragen. Die Objekte in einer HTML-Seite sind hierarchisch angeordnet. Ein Objekt kann Daten an den Reducer senden. Der leitet sie (reduziert) weiter an einen Store.
Objekte, die sich an dem Store registriert haben, erhalten ein Update für ihre Daten, sobald sich dort etwas ändert.
Siehe auch: https://react-redux.js.org/
Input und Output müssen sich am Reducer registrieren. Nachdem der Client seine Nachrichten gesendet hat, fängt eine Funktionalität an zu arbeiten. Diese Logik beeinflusst den Store. Der Store erkennt die Änderungen und informiert die Mitglieder der Liste. Daraufhin ändern sich die States der Empfänger.
Beispiel: \Block10-React\day05\my-redux-app:
Die Konstante counterreducer ist ein eine Methode, wo im ersten Teil die Daten übergeben werden und im zweiten Teil die Logik steht. Hier als unvollständiges Beispiel, wo State auch ein Objekt (Radius,…) sein könnte:
In Zeile 14 wird der Store mit dem Reducer kombiniert. Store weiß dann, was er zu verwalten hat. Im Store wird das State gehalten:
Hier noch der Inhalt von index.js und reducer.js:
Store erbt von Object und hat einige Default-Funktionen intus. Z.B. Store.subscribe,…
Registrierung des Render-Objektes:
Zeile 50-53 erzeugt eine Haupt-Komponente, speichert sie in einer Konstanten "render" und meldet sich als Subscriber am Store (Reduzer) an.
Wir erzeugen eine Referenz-Variable und können sie dann registrieren.
Im klassischen Weg wird das Rendern direkt ausgeführt (Kommentar Zeile 57). Jetzt (Zeile 51) wird das Ausführen einer Konstante übergeben (Hier jetzt "builder").
In Zeile 52 wird diese Konstante (welches eine Funktion ist) ausgeführt und in 53 wird diese Konstante an den Store übergeben.
Die Funktionen increment und decrement führen im Store die entsprechenden Aktionen aus:
Wir installieren das Redux DevTool im Chrome-Browser
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en
Wir können nun im Log (Strg-Shift-I) rechts oben auf den Redux-Button klicken und sehen einige Informationen mehr:
Weitere Beispiele:
Kap1601-flux-redux
AJAX /XHR / Promise / windows.fetch
Wir kopieren unser Template in ein neues ueb-ajax Projekt. Wir benennen das Projekt im package.json in "ueb-ajax" um (Zeile 2):
Hier der funktionierrende Code von app.jsx:
import logo from './logo.svg'; import './App.css'; import React from 'react';class App extends React.Component {
constructor(props) {
// Private PropertieInformation
// Data / Model
super(props);// Private StateInformation // Data / Model this.state = { "url" : 'https://swapi.dev/api/planets/1/', "status" : '', "data" : '' }; // Databinding an die private States binden // ReadOnly auf die States für die angegebene Methode aufheben this.handlerButtonFetch = this.handlerButtonFetch.bind(this); this.handlerButtonClear = this.handlerButtonClear.bind(this);
}
// Service
getDataXHR() {
console.log("Call: getDataXHR");
// 1. Local XHR erzeugen
let xhr = new XMLHttpRequest(); // ab ECMA-5.1
// 2. Init / Config des XHR-Objektes
xhr.open("GET", this.state.url, true);
// 3. Call-Back-Funktion einbinden als Lambda-Ausdrücke
xhr.onloadstart = () => {
console.log("xhr.onloadstart");
this.setState({ "status": "Load" });
};
xhr.onloadend = () => {
console.log("xhr.onloadend");
console.log("Data: " + xhr.responseText);
this.setState({ "status": "Loadend" });
this.setState({ data: xhr.responseText });
};
xhr.onprogress = () => {
console.log("xhr.onprogress");
this.setState({ "status": "OnProgress" });
;
};
// 4. Senden
xhr.send(); // bei GET xhr.send(); bei POST xhr.send(data); -> data in den RequestBody
}
// Control
handlerButtonClear(event){
this.setState({status : "Clean"});
this.setState({data : "Leer"});
}// Control handlerButtonFetch(event){ this.setState({status : "Fetch"}); this.getDataXHR(); }
render() {
return (
<div className="App">
<h1>AJAX-SWAPI</h1>
<hr/>
<div className="App">
<h1>Starwars-API</h1>
<hr/>
<button onClick={this.handlerButtonFetch}>Fetch Data</button>
<button onClick={this.handlerButtonClear}>Clear</button>
<hr/>
<div >
<div>URL: {this.state.url} </div>
<div>Status: {this.state.status} </div>
<div>Data: {this.state.data} </div>
</div>
</div>
</div>);
}
}
export default App;
Fetch ist ein alternativer Ansatz zu xhr.
Ein Promise ist das Versprechen, dass es irgendwann erfüllt wird. Es gibt drei Zustände: Erfüllt, Abgelehnt, ausstehend.
Das Fetch basiert auf einem Promise. Fetch macht eine Pipeline über AJAX-Antworten.
Wenn es gut geht, wird das Ergebnis in einen JSON-Datenstrom umgewandelt, danach kommt der nächste Befehl. Nach dem Komma werden die Fehlerfälle bearbeitet:
Fetch geht auch mit POST:
Xhr funktioniert immer noch, aber fetch ist moderner und kürzer.
Beispiel Katzen-Api:
Hier das funktionierende Katzen-Api-Beispiel-Script:
App.jsx:
import React from 'react';class App extends React.Component {
constructor(props) {
super(props);
this.state = {
breed: 'abys',
error: null,
isLoaded: false,
breedList: [],
data: []
};
this.handleGET = this.handleGET.bind(this);
this.handleChangeBreed = this.handleChangeBreed.bind(this);
}componentDidMount() { this.handleBreeds() } handleChangeBreed(event) { this.setState({ breed: event.target.value }); } handleBreeds() { fetch("https://api.thecatapi.com/v1/breeds") .then(res => res.json()) .then( (result) => { this.setState({ isLoaded: true, breedList: result }) }, (error) => { this.setState({ isLoaded: true, error }); } ) } handleGET() { console.log("Call handleGET") fetch("http://api.thecatapi.com/v1/images/search?breed_ids=" + this.state.breed) .then(res => res.json()) .then( (result) => { this.setState({ data: result }) }, (error) => { this.setState({ error }); } ) } render() { const { error, isLoaded, data } = this.state; if (this.state.isLoaded) { return ( <div> <h1>Katzen Rassen:</h1><br /> <button onClick={this.handleGET} className="btn btn-primary">Katze!</button> <select value={this.state.breed} onChange={this.handleChangeBreed}> {this.state.breedList.map(b => (<option value={b.id}>{b.name}</option>))} </select> <hr /> {data.map(d => (<div> <ul> {d.breeds.map(breed => (<div> <li>Zucht: {breed.name}</li> <li>Alternative Bezeichnungen: {breed.alt_names}</li> <li>Beschreibung: <p>{breed.description}</p> </li> <li>Lebenserwartung: {breed.life_span}</li> <li>Herkunft: {breed.origin}</li> <li>Wikipedia Link: <a href={breed.wikipedia_url} >{breed.wikipedia_url}</a></li> <li>Bild:<br /> <img alt={d.name} height={d.height * 0.3} width={d.width * 0.3} src={d.url}></img> </li> </div>))} </ul> </div> ))} </div>); } else { return ( <div> <h1>Lade...</h1> </div>); } }
}
export default App;
Übung KUG-Buch
- Katzenrassen ersetzen ducrh Book-list (Fetch URL) (Zeile 25)
- Constructore-Daten durch KUG-Daten erstzen (Array mit Bucheigenschaften)
- Unten im Renderbereich die Daten aus den Büchern auflisten
Wir wollen, basierend auf dem Katzen-API Script-Beispiel, unsere KUG-Buch Datenbank an ein React-Script anschließen. (Nach einer Woche mit morgens 2 h ausprobieren hat es dann auch endlich geklappt):
Hier das funktionierende Script von App.jsx:
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state = { book: '', error: null, isLoaded: false, bookList: [], data: [] }; this.handleGET = this.handleGET.bind(this); this.handleChangeBook = this.handleChangeBook.bind(this); } componentDidMount() { //Zugriff auf die Methode geben this.handleBooks() } handleChangeBook(event) { //Zugriff auf die Methode geben this.setState({ book: event.target.value }); } handleBooks() { //Daten holen fetch("http://localhost:8099/kugcontroller/buecherliste") .then(res => res.json()) .then( (result) => { this.setState({ isLoaded: true, bookList: result //Das komplette zurückgelieferte JSON-Objekt wird in bookList gespeichert }) }, (error) => { this.setState({ isLoaded: true, error }); } ) } handleGET() { //liest den Inhalt aus dem Auswahl-Feld aus und schreibt die gewünschten Daten in den State var booklist = (this.state.bookList); //Wir schreiben den erhaltenen JSON-String in eine lokale Variable // um besser an die Elemente zu kommen console.log("booklist = " + JSON.stringify(booklist)); console.log(this.state.bookList[0]); // Das hier geht console.log("BookList[0] = " + this.state.bookList[0]); //Das hier nicht console.log("BookList[0] = " + JSON.stringify(this.state.bookList[0])); //Das geht wieder var myinput = document.getElementById('mySelect'); // Inhalt aus mySelect-Button in myInput schreiben console.log("value = " + ReactDOM.findDOMNode(myinput).value); //Das ist die ISBN-Nummer aus dem im Feld "mySelect" ausgewählten Eintrag var isbn = ReactDOM.findDOMNode(myinput).value;booklist.forEach(element => { // Nun gehen wir durch alle Bücher in der Liste durch ... console.log("Element = " + JSON.stringify(element)); if (element.isbn === isbn) { //... und suchen das Element (Buch) mit der gemerkten ISBN-Nummer console.log("Element.isbn = " + element.isbn); console.log("Elemen.titel = " + element.titel); this.setState({ selectedTitel: element.titel }) // Wir merken uns im State den Titel, autor, jahr und preis: this.setState({ selectedAutor: element.autor }) this.setState({ selectedJahr: element.jahr }) this.setState({ selectedPreis: element.preis }) } }); this.setState({ selectedISBN: isbn }); //Wir merken uns im State die ausgesuchte ISBN-Nummer } // So sieht das Katzenbeispiel aus: /* handleGET() { console.log("Call handleGET") fetch("http://api.thecatapi.com/v1/images/search?breed_ids=" + this.state.breed) .then(res => res.json()) .then( (result) => { this.setState({ data: result }) }, (error) => { this.setState({ error }); } ) } */ render() { const { error, isLoaded, data } = this.state; // der komplette State wird in data gespeichert if (this.state.isLoaded) { return ( <div> <h1>Kug-Buch:</h1><br /> <button onClick={this.handleGET} className="btn btn-primary">Buch!</button> <select id="mySelect" value={this.state.book} onChange={this.handleChangeBook}> {this.state.bookList.map(b => (<option value={b.isbn}>{b.titel}</option>))} </select> <hr /> {data.map(d => (<div> <ul> {d.bookList.map(book => (<div> <li>Titel: {book.titel}</li> </div>))} </ul> </div> ))} {console.log("SelectedISBN = " + this.state.selectedISBN)} <div> <hr /> <h3>Buchinhalt:</h3>
BuchISBN = {this.state.selectedISBN} <br />
Buchtitel = {this.state.selectedTitel} <br />
BuchAutor = {this.state.selectedAutor} <br />
BuchJahr = {this.state.selectedJahr} <br />
BuchPreis = {this.state.selectedPreis} <br />
</div>
<hr />
ISBN-Nummer vom ersten Buch:
{this.state.bookList[0].isbn} <br />
<h3>Bücherliste:</h3>
{
this.state.bookList.map(book => (<div>
Buchname: {book.titel}
</div>))
}</div>); } else { return ( <div> <h1>Lade...</h1> </div>); } }
}
export default App;
Zusammenfassung React
------------------------------------------------------------------------------------------ Block 10 - React ------------------------------------------------------------------------------------------Bücher:
- Fullstack React: The Complete Guide to ReactJS and Friends ISBN-13: 978-0991344628; 64,30 €
- Learning React: Modern Patterns for Developing React Apps ISBN-13: 978-1492051725; 46,96 €
- React: Das umfassende Handbuch für moderne Frontend-Entwicklung.ISBN-13: 978-3836268776; 39,90 €
- React: Grundlagen, fortgeschrittene Techniken und Praxistipps – mit TypeScript und Redux ISBN-13: 978-3864905520; 34,30 €
Links:
Tutorials:
Material
- https://material-ui.com/getting-started/installation/
- https://material-ui.com/getting-started/usage/#quick-start
- https://github.com/mui-org/material-ui/tree/master/examples
Umgebung:
node -v Version
npm -v Version
npm ls -g Globes Installationsverzeichnis für die NodeJS - Module : C:\Users\joachim\AppData\Roaming\npm
Proxy-Settintgs:
User-Home-Verz:\nodejs-14.17.npmrc
npm config set proxy http://username:password@host:port
npm config set https-proxy http://username:password@host:port
Telekom-Proxy:
npm config set proxy http://...:8080/
npm config set https-proxy http://...:8080/
Überblick Arbeiten mit ReactJS-Projekten
Entwicklungsumgebung einrichten
npm install -g create-react-app
npm ls -g
Anlegen eines ReactJS-Projekts:
Installation react, react-dom, und react-scripts mit cra-template...:
npx create-react-app my-app01
Installing react, react-dom, and react-scripts with cra-template...
npm start Starts the development server.
npm run build Bundles the app into static files for production.
npm test Starts the test runner.
npm run eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back!
cd my-app01
Übersetzen und Starten der Applikation
npm start
- Übersetzen *.ts -> *.js
- Zusammenpackern webpack 5.68.0 compiled successfully in 8063 ms
- Starten eines NodeJS-Server http://localhost:3000
> my-app01@0.1.0 start
> react-scripts start
....
Starting the development server...
Compiled successfully!
You can now view my-app01 in the browser.
Local: http://localhost:3000
On Your Network: http://10.0.1.238:3000
Browser http://localhost:3000/
Projektstruktur:
tree /F
C:\...\day01\my-app
│ .gitignore
│ package-lock.json
│ package.json // -> pom.xml
│ README.md
│
├───node_modules // Maven
│
├───public // Static Web
│ favicon.ico
│ index.html // Single-Page -> DOM
│ logo192.png
│ logo512.png
│ manifest.json
│ robots.txt
│
└───src // Dynamic Web
App.css // App Componenten StyleSheet (View)
App.js // App Componenten Logik (Control /Model)
App.test.js // App Componenten Test
index.css
index.js // Verknüpfung Static Web mit Dynamic Web
logo.svg
reportWebVitals.js
setupTests.js
Single-Page: ReactJS - Applikation
public -> index.html <=> src -> index.js
Static Dynamic
<div id="root"></div> ReactDOM.render("HTML <App/>",document.getElementById('root')
src -> App.js (Funktionale Componente)
-------------------------------------------------------
function App() {
return (
.......... "HTML" .............
);
}
export default App;
Module
Global (C:\Users\joachim\AppData\Roaming\npm)
npm install -g ...................
npm install -g create-react-app
npm ls -gC:\Users\joachim\AppData\Roaming\npm ├── @angular/cli@12.2.10 ├── @popperjs/core@2.9.2 ├── babel@6.23.0 ├── bootstrap@4.6.0 ├── create-react-app@5.0.0 ├── eslint@7.26.0 ├── jshint@2.12.0 ├── jslint@0.12.1 ├── karma-cli@2.0.0 └── typescript@4.5.5
Local (Projekt-Verz./node_modules)
npx create-react-app my-app01
npm install ...................
npm lsZusätzliche Module lokal installieren
npm install react-router-dom (kap14xx)
npm install react-redux (kap15xx)
npm install react-addons-css-transition-group (kap17xx)
my-app01@0.1.0 C:\..........\block10-react\day01\my-app01 (node_modules)
├── @testing-library/jest-dom@5.16.2
├── @testing-library/react@12.1.2
├── @testing-library/user-event@13.5.0
├── react-dom@17.0.2 (Shadow-Tree)
├── react-redux@7.2.6
├── react-router-dom@6.2.1
├── react-scripts@5.0.0
├── react@17.0.2 (CORE-Funktionalität)
└── web-vitals@2.1.4
REACT sucht vom Projekt ausgehend in der Hierachie nach Open (-> C:) einen node_modules-Ordener und benutzt den ersten den er findet um zu starten
Ab react@17.0..0
VOR REACT 17.0.0 muste man SymLinks einrichten
Symbolistic-Links: (aus Projekt-Verzeichniss)
Linux / Mac / Unix
ln -s ../node_modules ./node_modules
Quelle ZielWindows
mklink /J node_modules "../node_modules"
mklink /J node_modules "../day01/node_modules"
mklink /J node_modules "../../node_modules"
Ziel Quelle
React: Funktionale-Component vs. Class-Component>
Functional Components (function NAME -> return(... React-HTML ...))
-> Stateless component
-> Small Lifecycle
Class Component (class NAME -> render(return (... React-HTML ...) ))
-> Stateful component
-> Big Lifecycle
Dateiendungen:
.ts -> *.js Funtional ES-5 function App() {
return ( "HTML");
}
.tsx -> *.jsx Klassesn ES-6 class App extends React.Component {
render() {
return ( "HTML");
}
} export default App;
jsx benötigt eine Transcription Babel d.h. erneutes übersetzen!!!!
Technologien für den Renderings-Prozess
Func-Class-Componente
//Ausgabe
render() {
return(
- React-HTML-Objekte
<p className="somevalue">This is the content!!!</p>
- <p data-myattribute="somevalue">This is the content!!!</p>- ExpressionLanguage (AusdrucksSprache) {..... Ausdruck .....} <h1>{1 + 1}</h1> <h1>{i === 1 ? 'True!' : 'False'}</h1> <h1 style = {myStyle}>Header</h1>
)
}
JavaScript Expressions
{} -> Natives JavaScript
// Kommentare in den Componenten (render() -> return())
{ /*Multi line comment...*/ }
// JS-Kommentar
<!-- HTML-Kommentar -->
Naming Convention
HTML tags always use lowercase tag names, while React components start with Uppercase.
Note: You should use className and htmlFor as XML attribute names instead of class and for.
class -> className
for -> htmlFor (<lable>)
Props und State
Componenten Eigenschaften / Parameter
- Props -> Properties die von aussen an eine Componente mitgegeben werden vgl. Parameter einer Funktion
this.props
HTML
------
<App name="Homer", age ="57"/>
Componente
-------------------
class App extends React.Component {
constructor(props) {
super(props);
console.log("Show Name-Property:" + this.props.name); // Show Name-Property; Homer
}
State -> Eigenschaften innerhalb einer Componente um StatusInformationen zu speichern
this.stateconstructor() {
// Private StateInformation // Data / Model this.state = { "name" : '', "gebjahr" : '', "alter" : '', "show" : false, "myStyle" : {display:'none'} };
}
...
// this.state.name = event.target.value; // So nicht da this.state.name da KEIN Access erlaubt ist ohne Bindung
this.setState({name : event.target.value}); // setState() -> Observable ( this.state) -> render(){}
Uebung: KehrwertRechner (Single-Page -> React)
Berechnung augelagert (Funktion oder Methode)
From: input: Zahl
button: Berechnung AufräumenOut: Zahl , Kehrwert
Fehlermeldung
LifeCycle
Funct-Componenten -> HOCs (Higher Order Components)
Class-Componenten -> component...... - Methoden
Event
Refs -> Referenzen in den React-DOM-Baum (Deprecated)
clearInput() {
this.setState({ data: '' });
//Alternative
var myinput = document.getElementById('myinput');
ReactDOM.findDOMNode(myinput).focus();
// Reference Deprecated
ReactDOM.findDOMNode(this.refs.myInput).focus();
}
render() {
return (
<div>
<input id='myinput' value={this.state.data} onChange={this.updateState}
ref="myInput"></input>
<button onClick={this.clearInput}>CLEAR</button>
<h4>{this.state.data}</h4>
</div>
);
}
Keys -> Key wird bei Collections von React-DOM-Baum-Elementen (Intern gebraucht)
Collection [][][][][][] -> React-DOM-Baum-Elementen (<li>, <tr>, <div>,....)
JavaScript React
{this.state.data.map((dynamicComponent, i) =>
<Content key={i} componentData={dynamicComponent} />)} // n - Content-Elemente
index.js:1 Warning: Each child in a list should have a unique "key" prop.
Routing
Aufteilung unserer Browser-Darstellung in mehrere Teilbereiche die sich wahlweise Umschalten lassen
Zusätzliche Module lokal installieren:
npm install react-router-dom (kap14xx)
npm ls
c:...\my-workspace\block10-react\node_modules
...
+-- react-router-dom@5.3.0 extraneo
+-- react-router@5.2.1 extraneous
index.js
import { Route, Link, BrowserRouter as Router, Switch } from 'react-router-dom'
const routing = (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/" component={App} />
<Route path="/users" component={Users} />
<Route path="/contact" component={Contact} />
<Route component={Notfound} />
</Switch>
</div>
</Router>
)
ReactDOM.render(routing, document.getElementById('app'))
Übung Routing (ueb-kreis-routing):
3 Componenten (App, Input, Output)
1 Utility-Klasse Kreisrechner
- App -> Menü zum Umschalten zwischen Input und Output
- Input -> Formular zur Erfassung des Radius
- Output -> Für die Ausgabe von Radius, Fläche, Umfang
Flux / Redux
index.js
// Create Store -------------------------------------------------------
import { createStore } from 'redux'
import counterReducer from './reducer';
//Native
const store = createStore(counterReducer)
reducer.js
const counterReducer = (state = 0, action) => { // state = 0 init des Store d.h der hält hier ein Objekt vom Type Numer (state)
switch (action.type) {
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
export default counterReducer
index.js
console.log(store)
{liftedStore: {…}, dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, …}
@@@observable: ƒ ()
dispatch: ƒ e(r)
getState: ƒ i()
liftedStore: {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, @@@observable: ƒ}
replaceReducer: ƒ replaceReducer(r)
subscribe: ƒ subscribe(listener)
[[Prototype]]: Object
console.log("Store-State: " + store.getState());
/ / -> 0
const render = () => ReactDOM.render(<Counter />, document.getElementById('root')) // Erzeugen meiner Haupt-Componente in einer Konstanten
render() // Haupt-Componente wird gerendert
store.subscribe(render); // Haupt-Componente meldet sich als Subscribe am Store an
// Create a Counter component by using the redux state. ---------------
function increment() {
store.dispatch({ type: "INCREMENT" });
console.log("Store-State: " + store.getState());
}
function decrement() {
store.dispatch({ type: "DECREMENT" });
console.log("Store-State: " + store.getState()) ;
}
const Counter = () => {
return (
<div>
<h1>{store.getState()}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
// Subscribe method ---------------------------------------------------
const builder = () => ReactDOM.render(<Counter />, document.getElementById('root'));
builder();
store.subscribe(builder);
AJAX / XHR / Promise / windo.fetch
- https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://developer.mozilla.org/de/docs/Web/API/Fetch_API
Fetch:
const data = { username: 'example' };
fetch('https://example.com/profile', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
Disclaimer
Alles was ich mitschrieb und verstanden habe ist ohne Gewähr. Die Bilder stammen teilweise aus dem Internet und wir haben keine Urheberansprüche darauf.
Besten Dank an unseren sehr empfehlenswerten
Trainer: Hans-Joachim Blanke blanke@4point.de
Man achte auf die Schwergewichtigkeit von node_modulesIch habe jetzt erst mal wieder eine Recap-Woche, aber danach geht’s weiter, so: stay tuned!
Gruß, Achim Mertens