Vytvořte si vlastní To-Do App s React

React je dnes jednou z nejpoužívanějších JavaScriptových knihoven. Usnadňuje vytváření interaktivních uživatelských rozhraní. React můžete použít k navrhování a vytváření jednoduchých i složitých aplikací. Pokud chcete, můžete jej dokonce použít k vytvoření jednostránkových webových stránek. Jinými slovy, pomocí Reactu můžete postavit téměř cokoliv. Dnes se s vámi chci podělit o tento návod, jak vytvořit aplikaci pro úkoly s Reactem. Než začneme, dám vám také trochu teorie. Doufám, že se vám tento tutoriál bude líbit.

Živá ukázka

Poznámka:Snažil jsem se vysvětlit, jak tato knihovna funguje a jak ji můžete používat. Pořád mi to vrtá hlavou. Je tedy možné, že najdete některé části, které nejsou řádně vysvětleny. Přesto doufám, že vám tento tutoriál pomůže dostat se do Reactu. Pokud ne, dejte mi vědět na twitteru. Řekněte mi, která část byla nejobtížnější na pochopení. Udělám vše pro to, abych to napravil. Děkuji mnohokrát.

Úvod

Dovolte mi začít tento příspěvek trochou teorie. Co je to vlastně React? Stručně řečeno, React je JavaScriptová knihovna vyvinutá Facebookem pro vytváření uživatelských rozhraní. Existuje již nepřeberné množství dalších JavaScriptových knihoven. V čem je tedy React jiný a proč je tak populární? React byl vyvinut softwarovými inženýry na Facebooku. Přesněji řečeno, tuto knihovnu vytvořil Jordan Walke. Inženýři na Facebooku a Instagramu jsou také těmi, kdo ji v tuto chvíli udržují. Web Instagramu ve skutečnosti běží výhradně na Reactu.

React se zaměřuje na jednoduchou a rychlou tvorbu interaktivních uživatelských rozhraní. Tato rozhraní se skládají z jednoduchých a opakovaně použitelných komponent, vlastností a stavů. První vrstva, komponenty, jsou nejmenší a základní stavební kameny Reactu. Bez komponent neexistuje uživatelské rozhraní. Druhou vrstvou jsou vlastnosti. Vlastnosti vám umožňují předávat hodnoty, které mají děti převzít. Vlastnosti fungují podobně jako atributy HTML. Tyto atributy jsou pak dostupné ve vaší komponentě jako this.props. Toto můžete použít k vykreslení dynamických dat.

Vlastnosti, stavy a události

Dětské prvky to pak mohou využít ke změně svého vlastního stavu. Můžete také uvažovat o vlastnostech jako o API pro konkrétní komponentu. Hlavním cílem vlastností je předat nějakou zprávu pro vytvoření komunikace s ostatními komponentami. Poslední vrstvou jsou stavy. Stav ovlivňuje, jak se daná komponenta chová a vykresluje. Vlastnosti a stavy mohou vypadat docela podobně. Rozdíl mezi těmito dvěma je v tom, že vlastnosti jsou definovány při vytváření komponent.

Vlastnosti jsou také viditelné a můžete je ověřit, pokud chcete. Další vrstvou jsou stavy. Na rozdíl od vlastností jsou stavy neviditelné. Stavy můžete vidět pouze uvnitř definic komponent. To také znamená, že stavy nelze ověřit. Každá komponenta v Reactu má jak stav, tak vlastnost (props). Pomocí setState můžete použít nebo nastavit různé stavy metoda. Tím se spustí specifická aktualizace uživatelského rozhraní. Za dobrou praxi se považuje použití jiné metody getInitialState k nastavení počátečního stavu před zavedením jakékoli interakce.

Reagovat a DOM

