Místní konstanty ve Svelte s tagem @const

Direktivy stylu nebyly jedinou novou funkcí zavedenou ve Svelte 3.46! Pojďme se podívat na další nedávný přírůstek do Svelte:značku @const.

Problém

Řekněme, že zobrazujete seznam polí a počítáte jejich plochy a také chcete použít nějaký styl, když je plocha určitá. Možná vás napadne udělat něco takového:

<script>
  let boxes = [
    {width: 1, height: 2},
    {width: 5, height: 2.5},
    {width: 2, height: 4}
  ];
</script>

{#each boxes as box}
  <p class:big={box.width * box.height > 10}>
    {box.width} * {box.height} = {box.width * box.height}
  </p>
{/each}

<style>
  .big {
    font-size: 2rem;
  }
</style>

Všimněte si, že počítáme box.width * box.height dvakrát — jednou pro zobrazení a jednou v class:big směrnice. I když se hodnota nezměnila, prohlížeč ji stále musí počítat dvakrát. I když to není problém s jednoduchým výpočtem, jako je tento, mohlo by to ovlivnit výkon, pokud by byl výpočet intenzivnější. Do kódu také zavádí duplicitu. Pokud byste oblast potřebovali použít vícekrát (např. pro použití různých tříd CSS), tyto problémy by se ještě více zhoršily.

(To je mimochodem jen problém, protože jsme uvnitř #každého bloku. Pokud by existoval pouze jeden rámeček, mohli bychom vypočítat oblast jednou v bloku skriptu a skončili bychom s tím.)

Před zavedením značky const existovalo několik způsobů, jak tento problém vyřešit. Můžete vytvořit pomocnou funkci pro výpočet hodnoty...

<script>
  let boxes = [
    {width: 1, height: 2},
    {width: 5, height: 2.5},
    {width: 2, height: 4}
  ];

  function area(box) {
    return box.width * box.height;
  }
</script>

{#each boxes as box}
  <p class:big={area(box) > 10}>
    {box.width} * {box.height} = {area(box)}
  </p>
{/each}

Tím se sníží duplikace, ale přesto bude výpočet provádět vícekrát, pokud neimplementujete nějakou formu zapamatování. Opět, toto se pravděpodobně netýká jednoduchého výpočtu, jako je plocha, ale bylo by to pro dražší výpočty.

Můžete také vytvořit nové pole, které předem vypočítá požadovanou vlastnost...

<script>
  let boxes = [
    {width: 1, height: 2},
    {width: 5, height: 2.5},
    {width: 2, height: 4}
  ];

  let mappedBoxes = boxes.map(b => {
    return {
      ...b,
      area: b.width * b.height
    };
  });
</script>

{#each mappedBoxes as box}
  <p class:big={box.area> 10 }>
    {box.width} * {box.height} = {box.area}
  </p>
{/each}

Funguje to, ale působí to trochu neohrabaně a nyní musíte pole opakovat několikrát. Ve velké komponentě byste také museli při provádění změn přeskakovat mezi šablonou, kde se proměnná používá, a skriptem, kde je definována.

Poslední možností je extrahovat novou součást...

<script>
  import Box from './Box.svelte';
  let boxes = [
    {width: 1, height: 2},
    {width: 5, height: 2.5},
    {width: 2, height: 4}
  ];
</script>

{#each boxes as box}
  <Box {box}></Box>
{/each}

<!-- Box.svelte -->
<script>
  export let box;

  $: area = box.width * box.height;
</script>

<p class:big={area > 10}>
  {box.width} * {box.height} = {area}
</p>

... ale to se zdá být přehnané pro tak jednoduchý případ použití.

Před Svelte 3.46 byste si museli vybrat jednu z těchto možností. Nyní je tu další řešení:místní konstanty.

Řešení:lokální konstanty

Místo přidání logiky do bloku skriptu můžete konstantu deklarovat přímo v samotném označení pomocí @const .

{#each boxes as box}
  {@const area = box.width * box.height}
  <p class:big={area > 10}>
    {box.width} * {box.height} = {area}
  </p>
{/each}

To je čitelnější, protože hodnota je deklarována přímo tam, kde je použita, a efektivnější, protože hodnotu počítá pouze jednou.

Název „const“ byl zvolen, protože se chová jako konstanta:je pouze pro čtení a nelze k němu přiřadit. Také jako nativní JavaScript const , je omezena na blok, ve kterém byla deklarována. Následující šablona vytváří chyby kompilátoru:

{#each boxes as box}
  {@const area = box.width * box.height}
    <!-- Error: 'area' is declared using {@const ...} and is read-only -->
    <p on:hover={() => area = 50}>
        {box.width} * {box.height} = {area}
    </p>
{/each}
<!-- 'area' is not defined -->
{area}

Navzdory podobnosti s klíčovým slovem JavaScript neexistuje žádné odpovídající let nebo var štítek. Také na rozdíl od const v JavaScriptu proměnné deklarované pomocí @const lze použít před jejich deklarací. Následující příklad je platný, navzdory area používá se předtím, než je deklarován pomocí @const .

{#each boxes as box}
  <p>
    {box.width} * {box.height} = {area}
  </p>
  {@const area = box.width * box.height}
{/each}

Destrukce uvnitř #každého

@const také usnadní destrukci objektů uvnitř #each bloků. V současné době můžete destruovat proměnnou uvnitř #každého bloku takto:

{#each boxes as {width, height}}
  <p>{width} * {height} = {width * height}</p>
{/each}

Jakmile to však uděláte, nebudete mít žádný odkaz na původní objekt. Pokud chcete použít původní objekt (např. pro předání jiné komponentě), musíte jej znovu vytvořit.

{#each boxes as {width, height}}
  <p>{width} * {height} = {width * height}</p>
  <Box box={{width, height}} />
{/each}

Pokud jsou vlastnosti přidány nebo odebrány z původního objektu, musíte udržovat aktuální i tento druhý objekt. Na to lze snadno zapomenout.

Nyní můžete objekt rozložit pomocí @const , při zachování odkazu na původní objekt.

{#each boxes as box}
  {@const { width, height } = box}
  <p>{width} * {height} = {width * height}</p>
  <Box box={box} />
{/each}

Vyžaduje to jeden řádek navíc, ale to znamená, že nemusíte zavádět duplicitní objekt.

Zlepšení čitelnosti

Pomocí @const může také zlepšit čitelnost vašeho kódu tím, že vám umožní pojmenovat proměnnou pro to, co by jinak bylo inline výrazem. Například:

<!-- Option 1: long, complex inline expression -->
{#each boxes as box}
  {#if box.width < 30 && box.width > 10 && box.height % 3 === 0}
  <!-- Do some conditional rendering... -->
  {/if}
{/each}

<!-- Option 2: extract into a local constant -->
{#each boxes as box}
  {@const boxFitsTheRoom = box.width < 30 && box.width > 10 && box.height % 3 === 0}
  <!-- The expression is named, which can help
    others understand the purpose of this code -->
  {#if boxFitsTheRoom}
  <!-- Do some conditional rendering... -->
  {/if}
{/each}

I když to není nutné dělat pro každý if, může váš kód učinit mnohem srozumitelnějším, když máte dlouhé inline výrazy.

Omezení

Nová značka má několik omezení.

Povoleno pouze v určitých kontextech :@const je povoleno pouze jako přímý potomek {#each} , {:then} , {:catch} , <Component /> nebo <svelte:fragment /> . Toto jsou všechny typy bloků, kde je vytvořen nový obor. Nemůžete jej použít samostatně na nejvyšší úrovni šablony nebo uvnitř {#if} / {:else} blok, ačkoli druhý má požadavek na otevřenou funkci.

Nepodporuje nestandardní JavaScript: protože JavaScriptové výrazy uvnitř značkovací části komponenty Svelte nejsou předzpracovány, nebudete moci do const tagu zapisovat výrazy, které používají nestandardní JavaScript (např. TypeScript nebo syntaxi vyžadující Babel pluginy).

Všimněte si také, že v době psaní tohoto článku stále existují nějaké otevřené chyby kolem této funkce: