Zephyrnet-logo

De valkuilen van geneste componenten in een ontwerpsysteem vermijden

Datum:

Bij het maken van een op componenten gebaseerde, front-end infrastructuur, is een van de grootste pijnpunten die ik persoonlijk ben tegengekomen het maken van componenten die zowel herbruikbaar als responsief zijn wanneer er geneste componenten in componenten zijn.

Voer de volgende "oproep tot actie" (<CTA />) onderdeel, bijvoorbeeld:

Op kleinere apparaten willen we dat het er als volgt uitziet:

Dit is eenvoudig genoeg met standaard mediaquery's. Als we flexbox gebruiken, kan een mediaquery de richting van de flex veranderen en de knop over de volledige breedte laten gaan. Maar we lopen tegen een probleem aan wanneer we andere componenten daarin gaan nesten. Stel bijvoorbeeld dat we een component voor de knop gebruiken en dat deze al een prop heeft die hem over de volledige breedte maakt. We dupliceren eigenlijk de stijl van de knop wanneer we een mediaquery toepassen op de bovenliggende component. De geneste knop kan het al aan!

Dit is een klein voorbeeld en het zou niet zo'n groot probleem zijn, maar voor andere scenario's kan het veel dubbele code veroorzaken om de stijl te repliceren. Wat als we in de toekomst iets zouden willen veranderen aan de vormgeving van knoppen op volledige breedte? We zouden het op al deze verschillende plaatsen moeten doornemen en veranderen. We zouden het in de knopcomponent moeten kunnen wijzigen en die update overal hebben.

Zou het niet mooi zijn als we afstand konden nemen van mediaquery's en meer controle hadden over de styling? We zouden de bestaande rekwisieten van een component moeten gebruiken en verschillende waarden moeten kunnen doorgeven op basis van de schermbreedte.

Nou, ik heb een manier om dat te doen en ik zal je laten zien hoe ik het deed.

ik ben me er bewust van dat containervragen kan veel van deze problemen oplossen, maar het staat nog in de kinderschoenen en lost het probleem niet op met het doorgeven van een verscheidenheid aan rekwisieten op basis van schermbreedte.

De vensterbreedte volgen

Eerst moeten we de huidige breedte van de pagina volgen en een breekpunt instellen. Dit kan met elk front-end framework, maar ik gebruik a Vue samen te stellen hier om het idee te demonstreren:

// composables/useBreakpoints.js

import { readonly, ref } from "vue";

const bps = ref({ xs: 0, sm: 1, md: 2, lg: 3, xl: 4 })
const currentBreakpoint = ref(bps.xl);

export default () => {
  const updateBreakpoint = () => {

    const windowWidth = window.innerWidth;

    if(windowWidth >= 1200) {
      currentBreakpoint.value = bps.xl
    } else if(windowWidth >= 992) {
      currentBreakpoint.value = bps.lg
    } else if(windowWidth >= 768) {
      currentBreakpoint.value = bps.md
    } else if(windowWidth >= 576) {
      currentBreakpoint.value = bps.sm
    } else {
      currentBreakpoint.value = bps.xs
    }
  }

  return {
    currentBreakpoint: readonly(currentBreakpoint),
    bps: readonly(bps),
    updateBreakpoint,
  };
};

De reden dat we getallen gebruiken voor de currentBreakpoint voorwerp zal later duidelijk worden.

Nu kunnen we luisteren naar gebeurtenissen voor het wijzigen van de grootte van het venster en het huidige breekpunt bijwerken met behulp van de composable in het hoofdmenu App.vue file:

// App.vue

<script>
import useBreakpoints from "@/composables/useBreakpoints";
import { onMounted, onUnmounted } from 'vue'

export default {
  name: 'App',

  setup() {
    const { updateBreakpoint } = useBreakpoints()

    onMounted(() => {
      updateBreakpoint();
      window.addEventListener('resize', updateBreakpoint)
    })

    onUnmounted(() => {
      window.removeEventListener('resize', updateBreakpoint)
    })
  }
}
</script>