Poslední, ale neméně důležitou součástí Reactu je virtuální DOM. Virtuální DOM vykreslí komponenty, které jste vytvořili, jako podstromy uzlů. Tyto uzly jsou založeny na změnách stavu. Tento virtuální DOM je vytvořen s cílem provádět co nejmenší množství manipulace s DOM. Dovolte mi uvést jednoduchý příklad. Předpokládejme, že máte znázornění nějakého předmětu, například auta. Váš objekt by měl stejné vlastnosti jako původní auto. Mělo by stejnou barvu, tvar, technické vlastnosti a tak dále.

Virtuální DOM a provádění změn

Nyní si představte, že byste chtěli změnit jednu z těchto vlastností. Řekněme například, že se vám již nelíbí barva. Za prvé, existuje pomalejší a výkonově náročnější způsob. Můžete provést změnu a poté rekonstruovat celý objekt. Jistě, pokud chcete provést pouze jednu změnu, může vám to stačit. Pokud však chcete vytvořit něco interaktivnějšího, toto je rozhodně správná cesta. Zde přichází do hry React a virtuální DOM.

React nabízí další mnohem lepší způsob, jak provádět změny. Když se rozhodnete něco změnit, stanou se dvě věci. Nejprve React spustí algoritmus, aby zjistil, co se změnilo. Poté provede pouze tyto změny a provede je bez rekonstrukce celého objektu. Vraťme se k vašemu hypotetickému autu a změně jeho barvy. Nemusíte měnit barvu a rekonstruovat celé auto. Změníte pouze barvu a všechny ostatní části zůstanou nedotčeny.

Uvedu ještě jeden příklad z vývoje webu. Představte si, že máte formulář. Tento formulář se může měnit podle zadání uživatele. Může například přidat nová pole nebo je odstranit. Může také upravovat vstupní štítky a text. React může provést všechny tyto změny bez opětovného načítání formuláře. Můžete také zmáčknout vícestránkový formulář na jednu stránku. Potom místo změny stránek můžete použít React k vykreslení nového obsahu na aktuální stránku. To je také důvod, proč je React skvělou knihovnou pro vytváření jednostránkových aplikací.

Reagovat a jednosměrný tok dat

Poslední vrstvou prostředí React jsou události. Knihovna React je dodávána s vestavěným systémem událostí. Jde o obdobu klasických událostí, které znáte z JavaScriptu. Tyto události můžete připojit ke komponentám jako vlastnosti. Potom můžete tyto události použít ke spuštění konkrétních metod. Poslední věc, kterou byste měli vědět, je, že React funguje v nesměrovém toku. To znamená, že data proudí celým rozhraním jedním směrem. To vám dává větší kontrolu. Jak?

Všechna data, která používáte ve svém rozhraní, proudí pouze jedním směrem. Tato data proudí od rodiče k dítěti. Díky tomu můžete poměrně snadno sledovat zdroj a také cíl. To je to, co odlišuje React od jiných architektur, jako je AngularJS, kde data proudí oběma směry. To znamená, že data mohou pocházet z mnoha částí aplikace. Pamatujte, že v Reactu stejný stav vytváří stejný pohled. Výsledkem je mnohem lepší předvídatelnost.

HTML

Většina kódu použitého k vytvoření rozhraní je v JavaScriptu nebo JSX. Tento kód je poté vykreslen do kontejneru v HTML. To znamená, že náš HTML kód bude velmi krátký. V tomto návodu použijeme řadu externích aktiv. Pokud jde o šablony stylů, zahrnul jsem Bootstrap 4 alpha, font awesome a font Roboto. Všechna tato aktiva jsou hostována na CDN. Můžete je také použít ve svém vlastním příkladu. V případě JavaScriptu potřebujeme pouze dvě knihovny. První z nich je reagovat. Druhým je reagovat-dom.

Ve fragmentu kódu níže je v podstatě nejdůležitější částí appContainer div . Tento div použijeme jako kontejner k zabalení naší aplikace. Jinými slovy, naše aplikace se v něm vykreslí. Zbytek jsou běžné věci. Uvnitř hlavy je meta tag pro znakovou sadu, název a meta tag pro výřez. Pak máme tři šablony stylů. V těle je již zmíněný kontejner a dva skripty nezbytné pro spuštění naší aplikace React.

