ES6 dla programistów Rails
Jeśli twoja wiedza o JavaScripcie zatrzymała się na jQuery, nowoczesny JavaScript (ES6) wyda się zaskakująco bliski Ruby. Przegląd funkcji, które mają znaczenie — let/const, funkcje strzałkowe, destrukturyzacja, klasy, moduły i promise'y — oczami rubisty.
Wielu programistów Rails ma lekko niespokojny stosunek do JavaScriptu: nauczyli się ledwie tyle jQuery lata temu, by sobie radzić, i obserwowali transformację frontu z bezpiecznej odległości. Dobra wiadomość jest taka, że nowoczesny JavaScript — ES6, właściwie ES2015, teraz szeroko używalny przez Babela — domknął większość przepaści, która czyniła język wrogim, a sporo z niego wyda się przyjemnie znajome rubiście. Oto przegląd funkcji, które mają znaczenie, ujęty dla kogoś, kto myśli w Ruby.
let i const: zasięg blokowy nareszcie
Pierwsza poprawka adresuje najbardziej osławioną brodawkę JavaScriptu: var ma zasięg
funkcyjny i jest hoistowany, co produkuje naprawdę zaskakujące błędy. ES6 zastępuje go przez
let (zasięg blokowy, przypisywalny ponownie) i const (zasięg blokowy, nieprzypisywalny
ponownie):
const TAX_RATE = 0.2; // nie można przypisać ponownie
let total = 0; // zasięg blokowy, może się zmieniać
for (let i = 0; i < items.length; i++) { /* i jest zasięgowane do pętli */ }
Reguła, która się wyłoniła i której się trzymamy: używaj const domyślnie, let, gdy
musisz przypisać ponownie, a var nigdy. Rubista uzna to za komfortowe — const jest
twoim niemal-domyślnym wyborem, tak jak sięgasz po niemal-niezmienne wartości w Ruby, a
zasięg blokowy zachowuje się tak, jak oczekuje twoja intuicja z bloków if/do.
Funkcje strzałkowe: krótsze i naprawiają this
Funkcje strzałkowe są zwięźlejsze i, co kluczowe, rozwiązują niesławny problem this w
JavaScripcie. Zwykła function przepina this w zależności od tego, jak jest wołana —
źródło niezliczonych błędów „czemu this jest undefined?” w callbackach. Funkcja strzałkowa
dziedziczy this z otaczającego zasięgu:
// zwięźle, a `this` to otaczający obiekt, nieprzepięty
const doubled = numbers.map(n => n * 2);
button.addEventListener("click", () => {
this.handleClick(); // `this` jest tym, czego oczekujesz, nie przyciskiem
});
Dla rubisty numbers.map(n => n * 2) czyta się niemal dokładnie jak
numbers.map { |n| n * 2 } — model myślowy „przekaż małą funkcję do metody kolekcji”
przenosi się wprost.
Literały szablonowe: interpolacja łańcuchów
JavaScript ma wreszcie interpolację łańcuchów i działa dokładnie jak w Ruby, z backtickami i
${} zamiast podwójnych cudzysłowów i #{}:
const greeting = `Cześć, ${user.name}! Masz ${cart.size} produktów.`;
// wieloliniowe też działa, koniec z "linia jeden\n" + "linia dwa"
Po latach konkatenacji "Cześć, " + user.name + "!" samo to czyni język przyjemniejszym. To
bezpośredni odpowiednik "Cześć, #{user.name}!".
Destrukturyzacja: rozkładanie wartości
Destrukturyzacja pozwala rozpakować tablice i obiekty na zmienne w jednym wyrażeniu — podobnie do wielokrotnego przypisania Ruby, ale rozszerzone na obiekty (hashe):
const [first, second] = items; // jak a, b = items w Ruby
const { name, email } = user; // wyciągnij klucze z obiektu
const { id, ...rest } = params; // id, plus wszystko inne w `rest`
function greet({ name, locale = "en" }) { // destrukturyzacja + default w sygnaturze
// ...
}
Destrukturyzacja obiektów w parametrach funkcji jest szczególnie użyteczna — to javascriptowy odpowiednik argumentów nazwanych Ruby, dający nazwane, samodokumentujące się parametry z defaultami.
Klasy i moduły: struktura
ES6 dodaje składnię class — lukier składniowy nad systemem prototypów JavaScriptu, ale
czyta się jak klasa Ruby i czyni typowy przypadek jasnym:
class ShoppingCart {
constructor(items = []) {
this.items = items;
}
get total() {
return this.items.reduce((sum, i) => sum + i.price, 0);
}
add(item) { this.items.push(item); }
}
A moduły dają JavaScriptowi prawdziwe importy i eksporty — jawne zależności i zasięgowanie, fundament czyniący graf zależności Webpacka możliwym:
// cart.js
export class ShoppingCart { /* ... */ }
export const FREE_SHIPPING = 5000;
// app.js
import { ShoppingCart, FREE_SHIPPING } from "./cart";
To struktura, której tak długo brakowało. Rubista rozpozna instynkt: każdy plik to jednostka z jasnym publicznym interfejsem, jak dobrze rozłożona klasa lub moduł Ruby, zamiast wszystkiego dzielącego jedną globalną przestrzeń nazw.
Promise’y: ujarzmianie kodu asynchronicznego
Najtrudniejszym skokiem koncepcyjnym dla programisty po stronie serwera jest asynchroniczność JavaScriptu. Wywołania sieciowe nie blokują; kończą się później, a stary sposób ich obsługi — zagnieżdżone callbacki — produkował niesławną „piramidę zagłady”. Promise’y spłaszczają to w czytelny łańcuch:
fetch(`/api/products/${id}`)
.then(response => response.json())
.then(product => render(product))
.catch(error => showError(error));
Promise reprezentuje „wartość, która przyjdzie później”, z .then dla sukcesu i .catch dla
porażki. To część bez bezpośredniego odpowiednika w Ruby — kod Ruby w większości działa od
góry do dołu i blokuje na I/O — więc warto poświęcić jej realny czas. (Jeszcze czystsza
składnia async/await nadchodzi, czyniąc kod asynchroniczny czytanym jak synchroniczny;
warto wiedzieć, że jest w drodze.)
Mniejsze wygrane
Dopełniając: domyślne parametry (function f(x = 1)), rest i spread (...args, by
zebrać, [...a, ...b], by rozwinąć — jak splat w Ruby) i helpery Array/Object, które
czynią pracę z kolekcjami bardziej jak Enumerable Ruby. Każdy usuwa małą drzazgę, o którą
rubista kiedyś się potykał.
Werdykt
Jeśli twój JavaScript utknął w epoce jQuery, ES6 to moment, gdy język staje się przyjemny do
pisania — a sporo z niego mapuje się czysto na instynkty, które już masz z Ruby. const
domyślnie, funkcje strzałkowe dla zwięzłych callbacków z rozsądnym this, literały szablonowe
dla interpolacji, destrukturyzacja dla nazwanych parametrów, klasy i moduły dla struktury.
Jedynym naprawdę nowym modelem myślowym jest asynchroniczność — promise’y (i wkrótce
async/await) — która nie ma odpowiednika w Ruby i zasługuje na realne studium. Naucz się
ich, oprzyj na Babelu (albo Webpackerze), by uruchamiać je wszędzie, a front przestaje czuć
się jak obcy kraj, a zaczyna jak język, w którym możesz być produktywny. Dla programisty Rails
w 2017 ta płynność nie jest już opcjonalna — i na szczęście jest bliżej domu, niż myślisz.