Suggerimenti e trucchi per la creazione di componenti riutilizzabili dell'interfaccia utente

Foto di Farzard Nazifi

In questo articolo voglio condividere alcuni suggerimenti e trucchi che utilizzo durante la creazione della nostra libreria di frontend core usando Ember.js. Non averlo mai contattato prima, è stata una grande opportunità di apprendimento. Spero che vi piaccia! Si noti che il codice utilizzato per esemplificare le idee nell'articolo contiene informazioni sufficienti per ottenere il punto. Utilizza anche un po 'di terminologia Ember.js, ma i concetti sono pensati per essere indipendenti dal framework.

Gli obiettivi

Per dirla semplicemente, i requisiti per la creazione della libreria sono i seguenti:

  1. Deve essere produttivo.
  2. Deve essere mantenibile.
  3. Deve essere coerente.

Gli approcci

Ridurre al minimo la logica aziendale

Uno dei problemi più frequenti che incontro nei progetti sono componenti che contengono troppa logica in essi. Pertanto, eseguire compiti che, in teoria, non rientrano nel loro ambito di applicazione.

Prima di implementare qualsiasi funzionalità, è bene delineare alcune delle funzioni di cui è responsabile il componente.

Immagina di costruire un componente pulsante.

Vorrei poter:

  • Informa che tipo di pulsante è - Principale o regolare
  • Informare il contenuto visualizzato all'interno del pulsante (icona e testo)
  • Disabilita o abilita il pulsante
  • Eseguire alcune azioni al clic

Avendo questo piccolo contorno, separa le diverse parti coinvolte nel processo di costruzione di questo componente. Cerca di identificare dove potrebbero essere posizionate le cose.

1 - Il tipo e il contenuto sono specifici del componente, quindi possono essere inseriti nel file del componente.

Poiché il tipo è - in una certa misura - richiesto, aggiungiamo una verifica nel caso in cui non sia stato fornito alcun valore.

const type = get (this, 'type');
tipi const = {
  primary: 'btn - primary',
  regolare: "btn - regular",
}
return (tipo)? types [tipo]: types.regular;

Mi piace mappare le proprietà in un oggetto perché consente di ridimensionare le cose senza troppi sforzi, nel caso in cui abbiamo bisogno di un pulsante di pericolo o qualcosa del genere.

2 - Lo stato disabilitato può essere trovato su diversi componenti come un input. Al fine di evitare la ripetizione, questo comportamento può essere spostato in un modulo o in qualsiasi struttura condivisa - la gente lo chiama mixin.

3 - L'azione clic può essere trovata in diversi componenti. Quindi può anche essere spostato in un altro file e non deve contenere alcuna logica al suo interno, semplicemente chiamando il callback fornito dallo sviluppatore.

In questo modo possiamo avere un'idea di quali casi il nostro componente deve affrontare, aiutando nel delineare un'architettura di base che supporta l'espansione.

Separare lo stato dell'interfaccia utente riutilizzabile

Alcune interazioni dell'interfaccia utente sono comuni tra diversi componenti, come:

  • Abilita / disabilita - ad es. pulsanti, ingressi
  • Espandi / Riduci - ad es. comprimi, elenchi a discesa
  • Mostra / nascondi: praticamente tutto

Queste proprietà sono spesso usate solo per controllare lo stato visivo - si spera.

Mantenere una nomenclatura coerente tra i diversi componenti. Tutte le azioni relative a uno stato visivo possono essere spostate in un mixin.