Kód:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="utf-8" />
 <meta name="viewport" description="width=device-width, initial-scale=1" />
 <title>React Todo App</title>

 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.4/css/bootstrap.min.css">

 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" />
 
 <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" />
 </head>

 <body>
  <div class="app-container" id="appContainer"></div>

  <script src="https://fb.me/react-15.1.0.js"></script>

  <script src="https://fb.me/react-dom-15.1.0.js"></script>
 </body>
</html>

CSS (SCSS)

Než začneme se stylingem, je tu jedna věc. V tomto tutoriálu budeme používat Sass, novější syntaxi, namísto prostého CSS. Pokud neznáte Sass, nebojte se. Celý kód nakonec vložím také do prostého CSS.

Kód SCSS:

// Remy function
@function remy($value, $base: 16px) {
 @return ($value / $base) * 1rem;
}

// Variables
$color-primary: #039be5;
$color-secondary: #eee;
$radius: 4px;

// Base
html {
 font-size: 16px;
}

body {
 font-size: 100%;
 font-family: 'Roboto', arial, sans-serif;
 background: #f9f9f9;
}

// Custom styles
.app-container {
 margin-top: 2rem;
 margin-right: auto;
 margin-left: auto;
 overflow: hidden;
 max-width: remy(420px);
 background: #fff;
 border-radius: $radius;
 box-shadow: 0 1px 3px rgba(0,0,0,.025), 0 3px 6px rgba(0,0,0,.065);
}

.input-group .form-control:not(:last-child) {
 padding-top: .6rem;
 padding-bottom: .6rem;
 width: 70%;
 border-bottom: 0;
 border-left: 0;
 border-color: $color-secondary;
}

.input-group-addon {
 padding-top: 10.5px;
 padding-bottom: 10.5px;
 min-width: remy(80px);
 width: 30%;
 color: #fff;
 background-color: $color-primary;
 border: 0;
 border-radius: 0;
 
 &:focus {
 outline: 0;
 }
}

.task-list {
 padding: 0;
 margin-bottom: 0;
 margin-left: 0;
 overflow: hidden;
 list-style-type: none;
}

.task-item {
 padding-top: 0.75rem;
 padding-right: 0.75rem;
 padding-bottom: 0.75rem;
 padding-left: 0.75rem;
 width: 100%;
 background: #fff;
 cursor: default;
 border-top: 1px solid $color-secondary;
 
 &:last-of-type {
 border-bottom: 1px solid $color-secondary;
 }
}

.task-remover {
 line-height: 1.45;
 color: #ddd;
 cursor: pointer;
 transition: color .25s ease-in-out;
 
 &:focus,
 &:hover {
 color: #e53935;
 }
}

Vyhovující CSS:

html {
 font-size: 16px;
}

body {
 font-size: 100%;
 font-family: 'Roboto', arial, sans-serif;
 background: #f9f9f9;
}

.app-container {
 margin-top: 2rem;
 margin-right: auto;
 margin-left: auto;
 overflow: hidden;
 max-width: 26.25rem;
 background: #fff;
 border-radius: 4px;
 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.025), 0 3px 6px rgba(0, 0, 0, 0.065);
}

.input-group .form-control:not(:last-child) {
 padding-top: .6rem;
 padding-bottom: .6rem;
 width: 70%;
 border-bottom: 0;
 border-left: 0;
 border-color: #eee;
}

.input-group-addon {
 padding-top: 10.5px;
 padding-bottom: 10.5px;
 min-width: 5rem;
 width: 30%;
 color: #fff;
 background-color: #039be5;
 border: 0;
 border-radius: 0;
}

.input-group-addon:focus {
 outline: 0;
}

