React Quickstart: Todo-Webanwendung

Dienstag, 8. August 2017 08.08.2017 von Raphael Roeling 1 Kommentare

Wie funktioniert eigentlich React? Kann man in 15 Minuten eine Todo-Liste als Webanwendung implementieren? Aber klar! Wir zeigen Ihnen wie.

React

React ist eine 2013 von Facebook entwickelte JavaScript-Bibliothek zur Entwicklung von Webanwendungen. Entstanden ist React, da die Facebook-Entwickler ihren Frontend-Code einfacher und wartbarer machen wollten.

React bietet unter anderem:

  • Komponentenbasierte Entwicklung
  • Performance (React verwendet ein Virtual DOM und überträgt nur Unterschiede auf das echte DOM)
  • Verwendung von HTML-Tags im JavaScript-Code (optional mit Babel/JSX)

Todo-Webanwendung

Innerhalb dieses Blogposts werden wir eine Todo-Liste als Webanwendung implementieren.
Es ist kein besonderes Vorwissen erforderlich und wir werden nur das Nötigste für einen ersten Eindruck von React verwenden.

Webserver

Um anfangen zu können benötigt ihr einen beliebigen Webserver. Unter Windows lässt sich z. B. der MiniWeb HTTP server verwenden.

index.html anlegen

Als Erstes legen wir eine Datei mit dem Namen index.html und folgendem Inhalt an:

<!doctype html> 
<html> 
 
<head> 
  <title>ToDoList</title> 
  <meta charset="utf-8"> 
  <meta name="viewport" content="width=device-width, initial-scale=1"> 
</head> 
 
<body> 
  <div>Hello, World!</div> 
</body> 
 
</html> 

Falls ihr den MiniWeb HTTP server verwendet, müsst ihr die Dateien im Verzeichnis htdocs ablegen (bereits vorhandene Dateien löscht ihr zuvor).

Ruft nun eure Seite im Browser auf, z. B. localhost:8000.

app.js anlegen

Als Zweites legen wir neben der index.html eine Datei mit dem Namen app.js an.
Innerhalb der app.js definieren wir nun die erste React-Klasse. React-Klassen erzeugt man in simplem JavaScript mit React.createClass. Die render-Funktion definiert, wie die Komponente dargestellt wird.

var ToDoList = React.createClass({ 
    render() { 
        return <h1>ToDoList</h1> 
    } 
}); 

app.js einbinden

Damit wir unsere React-Komponente darstellen können, müssen wir die index.html noch etwas ausbauen.

<body> 
  <script src="https://unpkg.com/react@15.4.2/dist/react.min.js"></script> 
  <script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.min.js"></script> 
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.38/browser.js"></script> 
  <script type="text/babel" src="/app.js"></script> 
 
  <div id='root'></div> 
</body> 

React und ReactDOM benötigen wir, um React verwenden zu können, Babel, damit wir HTML-Tags und JavaScript-Code mischen können.
Das div mit der ID root tauschen wir nun noch gegen unsere React-Komponente aus, dazu erweitern wir die app.js um folgende Zeile:

ReactDOM.render(<ToDoList />, document.getElementById('root')); 

Nach dem Neuladen der Seite solltet ihr nun den Text "ToDoList" sehen.

ToDoItem

Da eine Todo-Liste natürlich Einträge benötigt, legen wir als nächstes eine Klasse namens ToDoItem an. Diese platzieren wir am Anfang der app.js.

var ToDoItem = React.createClass({ 
    render() { 
        return <li>{this.props.name}</li> 
    } 
}); 

React-Komponenten können properties (kurz: props) erhalten. Das ToDoItem erwartet die Übergabe eines Namens. Zwischen den HTML-Tags lassen sich innerhalb von geschweiften Klammern JavaScript Ausdrücke verwenden.

Nun Binden wir das ToDoItem in unsere ToDoList ein:

var ToDoList = React.createClass({ 
    render() { 
        return <div> 
            <h1>ToDoList</h1> 
            <ul> 
                <ToDoItem name='SOPTIM Blog abonnieren' /> 
            </ul> 
        </div> 
    } 
}); 

State (initial)

Da die Liste auch editierbar sein soll, kommt als nächstes der state ins Spiel. Jede React-Komponente besitzt einen state, der den gesamten Zustand der Komponente enthält.
Für die Definition des initialen Zustands ist die Funktion getInitialState() vorgesehen.

var ToDoList = React.createClass({ 
    getInitialState() { 
        return { items: ['SOPTIM Blog abonnieren'] } 
    }, 
 
    render() { 
        var items = this.state.items.map(i => (<ToDoItem key={i} name={i} />)) 
        return <div> 
            <h1>ToDoList</h1> 
            <ul> 
                {items} 
            </ul> 
        </div> 
    } 
}); 