We willen waarschijnlijk dat dit ongedaan wordt gemaakt, maar ik houd het kort en bondig.

Styling componenten

We kunnen het <CTA /> component om een ​​nieuwe prop te accepteren voor hoe deze gestileerd moet worden:

// CTA.vue
props: {
  displayMode: {
    type: String,
    default: "default"
  }
}

De naamgeving hier is volkomen willekeurig. U kunt elke gewenste naam gebruiken voor elk van de componentmodi.

We kunnen dan deze prop gebruiken om de modus te wijzigen op basis van het huidige breekpunt:

<CTA :display-mode="currentBreakpoint > bps.md ? 'default' : 'compact'" />

Je kunt nu zien waarom we een getal gebruiken om het huidige breekpunt weer te geven - het is zo dat de juiste modus kan worden toegepast op alle breekpunten onder of boven een bepaald getal.

We kunnen dit vervolgens in de CTA-component gebruiken om te stylen volgens de modus die is doorgegeven:

// components/CTA.vue

<template>
  <div class="cta" :class="displayMode">

    <div class="cta-content">
      <h5>title</h5>
      <p>description</p>
    </div>

    <Btn :block="displayMode === 'compact'">Continue</Btn>

  </div>
</template>

<script>
import Btn from "@/components/ui/Btn";
export default {
  name: "CTA",
  components: { Btn },
  props: {
    displayMode: {
      type: String,
      default: "default"
    },
  }
}
</script>

<style scoped lang="scss">
.cta {
  display: flex;
  align-items: center;

  .cta-content {
    margin-right: 2rem;
  }

  &.compact {
    flex-direction: column;
    .cta-content {
      margin-right: 0;
      margin-bottom: 2rem;
    }
  }
}
</style>

We hebben al de noodzaak voor mediaquery's verwijderd! Je kunt dit in actie zien op a demo pagina Ik maakte.

Toegegeven, dit lijkt misschien een langdurig proces voor zoiets eenvoudigs. Maar wanneer toegepast op meerdere componenten, kan deze benadering de consistentie en stabiliteit van de gebruikersinterface enorm verbeteren, terwijl de totale hoeveelheid code die we moeten schrijven, wordt verminderd. Deze manier om JavaScript- en CSS-klassen te gebruiken om de responsieve stijl te regelen, heeft nog een ander voordeel...

Uitbreidbare functionaliteit voor geneste componenten

Er zijn scenario's geweest waarin ik terug moest naar een eerder breekpunt voor een onderdeel. Als het bijvoorbeeld 50% van het scherm in beslag neemt, wil ik dat het in de kleine modus wordt weergegeven. Maar bij een bepaalde schermgrootte wordt het volledige breedte. Met andere woorden, de modus zou op de een of andere manier moeten veranderen wanneer er een resize-gebeurtenis is.

Het tonen van drie versies van een call-to-action-component met daarin geneste componenten.

Ik ben ook in situaties geweest waarin hetzelfde onderdeel in verschillende modi op verschillende pagina's wordt gebruikt. Dit is niet iets dat frameworks zoals Bootstrap en Tailwind kunnen doen, en het zou een nachtmerrie zijn om mediaquery's te gebruiken om het voor elkaar te krijgen. (Je kunt die frameworks nog steeds gebruiken met deze techniek, alleen zonder dat je de responsieve klassen nodig hebt die ze bieden.)

We kon gebruik een mediaquery die alleen van toepassing is op middelgrote schermen, maar dit lost het probleem niet op met variërende rekwisieten op basis van schermbreedte. Gelukkig kan de aanpak die we behandelen dat oplossen. We kunnen de vorige code wijzigen om een ​​aangepaste modus per breekpunt mogelijk te maken door deze door een array te leiden, waarbij het eerste item in de array de kleinste schermgrootte is.

<CTA :custom-mode="['compact', 'default', 'compact']" />

