Articolo originale: How to Prepare for React Interviews – Front-End Technical Interview Guide

Un colloquio tecnico con argomento front-end è una opportunità per un potenziale datore di lavoro per accertare le tue capacità e conoscenze riguardo allo sviluppo web.

L'intervistatore ti farà domande in merito alla tua esperienza e abilità in HTML, CSS e JavaScript. Probabilmente ti verranno poste anche alcune domande specifiche sui framework come React, Angular, Vue o qualsiasi altro framework venga usato.

Potrebbe anche porti un problema di programmazione da risolvere per verificare la tue capacità in un particolare campo.

Oggi esamineremo i problemi più comuni discussi nei colloqui tecnici per sviluppo front-end, concentrandoci su React e Javascript.

Cosa Cercano gli Intervistatori

Quando vai a un colloquio per un posto come sviluppatore web front-end, preparati a discutere delle tue capacità ed esperienza con diversi linguaggi di programmazione, strumenti e framework.

Gli intervistatori vorranno anche vedere se hai una solida comprensione delle ultime tendenze e tecnologie di sviluppo web.

Preparati a parlare dei tuoi progetti precedenti e di come hai affrontato la risoluzione di vari problemi.

Assicurati di mostrare le tue capacità di risoluzione dei problemi discutendo come hai affrontato le diverse sfide durante il tuo processo di sviluppo.

Infine, non dimenticare di evidenziare i tuoi punti di forza.

Le Domande Più Comuni Poste in un Colloquio Tecnico per Front-End

I problemi proposti nei colloqui tecnici relativi al front-end sono piuttosto semplici e comuni. Se hai scritto codice per almeno 6 mesi, ti sarà familiare la maggior parte dei concetti che ti verrà chiesto di esporre.

Se ti eserciti con le giuste domande tenendo anche conto di un limite di tempo, dovresti essere in grado di superare il colloquio.

Esaminiamo le domande che vengono poste più di frequente.

Map, ForEach, Filter e Reduce