Unser initialer Zustand enthält nun ein Array mit einem Element. Dieses Array übersetzen wir zu einem Array mit ToDoItems.

State verändern - neue Items

Unsere render-Funktion bezieht sich nun auf den state und wird sich daher anpassen, wenn wir den state verändern. Dazu fügen wir ein Textfeld hinzu, über das sich neue Einträge hinzufügen lassen.

var ToDoList = React.createClass({ 
    getInitialState() { 
        return { items: [], newItem: '' } 
    }, 
 
    addItem() { 
        if (this.state.newItem.length > 0 && this.state.items.indexOf(this.state.newItem) === -1) { 
            var items = this.state.items.slice() 
            items.push(this.state.newItem) 
            this.setState({ ...this.state, items, newItem: '' }) 
        } 
    }, 
 
    render() { 
        var items = this.state.items.map(i => (<ToDoItem key={i} name={i} />)) 
        return <div> 
            <h1>ToDoList</h1> 
            <ul> 
                {items} 
            </ul> 
            <input type='text' 
                placeholder='Add todo' 
                value={this.state.newItem} 
                onChange={e => this.setState({ ...this.state, newItem: e.target.value })} 
                onKeyDown={e => { 
                    if (e.keyCode === 13 /* ENTER */) { 
                        this.addItem() 
                    } 
                }} /> 
        </div> 
    } 
}); 

Das Textfeld wird über value (Lesen) und onChange (Schreiben) an den state gebunden. Dazu enthält unser state jetzt neben den Items auch eine neue Eigenschaft newItem. Wenn innerhalb des Textfelds Enter gedrückt wird, wird addItem aufgerufen.

addItem prüft, ob ein Text enthalten ist, erzeugt eine Kopie der aktuellen Items, fügt das neue Item hinzu und leert den Text. React ruft nach jeder state-Änderung per setState die render-Funktion erneut auf.

State verändern - Items entfernen

Damit wir auch Dinge erledigen können, sollen sich Einträge löschen lassen.

Dazu erweitern wir das ToDoItem um eine Checkbox, die bei Klick eine übergebene Funktion aufruft.

var ToDoItem = React.createClass({ 
    render() { 
        return <li><input type='checkbox' onClick={this.props.remove} /> {this.props.name}</li> 
    } 
}); 

Anschließend erweitern wir die ToDoList um eine removeItem-Funktion und übergeben jedem ToDoItem eine Funktion, die removeItem für das jeweilige Item aufruft.

var ToDoList = React.createClass({ 
 
    ... 
 
    removeItem(item) { 
        var items = this.state.items.filter(i => i !== item) 
        this.setState({ ...this.state, items }) 
    }, 
 
    render() { 
        var items = this.state.items.map(i => (<ToDoItem key={i} name={i} remove={() => this.removeItem(i)} />)) 
        ... 
    } 
}); 

Zustand im LocalStorage des Browsers speichern

Damit die Todo-Liste nicht nach jedem Neuladen leer ist, speichern wir die Items nun noch im JSON-Format in den LocalStorage des Browsers.

Dazu lesen wir die Items in getInitialState und speichern sie in addItem und removeItem.

var ToDoList = React.createClass({ 
    getInitialState() { 
        var items = [] 
        var savedItems = localStorage.getItem('items') 
        if (savedItems) { 
            items = JSON.parse(savedItems) 
        } 
        return { items, newItem: '' } 
    }, 
 
    saveItems(items) { 
        localStorage.setItem('items', JSON.stringify(items)) 
    }, 
 
    addItem() { 
        if (this.state.newItem.length > 0 && this.state.items.indexOf(this.state.newItem) === -1) { 
            var items = this.state.items.slice() 
            items.push(this.state.newItem) 
            this.saveItems(items) 
            this.setState({ ...this.state, items, newItem: '' }) 
        } 
    }, 
 
    removeItem(item) { 
        var items = this.state.items.filter(i => i !== item) 
        this.saveItems(items) 
        this.setState({ ...this.state, items }) 
    }, 
    ... 
 
}); 

UI - Bootstrap

Die Todo-Liste funktioniert nun, sieht aber nicht gut aus. Um das zu ändern, binden wir zuerst Bootstrap in die index.html ein ...

<head> 
  <title>ToDoList</title> 
  <meta charset="utf-8"> 
  <meta name="viewport" content="width=device-width, initial-scale=1"> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> 
</head>