Laten we eerst de rekwisieten bijwerken die de <CTA /> component kan accepteren:

props: {
  displayMode: {
    type: String,
    default: "default"
  },
  customMode: {
    type: [Boolean, Array],
    default: false
  },
}

We kunnen dan het volgende toevoegen om naar de juiste modus te genereren:

import { computed } from "vue";
import useBreakpoints from "@/composables/useBreakpoints";

// ...

setup(props) {

  const { currentBreakpoint } = useBreakpoints()

  const mode = computed(() => {
    if(props.customMode) {
      return props.customMode[currentBreakpoint.value] ?? props.displayMode
    }
    return props.displayMode
  })

  return { mode }
},

Dit neemt de modus van de array op basis van het huidige breekpunt en is standaard de displayMode als er geen wordt gevonden. Dan kunnen we gebruiken mode in plaats daarvan om de component te stylen.

Extractie voor herbruikbaarheid

Veel van deze methoden kunnen worden geëxtraheerd in aanvullende composables en mixins die opnieuw kunnen worden gebruikt met andere componenten.

Berekende modus extraheren

De logica voor het retourneren van de juiste modus kan worden geëxtraheerd in een composable:

// composables/useResponsive.js

import { computed } from "vue";
import useBreakpoints from "@/composables/useBreakpoints";

export const useResponsive = (props) => {

  const { currentBreakpoint } = useBreakpoints()

  const mode = computed(() => {
    if(props.customMode) {
      return props.customMode[currentBreakpoint.value] ?? props.displayMode
    }
    return props.displayMode
  })

  return { mode }
}

Props extraheren

In Vue 2 konden we rekwisieten herhalen door mixins te gebruiken, maar er zijn merkbare nadelen. Met Vue 3 kunnen we deze samenvoegen met andere rekwisieten met dezelfde composable. Er is een klein voorbehoud hierbij, omdat IDE's de rekwisieten voor automatisch aanvullen met deze methode niet lijken te herkennen. Als dit te vervelend is, kun je in plaats daarvan een mixin gebruiken.

Optioneel kunnen we ook aangepaste validatie doorgeven om ervoor te zorgen dat we de modi gebruiken die alleen beschikbaar zijn voor elk onderdeel, waarbij de eerste waarde die wordt doorgegeven aan de validator de standaard is.

// composables/useResponsive.js

// ...

export const withResponsiveProps = (validation, props) => {
  return {
    displayMode: {
      type: String,
      default: validation[0],
      validator: function (value) {
        return validation.indexOf(value) !== -1
      }
    },
    customMode: {
      type: [Boolean, Array],
      default: false,
      validator: function (value) {
        return value ? value.every(mode => validation.includes(mode)) : true
      }
    },
    ...props
  }
}

Laten we nu de logica verplaatsen en deze in plaats daarvan importeren:

// components/CTA.vue

import Btn from "@/components/ui/Btn";
import { useResponsive, withResponsiveProps } from "@/composables/useResponsive";

export default {
  name: "CTA",
  components: { Btn },
  props: withResponsiveProps(['default 'compact'], {
    extraPropExample: {
      type: String,
    },
  }),

  setup(props) {
    const { mode } = useResponsive(props)
    return { mode }
  }
}

Conclusie

Het creëren van een ontwerpsysteem van herbruikbare en responsieve componenten is een uitdaging en vatbaar voor inconsistenties. Bovendien hebben we gezien hoe gemakkelijk het is om te eindigen met een lading gedupliceerde code. Er is een goede balans als het gaat om het maken van componenten die niet alleen in veel contexten werken, maar ook goed samengaan met andere componenten wanneer ze worden gecombineerd.

Ik weet zeker dat je dit soort situaties in je eigen werk bent tegengekomen. Het gebruik van deze methoden kan het probleem verminderen en hopelijk de gebruikersinterface stabieler, herbruikbaar, onderhoudbaar en gebruiksvriendelijker maken.

spot_img

Laatste intelligentie

spot_img