Le domande più comunemente poste (in genere all'inizio del colloquio) vertono sui metodi degli array. L'intervistatore vuole accertarsi che tu sia a tuo agio con le manipolazione degli array.

Il metodo .map()

Il metodo .map() esegue iterazioni su un array, calcola qualsivoglia logica tu scriva nel corpo della funzione map e restituisce un NUOVO array.

let arr = [
  { id: 1, age: 12, name: 'Manu' },
  { id: 2, age: 24, name: 'Quincy' },
  { id: 3, age: 22, name: 'Abbey' },
]

let names = arr.map((el) => el.name)
console.log(names)
// Risultato: [ 'Manu', 'Quincy', 'Abbey' ]

Il metodo .forEach()

.forEach è simile a .map() ma NON restituisce un array.

let arr = [
  { id: 1, age: 12, name: 'Manu' },
  { id: 2, age: 24, name: 'Quincy' },
  { id: 3, age: 22, name: 'Abbey' },
]

arr.forEach((el) => el.age+= 10);
console.log(arr);

// Risultato: 
// [
//   { id: 1, age: 22, name: 'Manu' },
//   { id: 2, age: 34, name: 'Quincy' },
//   { id: 3, age: 32, name: 'Abbey' }
// ]

Il metodo .filter()

Questo metodo, come suggerisce il suo nome in inglese, aiuta a filtrare i valori all'interno di un array in base a una condizione booleana.

Se la condizione booleana è true, l'elemento viene ritornato e aggiunto all'array finale. Se la condizione è false l'elemento viene ignorato. Come il metodo .map(), anche .filter() restituisce un nuovo array.

let arr = [
  { id: 1, age: 12, name: 'Manu' },
  { id: 2, age: 24, name: 'Quincy' },
  { id: 3, age: 22, name: 'Abbey' },
]

let tooYoung = arr.filter((el) => el.age <= 14);
console.log(tooYoung);

// Risultato: [ { id: 1, age: 12, name: 'Manu' } ]

Il metodo .reduce()

In termini semplici, .reduce() gestisce un valore precedente, un valore corrente e un accumulatore.

Il tipo restituito da .reduce() è sempre un valore singolo. Utile quando si vogliono elaborare tutti i valori in un array per ricavare un risultato cumulato.

// Calcola l'età totale delle tre persone.
let arr = [
  { id: 1, eta: 12, name: 'Manu' },
  { id: 2, eta: 24, name: 'Quincy' },
  { id: 3, eta: 22, name: 'Abbey' },
]

let sommaEta = arr.reduce((accumulatore, oggettoCorrente) => accumulatore + oggettoCorrente.eta, 0)
console.log(sommaEta)

// Risultato: 58

Qui oggettoCorrente è l'oggetto che viene di volta in volta elaborato, accumulatore conserva il risultato che si accumula a ogni iterazione, che viene restituito alla fine nella variabile sommaEta . L'ultimo parametro (facoltativo, predefinito 0) della funzione rappresenta il valore di partenza per accumulatore.

Come Implementare i Polyfill

Un'altra importante domanda è come è come implementare i polyfill sui metodi di array map() e filter().

Un polyfill è un frammento di codice (nei termini di architettura web JavaScript) usato per dotare di funzionalità recenti vecchie versioni di browser che non le implementano nativamente.

In poche parole, un polyfill è una implementazione personalizzata di funzioni Javascript native. Un po' come creare una tua versione dei metodi .map() o .filter().

Come usare il polyfill .map()

let data = [1, 2, 3, 4, 5];

Array.prototype.myMap = function (cb) {
  let arr = [];
  for (let i = 0; i < this.length; i++) {
    arr.push(cb(this[i], i, this));
  }
  return arr;
};
const mapLog = data.myMap((el) => el * 2);
console.log(mapLog);

Il metodo myMap riceve come argomento un callback che viene eseguito all'interno del corpo di myMap. Praticamente abbiamo un ciclo for all'interno del corpo di myMap che itera su this.length, che non è altro che la lunghezza dell'array dal quale viene chiamata la funzione myMap.

Visto che la sintassi di map() è arr.map(elementoCorrente, indice, array), la funzione myMap() tiene conto esattamente di questo.

Inoltre visto che map() ritorna un nuovo array, creiamo un array vuoto e vi inseriamo i risultati. Alla fine lo restituiamo.

Come usare il polyfill .filter()

let data = [1, 2, 3, 4, 5];

Array.prototype.myFilter = function (cb) {
  let arr = [];
  for (let i = 0; i < this.length; i++) {
    if (cb(this[i], i, this)) {
      arr.push(this[i]);
    }
  }
  return arr;
};
const filterLog = data.myFilter((el) => el < 4);
console.log(filterLog);

.filter() è molto simile a .map() per quanto riguarda l'implementazione. Visto che filter filtra il risultato in base a una condizione booleana, abbiamo un costrutto if() supplementare per filtrare i risultati e inserirli nell'array da restituire se la condizione è soddisfatta.

Cos'è il Debouncing?

Questa è una domanda famosa con molti utilizzi e implementazioni pratiche nel mondo reale.

Debouncing è un metodo per far sì che una funzione non sia chiamata troppo spesso, attendendo un certo periodo di tempo rispetto all'ultima chiamata prima di invocarla nuovamente.

Pensa al sito di Amazon in questo caso. Ogni volta che digiti qualcosa nella casella di ricerca, quando ti fermi per ALMENO mezzo secondo, allora vengono restituiti i risultati. Questo è esattamente quello che si intende per debouncing.

Per implementare il debouncing, facciamo un esempio: generiamo uno username in base a quanto digitato dall'utente.

import "./styles.css";
let inputEle = document.getElementById("inputElement");
let username = document.getElementById("username");

let generateUsername = (e) => {
  username.innerHTML = e.target.value.split(" ").join("-");
};
let debounce = function (cb, delay) {
  let timer;
  return function () {
    let context = this;
    clearTimeout(timer);
    timer = setTimeout(() => {
      cb.apply(context, arguments);
    }, delay);
  };
};

inputEle.addEventListener("keyup", debounce(generateUsername, 300));

Qui stiamo cercando di creare uno username personalizzato in base all'input dell'utente. Se l'utente inizia a digitare, non vogliamo creare lo username immediatamente, ma attendiamo 300 millisecondi prima di farlo. Stiamo cercando di imitare una chiamata API qui, pertanto assumi che l'utente digiti qualcosa e che debba fare una chiamata API al backend per ottenere la risposta.

La funzione debounce() riceve due parametri cb e delay. cb è la funzione callback che viene eseguita quando scade il tempo.

Usiamo setTimeout() per creare un timer, il che vuol dire che la funzione all'interno del corpo di setTimeout() verrà eseguita dopo un determinato tempo.

Il metodo apply viene utilizzato per chiamare la funzione di callback con l'oggetto con il quale è stata inizialmente chiamata, applicando all'oggetto gli argomenti e il contesto.

Cosa sono le Closure?

Secondo la  documentazione mdn per le closure, qui tradotta:

Una closure è la combinazione di una funzione legata insieme (chiusa) ai riferimenti al suo stato circostante (l'ambiente lessicale). In altre parole una closure ti fornisce accesso all'ambito di una funzione più esterna da una funzione interna. In JavaScript le closure sono create ogni volta che viene creata una funzione, nella fase di creazione della funzione.

Per semplificare facciamo un esempio per capire il funzionamento di una closure.

function start() {
  var name = "Manu"; // name è una variabile locale creata da start()
  function displayName() {
    // displayName() è la funzione più interna, una `closure`
    alert(name); // usa la variabile dichiarata nella funzione genitore
  }
  displayName();
}
start(); // "Manu" viene visualizzato nell'alert box

Qui una closure viene formata tra le funzioni start() e displayName(). La funzione displayName() ha accesso alla stessa variabile name presente nella funzione start().

In termini semplici, la funzione interna conoscerà l'ambiente circostante (l'ambiente lessicale).

Ho scritto un intero blog su come superare i colloqui su JavaScript (risorsa in inglese). Puoi dargli un'occhiata se vuoi approfondire l'argomento.

Hook di React

Per quanto riguarda l'argomento hook di React le domande più popolari poste in un colloquio per uno sviluppatore front-end riguardano:

  1. useState()
  2. useReducer()
  3. useEffect()
  4. useRef()
  5. hook personalizzati e loro implementazione.

Come funziona l'hook useState()

Per gestire uno stato all'interno del tuo componente, useState() è l'hook che ti serve.

Vediamo un esempio per capire:

import { useState } from "react";
import "./styles.css";

export default function App() {
  const [title, setTitle] = useState("freeCodeCamp");
  const handleChange = () => {
    setTitle("FCC");
  };
  return (
    <div className="App">
      <h1>{title} useState</h1>
      <button onClick={handleChange}>Change Title</button>
    </div>
  );
}

useState() restituisce due valori: una variabile che contiene il valore dello stato (title nell'esempio), e una funzione per cambiare il valore dello stato (setTitle nell'esempio).

Nel codice qui sopra abbiamo creato uno stato chiamato title  per conservare il titolo della pagina. Il valore iniziale passato è "freeCodeCamp".

Quando si fa click sul bottone "Change Title", possiamo usare la funzione setTitle() per modificare la variabile dello stato title in "FCC".

useState() è la tua risorsa di riferimento per la gestione dello stato in un componente funzionale.

Come funziona l'hook useReducer()

In parole povere, useReducer() è un modo interessante per gestire lo stato nella tua applicazione. È più strutturato e aiuta a mantenere stati complessi nella tua applicazione.

Ecco un esempio per comprendere l'hook useReducer:

import "./styles.css";
import { useReducer } from "react";

const initialState = { title: "freeCodeCamp", count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "change-title":
      return { ...state, title: "FCC" };
    case "increment-counter":
      return { ...state, count: state.count + 1 };
    default:
      throw new Error();
  }
}