... und erweitern anschließend unsere HTML-Elemente in den render-Funktionen um Bootstrap-CSS-Klassen.

var ToDoItem = React.createClass({ 
    render() { 
        return <li className='list-group-item'> 
            <input type='checkbox' onClick={this.props.remove} /> {this.props.name} 
        </li> 
    } 
}); 
 
var ToDoList = React.createClass({ 
 
    ... 
 
    render() { 
        var items = this.state.items.map(i => (<ToDoItem key={i} name={i} remove={() => this.removeItem(i)} />)) 
        return <div className='container'> 
            <h1>ToDoList</h1> 
            <ul className='list-group'> 
                {items} 
            </ul> 
            <input type='text' 
                className='form-control' 
                placeholder='Add todo' 
                value={this.state.newItem} 
                onChange={e => this.setState({ ...this.state, newItem: e.target.value })} 
                onKeyDown={e => { 
                    if (e.keyCode === 13 /* ENTER */) { 
                        this.addItem() 
                    } 
                }} /> 
        </div> 
    } 
}); 

Abschluss

Damit ist unsere Todo-Liste fertig. Den Code findet ihr zum Ausprobieren auch auf JSFiddle .

Die komplette index.html:

<!doctype html> 
<html> 
 
<head> 
  <title>ToDoList</title> 
  <meta charset="utf-8"> 
  <meta name="viewport" content="width=device-width, initial-scale=1"> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> 
</head> 
 
<body> 
  <script src="https://unpkg.com/react@15.4.2/dist/react.min.js"></script> 
  <script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.min.js"></script> 
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.38/browser.js"></script> 
  <script type="text/babel" src="/app.js"></script> 
 
  <div id='root'></div> 
</body> 
 
</html> 

Die komplette app.js:

var ToDoItem = React.createClass({ 
    render() { 
        return <li className='list-group-item'> 
            <input type='checkbox' onClick={this.props.remove} /> {this.props.name} 
        </li> 
    } 
}); 
 
var ToDoList = React.createClass({ 
    getInitialState() { 
        var items = [] 
        var savedItems = localStorage.getItem('items') 
        if (savedItems) { 
            items = JSON.parse(savedItems) 
        } 
        return { items, newItem: '' } 
    }, 
 
    saveItems(items) { 
        localStorage.setItem('items', JSON.stringify(items)) 
    }, 
 
    addItem() { 
        if (this.state.newItem.length > 0 && this.state.items.indexOf(this.state.newItem) === -1) { 
            var items = this.state.items.slice() 
            items.push(this.state.newItem) 
            this.saveItems(items) 
            this.setState({ ...this.state, items, newItem: '' }) 
        } 
    }, 
 
    removeItem(item) { 
        var items = this.state.items.filter(i => i !== item) 
        this.saveItems(items) 
        this.setState({ ...this.state, items }) 
    }, 
 
    render() { 
        var items = this.state.items.map(i => (<ToDoItem key={i} name={i} remove={() => this.removeItem(i)} />)) 
        return <div className='container'> 
            <h1>ToDoList</h1> 
            <ul className='list-group'> 
                {items} 
            </ul> 
            <input type='text' 
                className='form-control' 
                placeholder='Add todo' 
                value={this.state.newItem} 
                onChange={e => this.setState({ ...this.state, newItem: e.target.value })} 
                onKeyDown={e => { 
                    if (e.keyCode === 13 /* ENTER */) { 
                        this.addItem() 
                    } 
                }} /> 
        </div> 
    } 
}); 
 
ReactDOM.render(<ToDoList />, document.getElementById('root'));

Weiterführende Links

  • React: Dokumentation und Tutorials
  • webpack: Module Bundler, der euch z. B. ermöglicht, euren Code in Dateien/Ordner aufzuteilen
  • Babel: JavaScript Compiler, der euch durch Plugins ermöglicht, z. B. JSX und Features aktueller ECMAScript-Versionen zu nutzen

Kommentare

Heiner Halbach schrieb am Donnerstag, 10. August 2017, 14:15 Uhr:
Vielen Dank für die Hilfe zum "Quickstart" - sehr schöner anschaulicher Beitrag.

Kommentar schreiben

Ihr Kontakt Stephanie Lemken Was kann ich für Sie tun?
+49 241 400 230

Direkt anrufen

Verwendung von Cookies

Cookies ermöglichen eine bestmögliche Bereitstellung unserer Dienste. Mit der Nutzung der SOPTIM-Seiten erklären Sie sich damit einverstanden, dass wir Cookies verwenden.

Weitere Informationen erhalten Sie in der Datenschutzerklärung
OK, verstanden