.task-list {
 padding: 0;
 margin-bottom: 0;
 margin-left: 0;
 overflow: hidden;
 list-style-type: none;
}

.task-item {
 padding-top: 0.75rem;
 padding-right: 0.75rem;
 padding-bottom: 0.75rem;
 padding-left: 0.75rem;
 width: 100%;
 background: #fff;
 cursor: default;
 border-top: 1px solid #eee;
}

.task-item:last-of-type {
 border-bottom: 1px solid #eee;
}

.task-remover {
 line-height: 1.45;
 color: #ddd;
 cursor: pointer;
 -webkit-transition: color .25s ease-in-out;
 transition: color .25s ease-in-out;
}

.task-remover:focus,
.task-remover:hover {
 color: #e53935;
}

JavaScript

Než začneme psát jakýkoli kód JavaScript, měl bych zmínit jednu věc. V rámci učení ES6 jsem se rozhodl napsat tento tutoriál v této nové syntaxi. To znamená dvě věci. Za prvé, pokud nejste obeznámeni s ES6, můžete mít určité potíže. Dělám si srandu. Zahrnu také příklad zkompilovaného kódu přes babel. Tento kód je ve staré syntaxi ES5. Pokud nejste připraveni ochutnat budoucnost JavaScriptu, můžete použít toto. Druhá věc je, že pokud chcete tento kód spustit sami, budete muset použít nějaký kompilátor jako je babel. Jinak kód nemusí fungovat.

Kód ES6:

// Store app container in variable
const appContainer = document.querySelector('#appContainer');

// Create component for app header composed of input and button
const AppHead = ({addTask}) => {
 // Input Tracker
 let input;
 
 // Return AppHead component
 return (
  <div className='input-group'>
  <input ref={node => {
   input = node;
  }} className='form-control' type='text' />
  <button onClick={() => {
   addTask(input.value);
   input.value = '';
  }} className='input-group-addon'>
   Add task
  </button>
 </div>
 );
};

// Create component for new task composed of list item, text and icon
const Task = ({task, remove}) => {
 // For each task create list item with specific text and icon to remove the task
 return (
  <li className='task-item'>{task.text} <span className='fa fa-trash-o task-remover pull-right' onClick={() => {remove(task.id)}}></span></li>
 );
}

// Create component for list of tasks
const AppList = ({tasks,remove}) => {
 // Create new node for each task
 const taskNode = tasks.map((task) => {
  return (<Task task={task} key={task.id} remove={remove}/>)
 });

 // Return the list component with all tasks
 return (<ul className='task-list'>{taskNode}</ul>);
}

// Create global variable for task id
window.id = 0;

// Create main task app component
class TaskApp extends React.Component {
 constructor(prop) {
  // Provide parent class with prop
  super(prop);

  // Set initial state as empty
  this.state = {
   data: []
  }
 }
 
 // Add task handler
 addTask(val) {
  // Get the data for tasks such as text and id
  const task = {
   text: val,
   id: window.id++
  }
 
  // Update data if input contains some text
  if (val.length > 0) this.state.data.push(task);
 
  // Update state with newest data - append new task
  this.setState({
   data: this.state.data
  });
 }
 
 // Handle remove
 removeTask(id) {
  // Filter all tasks except the one to be removed
  const taskCollection = this.state.data.filter((task) => {
   if (task.id !== id) return task;
  });

  // Update state with filtered results
  this.setState({
   data: taskCollection
  });
 }

 render() {
 // Render whole App component
 // use AppHead and AppList components
 return (
  <div>
   <AppHead addTask={this.addTask.bind(this)}/>
  
   <AppList 
    tasks={this.state.data}
    remove={this.removeTask.bind(this)}
   />
  </div>
 );
 }
}

// Finally, render the whole app
ReactDOM.render(<TaskApp />, appContainer);

ES5 kód přímo od babel:

'use strict';
function _classCallCheck(instance, Constructor) {
 if (!(instance instanceof Constructor)) {
  throw new TypeError("Cannot call a class as a function");
 }
}