export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <div className="App">
        <h1>{state.title} CodeSandbox</h1>
        <button onClick={() => dispatch({ type: "change-title" })}>
          Change Title
        </button>
        <button onClick={() => dispatch({ type: "increment-counter" })}>
          Increment Counter
        </button>
      </div>
      <p style={{ textAlign: "center" }}>{state.count}</p>.
    </>
  );
}

L'hook useReducer() riceve due parametri, la funzione (parametro reducer) che verrà utilizzata per la gestione dello stato e un valore di stato iniziale (parametro initialState).

La funzione reducer è una implementazione basata so uno switch-case che ritorna lo stato finale del valore che useReducer() usa internamente per fornirlo al componente.

I valori ritornati da useReducer() sono state e dispatch. state è l'effettivo valore dello stato che può essere usato all'interno del componente. Nel nostro caso, lo stato ha due valori: title e count. Questi valori possono essere manipolati usando il metodo dispatch() che viene ritornato da useReducer().

Nel caso qui sopra, per modificare il titolo abbiamo gestito un caso chiamato change-title nell'istruzione di switch. Questo caso è stato attivato tramite dispatch({ type: "change-title" }), che modificherà lo stato, in particolare il valore di title .

Alla stessa stregua la procedura viene seguita per aumentare il contatore count visualizzato nell'applicazione gestendo il caso increment-counter.

