Как обновить sh DOM при смене реквизита в Svelte? - PullRequest
0 голосов
/ 10 февраля 2020

У меня есть сетчатая структура с указателем, исходящим от родителя. Теперь с помощью некоторой операции указатель обновляется, но изменение не отражается в дочернем компоненте, а также не обновляется DOM.

Parent:

<script>
 import Boxes from "./Boxes.svelte";

 let boxes = [
   ["", "", "", "", "", "", "", ""],
   ["", "", "", "", "", "", "", ""],
   ["", "", "", "", "", "", "", ""],
   ["", "", "", "", "", "", "", ""],
   ["", "", "", "", "", "", "", ""],
   ["", "", "", "", "", "", "", ""],
   ["", "", "", "", "", "", "", ""],
   ["", "", "", "", "", "", "", ""]
 ];

 let activeBox = {
   x: 0,
   y: 0
 };

 function handleKeydown(keyEvent) {
   let i = activeBox.x;
   let j = activeBox.y;

   const width = boxes[i].length,
     height = boxes.length,
     left = 37,
     up = 38,
     right = 39,
     down = 40,
     tab = 9,
     backspace = 8;



   // Loop around single row with right and left arrows
   if (keyEvent.keyCode == right) {
     activeBox.x = activeBox.x + 1;
     if(activeBox.x === boxes[i].length) activeBox.x = 0;
     return;
   }

   $: console.log("^^^ activeBox &&", activeBox);
</script>

<style>
 main {
   display: flex;
   align-items: center;
   justify-content: center;
 }

 @media (min-width: 640px) {
   main {
     max-width: none;
   }
 }
</style>

<svelte:window on:keydown="{handleKeydown}" />
<main>
 <Boxes boxes={boxes} activeBox={activeBox} />
</main>

Дочерний компонент использует переменная activeBox для отображения выбранного блока. Но, похоже, это не сработает, поскольку, хотя первый рендер работает отлично, окно не обновляется вместе с ним.

Child:

<script>
  export let boxes;
  export let activeBox;

  $: console.log("^^^ active Box updated");

  function getSelectedClass(i, j) {
    if (activeBox.x === j && activeBox.y === i) {
      return "selected";
    }
    return "";
  }
</script>

<style>
  .grid-container {
    display: grid;
    grid-template-columns: auto auto auto auto auto auto auto auto;
    background-color: #2196f3;
    padding: 0px;
  }
  .grid-item {
    background-color: rgba(255, 255, 255, 0.8);
    border: 1px solid rgba(0, 0, 0, 0.8);
    width: 40px;
    height: 40px;
    padding: 20px;
    font-size: 30px;
    text-align: center;
  }
  .selected {
    border: 1px solid red;
  }
</style>

<main>
  <div class="grid-container">
    {#each boxes as row, i}
      {#each row as column, j}
        <div class=" grid-item {getSelectedClass(i, j)}">{boxes[i][j]}</div>
      {/each}
    {/each}
  </div>
</main>

Я действительно мог бы использовать некоторые идеи как Я до сих пор не понял концепцию Svelte. Любая помощь будет высоко ценится.

Ответы [ 2 ]

2 голосов
/ 10 февраля 2020

С вашим кодом значение activeBox фактически передается дочернему элементу, но вы его не видите, потому что нет никакого реактивного кода, который использует его в дочернем элементе.

Реактивный код перезапускается и переоценивается, когда значение переменных зависит от изменения, но только для переменных, непосредственно присутствующих в реактивном коде.

Итак, в этом коде :

  $: console.log("^^^ active Box updated");

... нет переменных вообще. Таким образом, этот блок будет запускаться один раз вначале, а затем никогда.

Это:

    {#each boxes as row, i}
      ...
    {/each}

будет переоценено в любое время при изменении переменной boxes.

И в этом:

        <div class=" grid-item {getSelectedClass(i, j)}">...</div>

... реактивное значение {getSelectedClass(i, j)} только «видит» переменные getSelectedClass, i и j. i и j являются локальными переменными, поэтому Svelte не отслеживает их. getSelectedClass - это переменная верхнего уровня, поэтому она будет отслежена и блок будет переоценен, если его значение изменилось (т. Е. getSelectedClass = 'foo'), но здесь это не так.

Итак ... Как исправить?

Простое добавление activeBox в реактивное выражение заставит компилятор пересчитать его при изменении его значения (даже если оно на самом деле не используется в функции). Так что только это исправит ваш код таким, какой он есть:

        <div class=" grid-item {getSelectedClass(i, j, activeBox)}">...</div>

Другой вариант - использовать другую конструкцию, основанную на выражении, которое на самом деле содержит переменную activeBox:

        <div class="grid-item" class:selected={activeBox.x === j && activeBox.y === i}>{boxes[i][j]}</div>

А вот золотая середина:

<script>
  // NOTE this block contains activeBox variable, so isSelected is recreated when
  // activeBox changes
  $: isSelected = (i, j) => activeBox.x === j && activeBox.y === i
</script>

<main>
  <div class="grid-container">
    {#each boxes as row, i}
      {#each row as column, j}
        <div class="grid-item" class:selected={isSelected(i, j)}>{boxes[i][j]}</div>
      {/each}
    {/each}
  </div>
</main>
0 голосов
/ 10 февраля 2020

Итак, при использовании Svelte следует помнить несколько вещей:

  1. Не консоль регистрирует реактивные объявления $: console.log () запустится, как только компонент будет создан, но имеет никакого другого значения или использования.
  2. Если ваша экспортируемая переменная совпадает с именем переменной в родительском компоненте, просто используйте {varName} в теге, нет необходимости в varName = {varName}

Что касается обновления activeBox в дочернем компоненте, оно обновляется, но в вашем коде нет способа узнать, что ему нужно снова запустить функцию, которая выбирает класс. Эта функция запускается только один раз, поэтому она работает и при начальном рендеринге.

Один из способов сохранить класс в актуальном состоянии - использовать троичный оператор непосредственно в выражении вашего условного класса в дочернем элементе:

<div class=" grid-item {activeBox.x === j && activeBox.y === i? 'selected' : ''}">{boxes[i][j]}</div>

И просто бросьте функцию. Как это

...