wtorek, 2 lutego 2021

undefined vs null

Ostatnio natknęłam się na artykuł stwierdzający, że typeof null zwraca "object" z powodu błędu w JavaScript. W ciągu ostatnich 20 lat pisania w różnych językach widziałam kilka błędów (np różna kolejność tych samych parametrów w funkcjach explode i implode w starym PHP) ale ta logika jest w JavaScript od początku i jakoś nie chce mi się wierzyć, że od ponad 20 lat nikt tego nie poprawił.

Tak więc wzięłam się (po raz któryś z kolei) za czytanie dokumentacji na mdn. A tam stoi: The value null represents the intentional absence of any object value. It is one of JavaScript's primitive values and is treated as falsy for boolean operations..

Co to oznacza w praktyce? A oznacza to, że jeśli nasz obiekt (lub cokolwiek co po nim dziedziczy) nie ma wartości i jest to zamierzone (np takiego użytkownika nie ma) to używamy null. A co jeśli pracujemy ze stringiem? Wtedy zazwyczaj używamy "", żeby oznaczyć, że nie ma wartości, dla liczb dajemy 0 a dla wartości logicznych false.

Ten null dla obiektów jest bardzo przydatny. Zapis {} zwraca istniejącą wartość (jest obiekt, choć pusty), tak samo z []. Więc żeby zachować konwencję używania !zmienna obiektom ustawiamy null, gdy celowo nie mają wartości.

Z undefined historia jest trochę inna. To jest domyślna wartość używana przez JS gdy wartość czegoś nie została ustawiona (jeszcze). Warto utrzymać to rozróżnienie dla zwiększenia czytelności kodu.

typeof {} // "object"
typeof [] // "object"
typeof (() => {}) // "function"

({}) instanceof Object // true
new Object() instanceof Object // true
([]) instanceof Object // true
new Array() instanceof Object // true
(() => {}) instanceof Object // true

!!null === false // true
!null === true // true
!!"" === false // true
!"" === true // true
!!0 === false // true
!0 === true // true
Dla zainteresowanych dodatkowy artykuł undefined vs. null revisited

poniedziałek, 19 października 2020

react hooks - useState

Hooki w react pojawiły się pod koniec 2018 roku. Umożliwjają one wygodne pisanie komponentów funkcyjnych, dzielenie odpowiedzialności w kodzie oraz tworzenie małych plików - łatwych w testowaniu, utrzymaniu i wygodnych, gdy trzeba znaleźć błąd.

Najbardziej podstawowym hookiem jest useState(). Umożliwia on zarządzanie wartością stanu pomiędzy kolejnymi wywołaniami funkcji komponentu.

useState() zwraca tablicę z dwiema wartościami: stanem i funkcją do jego aktualizacji. Zgodnie z konwencją nazwa funkcji do aktualizacji powinna zaczynać się od czasownika set, czyli ustawiać. Jeśli zmienną stanu nazwiemy user to funcja powinna nazywać się setUser.
const [state, setState] = useState();

useState() przyjmuje jako parametr początkową wartość stanu
const [state, setState] = useState(4);
console.log(state); // 4

Funkcja setState przyjmuje jako parametr nową wartość stanu lub callback
const [state, setState] = useState(4);
console.log(state); // 4
setState(24);
console.log(state); // 24

Callback przyjmuje jako parametr aktualna wartość lub referencję stanu:
const [state, setState] = useState(4);
console.log(state); // 4
setState(state => state + 1);
console.log(state); // 5

Po co setState przyjmuje callback jako parametr? Po to, żeby w każdym przypadku, gdy nasz kod nie ma dostępu do aktualnego state móc jednak z niego korzystać. Dość toporny przykład pokazujący jak to działa jest z wykorzystaniem setInterval
.
import React from "react";

export default function App() {
  const [state, setState] = React.useState(0);

  function updateState() {
    setState((state || 0) + 1);
  }

  React.useEffect(() => {
    const interval = setInterval(updateState, 3000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="App">
      {console.log("rerender", state)}
      <h3>{state}</h3>
    </div>
  );
}

Powyższy przykład pokazuje ustawianie nowej wartości stanu co 3 sekundy. Niestety, stan ustawia się na 1, cały czas co 3 sekundy, ponieważ funkcja updateState jest utworzona, gdy state wynosi 0. I tak zostaje przekazana do setInterval. Co 3 sekundy setInterval zwieksza state równy 0 o 1...
zobacz na Sandbox

Sposobem na naprawienie (poza zabawą scope i clojure) jest użycie callbacku jako parametru w setState, wtedy cała funkcja bedzie wygladać tak:
zobacz na Sandbox

  function updateState() {
    setState(state => (state || 0) + 1);
  }

state przekazywany do callbacku jest tym aktualnym, wiec co 3 sekundy rośnie jego wartośc o 1.