Come detto in precedenza, si tratta di un modo interessante per implementare la gestione dello stato nella tua applicazione. 😉

Come funziona l'hook useEffect()

Mettila così: se vuoi ottenere un effetto collaterale rispetto a una variabile di stato quando il suo valore viene modificato, puoi usare l'hook useEffect() per attivarlo.

Ad esempio diciamo che il valore di una casella di input cambia e vuoi chiamare un'API dopo che il valore viene modificato. Puoi scrivere la logica per la gestione dell'API all'interno di useEffect().

import React, {useState, useEffect} from 'react';

export const App = () => {
    const [value, setValue] = useState('');
    useEffect(() => {
      console.log('value changed: ', value);
    }, [value])
	return <div>
        	<input type="text" name="username" value={value} onChange={(e) => setValue(e.target.value)} />
        </div>
}

Qui abbiamo una casella di input che ha un valore di stato value ad essa collegato. Questo valore cambia quando l'utente prova a digitare qualcosa.

Una volta che il valore è stato aggiornato e presentato, il blocco di codice all'interno di useEffect() viene eseguito stampando alla console il valore aggiornato della variabile di stato value.

Un buon caso d'uso per useEffect() può essere l'implementazione di chiamate API. Diciamo che vuoi chiamare una API passando come valore il contenuto della casella di input. useEffect sarà il posto migliore dove eseguire l'implementazione.

useEffect riceve come secondo argomento un array di dipendenze, nel nostro caso [value].

Questo significa che OGNI VOLTA che il valore di una variabile di stato inclusa nell'array di dipendenze (nel nostro caso value) CAMBIA, la funzione all'interno di useEffect viene eseguita, se si passa un array di dipendenze vuoto, la funzione verrà eseguita solo una volta.

Come funziona l'hook useRef()

useRef() ti consente di modificare il DOM (tuttavia questa non è l'unica caratteristica dell'hook).

Secondo la documentazione:

useRef ritorna un oggetto ref mutabile la cui proprietà .current è inizializzata con l'argomento passato (initialValue). L'oggetto restituito persisterà per l'intero ciclo di vita del componente.