/ * UIStateMixin * /
disattivare() {
  set (questo, "disabilitato", vero);
  restituire questo;
},
abilitare() {
  set (this, 'disabled', false ');
  restituire questo;
},

Ogni metodo è responsabile solo della commutazione di una particolare variabile e restituisce il contesto corrente per il concatenamento, come:

pulsante
  .disattivare()
  .showLoadingIndicator ();

Questo approccio può essere esteso. Può accettare contesti diversi e controllare variabili esterne invece di utilizzare quelle interne. Per esempio:

_getCurrentDisabledAttr () {
  return (isPresent (get (this, 'disabled')))
    ? 'disabilitato' / * Parametro esterno * /
    : 'è disabilitato'; / * Variabile interna * /
},
abilita (contesto) {
  set (contesto || questo, this._getCurrentDisabledAttr (), false);
  restituire questo;
}

Estrarre le funzionalità di base

Ogni componente contiene determinate routine. Queste routine devono essere eseguite indipendentemente dallo scopo del componente. Ad esempio, verificare un callback prima di attivarlo.

Questi metodi predefiniti possono anche essere spostati nei propri mixin, in questo modo:

/ * BaseComponentMixin * /
_isCallbackValid (callbackName) {
  const callback = get (this, callbackName);
  
  return !! (isPresent (callback) && typeof callback === 'funzione');
},
_handleCallback (callback, params) {
  if (! this._isCallbackValid (callback)) {
    genera un nuovo errore (/ * messaggio * /);
  }
  this.sendAction (callback, params);
},

E quindi incluso nei componenti.

/* Componente */
onClick (params) {
  this._handleCallback ('onClick', params);
}

Ciò mantiene coerente l'architettura di base. Consente inoltre l'espansione e persino l'integrazione con software di terze parti. Ma per favore, non essere un astrattore filosofico.

Componenti di composizione

Evita di riscrivere la funzionalità il più possibile. La specializzazione può essere raggiunta. Può essere fatto attraverso la composizione e il raggruppamento. Oltre a modificare i componenti più piccoli insieme per creare nuovi componenti.

Per esempio:

Componenti di base: pulsante, menu a discesa, input.
Pulsante a discesa = pulsante> + menu a discesa
Completamento automatico => input + menu a discesa
Seleziona => input (sola lettura) + menu a discesa

In questo modo, ogni componente ha i suoi compiti. Ognuno gestisce il proprio stato e parametri mentre il componente wrapper gestisce la propria logica specifica.

Separazione delle preoccupazioni al massimo.

Dividere le preoccupazioni

Quando si compongono componenti più complessi, esiste la possibilità di dividere le preoccupazioni. È possibile dividere le preoccupazioni tra le diverse parti di un componente

Diciamo che stiamo costruendo un componente selezionato.

{{form-select binding = productId items = items}}
articoli = [
  {descrizione: "Prodotto n. 1", valore: 1},
  {descrizione: "Prodotto n. 2", valore: 2}
]

Internamente, abbiamo un semplice componente di input e un menu a discesa.

{{form-input binding = _description}}
{{ui-dropdown items = items onSelect = (azione 'selectItem')}}

Il nostro compito principale è presentare la descrizione all'utente, ma non ha alcun significato per la nostra applicazione - il valore lo fa.

Quando si seleziona un'opzione, si divide l'oggetto, inviando la descrizione fino al nostro input attraverso una variabile interna mentre si spinge il valore verso il controller, aggiornando la variabile associata.

Questo concetto può essere applicato ai componenti in cui il valore associato deve essere trasformato, ad esempio un numero, il completamento automatico o il campo di selezione. Anche i Datepicker possono implementare questo comportamento. Possono smascherare la data prima di aggiornare la variabile associata mentre presentano all'utente il valore mascherato.

I rischi aumentano con l'aumentare della complessità delle trasformazioni. Con una logica eccessiva o con il supporto di eventi, quindi pensaci bene prima di implementare questo approccio.

Preset vs nuovi componenti

A volte è necessario ottimizzare componenti e servizi per facilitare lo sviluppo. Questi vengono consegnati sotto forma di preset o nuovi componenti.

I preset sono parametri. Quando informati, impostano valori predefiniti sul componente, semplificandone la dichiarazione. Tuttavia, i nuovi componenti sono in genere versioni più specializzate dei componenti di base.

La parte difficile è sapere quando implementare i preset o creare nuovi componenti. Uso le seguenti linee guida per prendere questa decisione:

Quando creare i preset

1 - Modelli di utilizzo ripetitivi

Ci sono momenti in cui un particolare componente viene riutilizzato in vari punti con gli stessi parametri. In questi casi, mi piace privilegiare i preset sui nuovi componenti, specialmente quando il componente base ha un numero eccessivo di parametri.

/ * Attuazione regolare * /
{{Form-completamento automatico
    binding = productId
    url = "products" / * URL da recuperare * /
    labelAttr = "description" / * Attributo usato come etichetta * /
    valueAttr = "id" / * Attributo usato come valore * /
    apiAttr = "product" / * Param inviato su richiesta * /
}}
/ * Preselezioni * /
{{Form-completamento automatico
    preset = "prodotto"
    binding = productId
}}

I valori della preimpostazione vengono impostati solo se il parametro non è stato informato, mantenendo la sua flessibilità.

/ * Implementazione ingenua del modulo preset * /
const preset = {
  Prodotto: {
    url: "prodotti",
    labelAttr: "descrizione",
    valueAttr: "id",
    apiAttr: "prodotto",
  },
}
const attrs = preset [get (this, "preset")];
Object.keys (attrs) .forEach ((prop) => {
  if (! get (this, prop)) {
    set (this, prop, attrs [prop]);
  }
});

Questo approccio riduce le conoscenze necessarie per personalizzare il componente. Allo stesso tempo, facilita la manutenzione consentendo di aggiornare i valori predefiniti in un'unica posizione.

2 - Il componente base è troppo complesso

Quando il componente base che useresti per creare un componente più specifico accetta troppi parametri. Pertanto, la sua creazione genererebbe alcuni problemi. Per esempio:

  • Dovresti iniettare la maggior parte - se non tutti - i parametri dal nuovo componente al componente base. Man mano che sempre più componenti ne derivano, qualsiasi aggiornamento sul componente di base rifletterebbe un'enorme quantità di modifiche. Pertanto, portando a una maggiore incidenza di bug.
  • Man mano che vengono creati più componenti, più diventa difficile documentare e memorizzare le diverse sfumature. Ciò è particolarmente vero per i nuovi sviluppatori.

Quando creare nuovi componenti

1 - Estensione della funzionalità

È possibile creare un nuovo componente quando si estende la funzionalità da un componente più semplice. Ti aiuta a prevenire la perdita della logica specifica del componente in un altro componente. Ciò è particolarmente utile durante l'implementazione di comportamenti extra.

/ * Dichiarazione * /
{{ui-button-dropdown items = items}}
/* Sotto il cappuccio */
{{# ui-button onClick = (azione 'toggleDropdown')}}
  {{label}}  
{{Ui-tasto /}}
{{#if isExpanded}}
  {{ui-dropdown items = items}}
{{/Se}}

L'esempio sopra utilizza il componente pulsante. Ciò estende il suo layout per supportare un'icona fissa includendo un componente a discesa e il suo stato di visibilità.

2 - Parametri di decorazione

C'è un'altra possibile ragione per creare nuovi componenti. Questo è quando è necessario controllare la disponibilità dei parametri o decorare i valori predefiniti.

/ * Dichiarazione * /
{{form-datepicker onFocus = (azione 'doSomething')}}
/* Sotto il cappuccio */
{{form-input onFocus = (azione '_onFocus')}}
_onFocus () {
  $ (This.element)
    .find ( 'ingresso')
    .selezionare(); / * Seleziona il valore del campo attivo * /
  this._handleCallback ( 'onFocus'); / * Attiva la richiamata dei parametri * /
}

In questo esempio, è stata fornita al componente una funzione che doveva essere chiamata quando il campo era focalizzato.

Internamente, invece di passare il callback direttamente al componente base, passa una funzione interna. Ciò esegue una particolare attività (selezionando il valore del campo) e quindi chiama il callback fornito.

Non sta reindirizzando tutti i parametri accettati dal componente di input di base. Questo aiuta a controllare l'ambito di alcune funzionalità. Evita inoltre convalide non necessarie.

Nel mio caso, l'evento onBlur è stato sostituito da un altro evento: onChange. Ciò si innesca quando l'utente riempie il campo o seleziona una data sul calendario.

Conclusione

Quando costruisci i tuoi componenti, considera la tua parte e chiunque stia usando quel componente nella loro vita quotidiana. In questo modo, vincono tutti.

Il miglior risultato arriva da tutti i membri del gruppo che fanno ciò che è meglio per sé e per il gruppo: John Nash

Inoltre, non vergognarti di chiedere feedback. Troverai sempre qualcosa su cui lavorare.

Per affinare ulteriormente le tue capacità di ingegneria del software, ti consiglio di seguire la serie "Software di composizione" di Eric Elliott. È meraviglioso!

Spero che l'articolo ti sia piaciuto. Per favore, prendi questi concetti, trasformali nelle tue idee e condividile con noi!

Inoltre, sentiti libero di contattarmi su Twitter @gcolombo_! Mi piacerebbe sentire la tua opinione e persino lavorare insieme.

Grazie!