function _possibleConstructorReturn(self, call) {
 if (!self) {
  throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
 }
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

function _inherits(subClass, superClass) {
 if (typeof superClass !== "function" && superClass !== null) {
  throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
 }
 
 subClass.prototype = Object.create(superClass && superClass.prototype, {
  constructor: {
   value: subClass,
   enumerable: false,
   writable: true,
   configurable: true
  }
 });
 
 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

// Store app container in variable
var appContainer = document.querySelector('#appContainer');

// Create component for app header composed of input and button
var AppHead = function AppHead(_ref) {
 var addTask = _ref.addTask;

 // Input Tracker
 var input = undefined;

 // Return AppHead component
 return React.createElement(
  'div', {
   className: 'input-group'
  },
  React.createElement('input', {
   ref: function ref(node) {
   input = node;
  },
  className: 'form-control',
  type: 'text'
  }),
  React.createElement(
   'button', {
    onClick: function onClick() {
     addTask(input.value);
     input.value = '';
   },
    className: 'input-group-addon'
   },
   'Add task'
  )
 );
};

// Create component for new task composed of list item, text and icon
var Task = function Task(_ref2) {
 var task = _ref2.task;
 var remove = _ref2.remove;

 // For each task create list item with specific text and icon to remove the task
 return React.createElement(
  'li', {
   className: 'task-item'
  },
  task.text,
  ' ',
  React.createElement('span', {
   className: 'fa fa-trash-o task-remover pull-right',
   onClick: function onClick() {
    remove(task.id);
   }
  })
 );
};

// Create component for list of tasks
var AppList = function AppList(_ref3) {
 var tasks = _ref3.tasks;
 var remove = _ref3.remove;

 // Create new node for each task
 var taskNode = tasks.map(function(task) {
  return React.createElement(Task, {
   task: task,
   key: task.id,
   remove: remove
  });
 });

 // Return the list component with all tasks
 return React.createElement(
  'ul', {
   className: 'task-list'
  },
  taskNode
 );
};

// Create global variable for task id
window.id = 0;

// Create main task app component
var TaskApp = function(_React$Component) {
 _inherits(TaskApp, _React$Component);

 function TaskApp(prop) {
  _classCallCheck(this, TaskApp);

  var _this = _possibleConstructorReturn(this, _React$Component.call(this, prop));
 
  // Set initial state as empty
  _this.state = {
   data: []
  };
 
  return _this;
 }

 // Add task handler
 TaskApp.prototype.addTask = function addTask(val) {
  // Get the data for tasks such as text and id
  var task = {
   text: val,
   id: window.id++
  };

  // Update data if input contains some text
  if (val.length > 0) this.state.data.push(task);

  // Update state with newest data - append new task
  this.setState({
   data: this.state.data
  });
 };

 // Handle remove
 TaskApp.prototype.removeTask = function removeTask(id) {
  // Filter all tasks except the one to be removed
  var taskCollection = this.state.data.filter(function(task) {
   if (task.id !== id) return task;
  });

  // Update state with filtered results
  this.setState({
   data: taskCollection
  });
 };

 TaskApp.prototype.render = function render() {
  // Render whole App component
  // use AppHead and AppList components
  return React.createElement(
   'div',
   null,
   React.createElement(AppHead, {
    addTask: this.addTask.bind(this)
   }),
   React.createElement(AppList, {
    tasks: this.state.data,
    remove: this.removeTask.bind(this)
   })
  );
 };

 return TaskApp;
}(React.Component);

// Finally, render the whole app
ReactDOM.render(React.createElement(TaskApp, null), appContainer);

Závěrečné úvahy o Reactu

To je ono. Vytvořili jste svou první aplikaci s knihovnou React. Doufám, že vám tento tutoriál poskytl dostatečné množství informací. Také doufám, že tyto informace byly dostatečně praktické, abyste mohli začít.