Semplificando, usiamo useRef se vogliamo persistere il valore di qualcosa per tutta la durata di vita del componente. L'implementazione base di useRef si applica a elementi DOM. Facciamo un esempio:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` punta all'elemento input testo montato nel DOM
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

In questo caso stiamo assegnando una proprietà ref all'elemento input, che sarà referenziato dalla variabile inputEl che abbiamo creato.

Questo elemento input può ora essere manipolato in qualsiasi modo vogliamo. Possiamo modificare l'attributo style abbellendo l'elemento, possiamo estrarre la proprietà value per vedere che valore contiene, e così via.

Nell'esempio qui sopra, quando facciamo click sul bottone "Focus the input", l'elemento input riceve il focus e possiamo immediatamente iniziare a scriverci dentro. Lo facciamo chiamando inputEl.current.focus(), in pratica è il metodo focus() presente nell'oggetto current.

Cosa sono gli hook personalizzati?

Una delle domande che ho visto porre con più frequenza nei colloqui relativi al front-end è la richiesta di creare un hook personalizzato per gli eventi da tastiera.

Abbiamo visto tanti hook, ma l'intervistatore potrebbe chiederti di crearne uno tuo. Per alcuni potrebbe essere impegnativo ma con un poco di pratica diventa molto più facile.

Cerchiamo di capire cos'è un hook:

L'utilizzo base di un hook personalizzato è di estrarre la logica di una funzione in un suo proprio componente.

Immagina cosa succederebbe se dovessi catturare la pressione del tasto Invio all'interno di ognuno dei tuoi componenti. Invece di scrivere la logica per un event listener in ogni componente, puoi estrarre detta logica in un componente a sé stante e usarlo ovunque tu voglia (proprio come usiamo useState() o useEffect()).

Ci sono alcune condizioni affiché una funzione possa essere considerata un hook:

  1. Il nome del componente deve iniziare sempre con la parola chiave use.
  2. Possiamo decidere se debba ricevere argomenti, e cosa, se necessario, dovrebbe ritornare.
// Hook personalizzato: useAvailable
function useAvailabe(resource) {
  const [isAvailable, setIsAvailable] = useState(null);

  // ...

  return isAvailable;
}

// Utilizzo:
  const isAvailable = useAvailable(cpu);

Qui, non importa quanto volte chiamiamo useState e useEffect all'interno dell'hook personalizzato, saranno completamente indipendenti rispetto al punto nel quale lo utilizziamo.

Facciamo un esempio nel quale creiamo un hook personalizzato per conservare dei valori in localStorage.

Come creare un hook personalizzato  – esempio useLocalStorage

L'hook personalizzato useLocalStorage è un modo per salvare i dati in localStorage. Otteniamo e impostiamo i valori all'interno di localStorage usando coppie chiave/valore in modo che ogniqualvolta l'utente ritorna nella nostra applicazione web possa avere a disposizione dati memorizzati nei precedenti utilizzi.

L'implementazione che segue è un semplice elemento select che, una volta modificato il suo valore, salva il dato nel localStorage.

useLocalStorage.js

// Use Local Storage Custom Hook
import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });
  const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.log(error);
    }
  };
  return [storedValue, setValue] as const;
}

export default useLocalStorage;

App.js

import * as React from 'react';
import './style.css';
import useLocalStorage from './useLocalStorage';

export default function App() {
  const [storedValue, setStoredValue] = useLocalStorage(
    'select-value',
    'light'
  );

  return (
    <div>
      <select
        className="select"
        value={storedValue}
        onChange={(e) => setStoredValue(e.target.value)}
      >
        <option value="dark">Dark</option>
        <option value="light">Light</option>
      </select>
      <p className="desc">
        Value from local storage: <span>{storedValue}</span>
      </p>
    </div>
  );
}

