Skip to content
← Wszystkie wpisy
5 min czytania Dawid Skłodowski

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.