/ Learning Log

Learning Log - Week #1

Ten tweet Davida Walsh'a zainspirował mnie do tego, żeby zacząć podsumowywać to, czego nauczyłem się przez ostatni tydzień. Wiele razy zdarzała mi się chwila załamania, kiedy to myślałem - kurde...ciąglę klepię to samo...zero progresu, stoję w miejscu, może to nie dla mnie... Z drugiej strony nie doceniałem wielu małych kroczków, dzięki którym zdobywałem coraz to większe doświadczenie. Mam nadzieję, że kiedyś w przyszłości zrobię z tego typu wpisów podsumowanie miesiąca, albo roku; pozytywnie się zaskoczę i zmotywuję do dalszego działania ;) ...

Week #1

  1. .filter(Boolean)
  2. Public class fields as event handlers
  3. cypress.io
  4. Introduction to Firebase
  5. Google I/O ->
    • what's new in chrome dev tools
    • headless-chrome
    • PWA starter KIT
    • Performance in web app

.filter(Boolean)

Jak odfiltrować listę z "falsy" elementów ?

W 99% projektów, w których brałem udział dostępny był underscore bądź lodash, które wystawiały metodę _.compact.

Implementacja lodasha wygląda tak:

function compact(array) {
    var index = -1,
    length = array == null ? 0 : array.length,
    resIndex = 0,
    result = [];

    while (++index < length) {
        var value = array[index];
        if (value) {
          result[resIndex++] = value;
        }
    }
    
    return result;
}

Nieco przydługawa co nie ? ;)

Na jednym z code-review widziałem też konstrukcję typu: .filter(val => !!val).

Minusem tego podejścia jest to, że za każdym razem tworzy nową funkcję do filtrowania. Można by ją sobie gdzieś zapisać w module i przekazywać przez referencję ale nakłada to boilerplate związany z importem.

import { filterOutFalsy } from "utils";
const a = [...].filter(filterOutFalsy);

Najkrótszym sposobem bez ładowania zależności i tworzenia nowej funkcji filtrującej jest użycie konstruktora Boolean :)

const a = ["", false, null, "123", 123];
a.filter(Boolean) // ["123", 123];

Mała rzecz a cieszy ;)

Public class fields as event handlers

Swoją przygodę z Reactem zacząłem jakoś w 2014 roku od wersji 0.12.x. Pamiętam, że jeszcze nie używaliśmy do tego JSX'a....katorga... :) Jednym z feature'ów tamtej wersji był auto-binding event handlerów. Component tworzyło się za pomocą React.createClass - reszta była magią.

define(["react"], function (React) {

    var MyComponent = React.createClass({
    
        onClick: function() {
            console.log(this); // Current instance of the component
        }
    
        render: function () {
            return (
                <div onClick={ this.onClick }>Click me</div>
            );
        }
    });

});

React.createClass w połączeniu z requireJs

W kolejnych wersjach React.createClass został uznany jako deprecated. Za to pojawiły się componenty funkcyjne oraz class syntax sugar, który pozwala "podziedziczyć" z React.Component. Problem w tym, że teraz event handlery nie są automatycznie bindowane.

import React, { Component } from "react"

class MyComponent extends Component {

    onClick(event) {
        console.log(this); // undefined
        console.log(event); // Event object
    }

    render() {
        return (
            <div onClick={ this.onClick }>Click me</div>
        );
    }

}

undefined is not an object

W kursach Reacta często spotkać możemy taki zapis:

<div onClick={ (e) => this.onClick(e) }>Click me</div>

arrow function jako event handler

Ale jest pewien problem. Arrow function, którą przekazujemy jako event handler, przy każdym renderze tworzona jest na nowo. Powoduje to niepotrzebne prze-renderowanie elementu w DOM. React bazuje na VirtualDOM i porównuje sobie zserializowane objekty. Do porównania bierze także props'y danego węzła/komponentu. Przy kolejnym renderze React widzi, że poprzedni onClick nie jest tą samą funkcją i powoduje przerenderowanie węzła. Dla lepszego zobrazowania sytuacji polecam ten artykuł. Oczywiście dla pojedynczych komponentów nie ma problemu; nie boli to tak bardzo...ale gdy wyświetlacie listę np. 100 elementów i każdy z nich ma taki handler to przy każdej zmianie wszystkie elementy zostną niepotrzebnie prze-renderowane.

No to "jak żyć" ? Do tej pory używałem bind w konstruktorze:

import React, { Component } from "react"

class MyComponent extends Component {

    constructor() {
        super();
        this.onClick = this.onClick.bind(this);
    }

    onClick(event) {
        console.log(this); // current instance of the component
    }

    render() {
        return (
            <div onClick={ this.onClick }>Click me</div>
        );
    }

}

bind w kontruktorze

Ale jest lepszy/bardziej czytelny/krótszy w zapisie sposób - public class fields!

class MyComponent extends Component {

    onClick = (event) => {
        console.log(this); // current instance of the component
    }

    render() {
        return (
            <div onClick={ this.onClick }>Click me</div>
        );
    }

}

wykorzystanie public class fields

Mniej kodu - ten sam efekt :)

cypress.io