L'hook useLocalStorage riceve due parametri, il nome della chiave da utilizzare in localStorage e un valore predefinito che deve trovarsi lì.

L'hook ritorna due valori: il valore della chiave in localStorage che abbiamo passato, e una funzione che consente di modificare questo valore, in questo caso setStoredValue.

Nel file useLocalStorage.js per prima cosa cerchiamo di ottenere il valore salvato in localStorage per la chiave passata usando il metodo localStorage.getItem(). Se la chiave esiste otteniamo il valore che, se esiste, ritorniamo elaborandolo con JSON.parse(). Altrimenti il contenuto della variabile initialValue viene fornito come valore predefinito.

La funzione setLocalStorage() gestisce il fatto che il valore passato sia una funzione o un semplice valore di variabile. Inoltre si occupa di impostare il valore in localStorage usando la funzione localStorage.setItem().

Come Distinguersi Come Sviluppatore Creando Progetti Secondari

La cosa che ha sempre funzionato per me e mi ha aiutato a spiccare sono i progetti secondari che ho creato.

Secondo me, non devi costruire 10 progetti secondari di base fatti in serie. Prova invece a costruire uno o due progetti davvero buoni nei quali puoi implementare tutti i concetti di React/HTML/CSS/JavaScript e tutto ciò che hai imparato.

Supponiamo che l'intervistatore abbia 14 colloqui in una settimana e debba esaminare il curriculum di altrettanti candidati. È più probabile che sia interessato al tuo profilo perché hai creato un sito Web che genera link shortener e addebita 1 dollaro dopo ogni 1000 visite al link anziché un clone Amazon / Netflix.

Ancora una volta, non c'è nulla di sbagliato nel creare cloni per esercitare le tue abilità. Tuttavia è sempre bello avere almeno 1 progetto che ti aiuta a distinguerti nella folla.

Inoltre, la creazione di progetti secondari ti aiuterà a progredire come sviluppatore. È improbabile sapere tutto in anticipo quando si crea un progetto da zero. Lungo il percorso, dovrai imparare e padroneggiare molte abilità diverse.

Pratica, Pratica, Pratica.

C'è un detto famoso che dice:

(traduzione: "Ogni intervista è un'intervista di prova per te fino a quando non ottieni il tuo primo lavoro front-end"), e questo è vero in larga misura.

Io stesso ho fallito centinaia di volte prima di ottenere il mio primo lavoro. È il feedback costante e le iterazioni che devi fare per ottenere ciò che desideri.

Nel nostro caso, ottenere un posto come sviluppatore front-end diventa facile quando:

  • Hai una profonda conoscenza delle tue abilità, React in questo caso (oltre a HTML, CSS e JS).
  • Hai un gruppo di progetti da mostrare, che ti mettono in evidenza.
  • Sei disposto a dedicare tempo e sforzo per imparare nuove cose e metterti alla prova.
  • Leggi regolarmente il blog di freeCodeCamp e ti eserciti qui con le domande (😉)

Conclusione

Ci sono molte domande su cui esercitarsi per un machine-coding round.

N.d.T.
Si tratta di risolvere un problema di progettazione in un paio d'ore. Richiede la progettazione e la scrittura del codice per una soluzione pulita, lineare ed espandibile in base a uno specifico insieme di requisiti. In genere segue un processo di revisione dove l'intervistatore esamina il codice e tenta di capire la logica del progetto.

L'intervistatore può porti diversi gruppi di domande per mettere alla prova le tue abilità.

Puoi usare Algochurn per esercitarti con le più popolari domande per colloqui su JavaScript, per domande per colloqui su React e domande relative agli algoritmi (tutte le risorse collegate sono in lingua inglese) poste in un colloquio tecnico sul front-end con i loro approcci e soluzioni.

Se hai una qualsiasi domanda, contattami via  Twitter(@mannupaaji) e/o sul mio sito web (manuarora.in)

Buona fortuna e buona programmazione! 👑🫡