Co jakiś czas dostaję maile od egghead.io z informacją o nowym kursie. Tym razem jajogłowi przygotowali kurs cypress.io, biblioteki do testów end-to-end. Postanowiłem sprawdzić o co chodzi i wbiłem się w ciężki szok.

Nie mogę oprzeć się wrażeniu, że pisanie testów end-to-end z wykorzystaniem Selenium to dla wielu developerów droga przez mękę. Mamy tego webdrivera, mamy do niego bindingi w różnych językach programowania, mamy wrappery ale gdy przychodzi do odpalenia testów często dostajemy false-positivy, albo informacje, że danego elementu na stronie nie ma chociaż widzimy go gołym okiem. O co chodzi ? A no, chodzi o to że wszystkie rozwiązania bazujące na Selenium opierają się na komunikacji z przeglądarką przez protokół sieciowy.

Cypress.io jest inny. Nie bazuje na Selenium, a na własnym silniku. W trakcie testu mamy bezpośredni dostęp do wszystkich API przeglądrki np. service-workerów, web-workerów etc. Posiada wbudowany time-travelling - oznacza to, że każdy krok testu zapisywany jest jako snapshot i możemy do niego wrócić, podejrzeć w którym miejscu strony został odpalony click, co zmieniło się w drzewie DOM. Mamy większą kontrolę nad aplikacjami typu SPA - gdzie content doładowywany jest dynamicznie. Poza tym cyperss.io pozwala na robienie zrzutów ekranu i nagrywanie testu w postaci video ! Pierwsze co przychodzi mi do głowy to prezentacja przyrostu na koniec sprintu. Piszemy sobie funckjonalność i od razu testy end-2-end. Odpalamy testy i nagrywamy video. Przed demem tylko obrabiamy video i voil'a - prezentacja przyrostu jak ta lala :)

Zobaczcie, jak cypress.io wygląda w praktyce:

Do minusów można zaliczyć jedynie wsparcie dla przeglądarek. Obecnie cypress.io odpalimy "tylko" na:

  • Canary
  • Chrome
  • Chromium
  • Electron

Wsparcie dla FireFox'a, Safari i IE jest w planach.

Myślę, że przy najbliższej okazji nie omieszkam wykorzystać cypress.io do testów E2E.

Introduction to Firebase

O Firebase pierwszy raz usłyszałem w kontekście Progressive Web Apps. Przykład, który pokazywał jak zaimplementować Web-Push-Notfications opierał się właśnie na Firebase. Postanowiłem temat zgłębić i znów pozytywnie się zaskoczyłem.

Firebase jest produktem typu BaaS - Backend as a Service. Jesteś WebDeveloperem, z podstawami node.js'a ale o infrastrukturze nie masz pojęcia ? Nie ma sprawy - Firebase zrobi to za Ciebie. Real-time-data-base, authorization flow, skalowanie aplikacji, CDN i wiele innych. Do tej pory zapoznałem się z Real-Time-Database i podstawowym mechanizmem autoryzacji użytkowników. Spędziłem jakąś godzinkę na przeglądaniu oficjalnych video-tutoriali i wiem, że następny projekt będę stawiał właśnie na rozwiązaniu od Googla :) Nigdy więcej ręcznego konfigurowania vps'a i martwienia się o wydajność! Firebase zrobi to za mnie... taką mam nadzieję...:)

Google I/O

W tym tygodniu odbyła się również konferencja Google I/O. I chociaż nie oglądałem streamów na żywo, to w bardzo krótkim odstępie czasu większość prezentacji wylądowała się na oficjalnym kanale youtube.

Z prezentacji Paula Irish'a dowiedziałem się o tym, co nowego znalazło się w Chrome-Developer-Tool'sach. Np. Eager Evaluation, które polega na wywołaniu wyrażenia wpisanego do konsoli i wyświetleniu resulatatu bezpośrednio pod wyrażeniem. Nie musimy przy tym zatwierdzać komendy, aby zobaczyć wynik. Po więcej odsyłam do live demo.

Monica Dinculescu opowiadała o PWA starter kit. Narzędziu, dzięki któremu tworzenie aplikacji PWA to bułka z masłem :). Nie musimy martwić się o wybór frameworka, biblioteki do zarządzania service-workerem, RWD. Wszystkie "najlepsze praktyki" są już włączone do projektu i możemy z nich korzystać. Jeśli chcemy coś wymienić - nie ma problemu! Projekt ma za zadnie ułatwić szybki start do dalszego rozwoju aplikacji PWA.

Addy Osmani wraz z Ewą Gasperowicz opowiadali o wydajności web aplikacji i jak tą wydajność poprawić. Kilka ciekawych tricków na pewno wykorzystam!

O Headless-Chrome i przypadkach jego użycia opowiadał Eric Bidelman. Dzięki jego prezentacji dowiedziałem się że Chrome-Dev-Tools'y udostępniają protokół po którym możemy się łączyć i zdalnie wywoływać komendy. Eric pokazał też czym jest Puppeter i jak z niego korzystać do automatyzacji. Testy A/B, generowanie PDFów, code coverage, server side rendering to tylko kilka z przykładów jak wykorzystać headless-chrome.

Podsumowanie

Jak widzicie 6 z pozoru małych rzeczy - małych kroków ku zdobywaniu większego doświadcznia :). Tylko tyle i aż tyle...

Widzimy się za tydzień !