Создание пользовательских директив

В Svelte директивы — это инструкции, которые привязываются к DOM-элементам и управляют их поведением. Например, директивы вроде bind:, use: и transition: позволяют привязать данные к элементам, использовать сторонние библиотеки или настраивать анимации. Однако, помимо стандартных директив, можно создавать и собственные, которые будут решать специфические задачи. Этот процесс подразумевает использование функции action, которая дает возможность расширять функциональность фреймворка.

Директивы и действия в Svelte

В Svelte пользовательские директивы реализуются через механизм действий (actions). Они позволяют добавлять кастомную логику к элементам в момент их привязки и изменения в DOM. Пользовательская директива может использоваться для различных целей: от управления событиями до сложных манипуляций с DOM-элементами.

Директива в Svelte — это просто JavaScript-функция, которая принимает элемент DOM и набор аргументов, определяющих поведение. Для её создания достаточно создать функцию, которая будет вызвана при монтировании компонента.

Основы создания действия

Каждое действие — это функция, принимающая элемент DOM и опциональные аргументы. Функция возвращает объект, в котором можно определить колбэки для жизненного цикла элемента: destroy (когда элемент удаляется из DOM) и update (когда элемент обновляется).

Простейшая структура действия выглядит следующим образом:

function myAction(node, params) {
  // Логика для node, элемент DOM
  node.style.backgroundColor = params.color;

  return {
    update(newParams) {
      node.style.backgroundColor = newParams.color;
    },
    destroy() {
      // Очистка
      node.style.backgroundColor = '';
    }
  };
}

Использование действия в компоненте

После того как действие создано, его можно применить к элементу в компоненте с помощью директивы use:. Эта директива принимает имя функции действия и параметры, которые будут переданы в неё.

<script>
  import { myAction } from './myAction.js';
</script>

<div use:myAction={{ color: 'blue' }}>
  Этот элемент будет синим.
</div>

В приведенном примере, когда элемент будет добавлен в DOM, его фон станет синим. Если параметры действия изменятся, цвет фона будет обновлён.

Действия с аргументами

Директивы могут принимать не только простые значения, но и объекты, функции или любые другие данные. Для передачи параметров в действие используется синтаксис JavaScript-объектов.

<script>
  function fade(node, { duration }) {
    node.style.opacity = 0;
    node.style.transition = `opacity ${duration}ms`;

    return {
      update({ duration }) {
        node.style.transition = `opacity ${duration}ms`;
      },
      destroy() {
        node.style.transition = '';
      }
    };
  }
</script>

<div use:fade={{ duration: 1000 }}>
  Этот элемент будет плавно исчезать за 1 секунду.
</div>

Директива use:fade использует функцию fade, которая управляет прозрачностью элемента при его отображении и скрытии. При обновлении параметра duration обновляется длительность анимации.

Действия с реактивностью

Одной из сильных сторон Svelte является реактивность. Пользовательские директивы могут также использовать реактивность для динамического обновления значений или параметров.

<script>
  let color = 'red';

  function changeColor(node, { color }) {
    node.style.color = color;
    
    return {
      update({ color }) {
        node.style.color = color;
      }
    };
  }
</script>

<input bind:value={color} />

<div use:changeColor={{ color }}>
  Текст будет менять цвет.
</div>

В этом примере директива changeColor изменяет цвет текста на основе значения переменной color. Так как переменная color привязана к инпуту, изменение значения инпута автоматически приводит к обновлению цвета текста.

Работа с событиями

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

function clickAction(node) {
  function handleClick(event) {
    alert('Элемент был кликнут!');
  }

  node.addEventListener('click', handleClick);

  return {
    destroy() {
      node.removeEventListener('click', handleClick);
    }
  };
}

После создания этого действия его можно использовать в компоненте следующим образом:

<div use:clickAction>
  Нажми меня.
</div>

При каждом клике на элемент будет выводиться уведомление.

Передача значений в действия

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

<script>
  let scale = 1;

  function scaleAction(node, { scale }) {
    node.style.transform = `scale(${scale})`;

    return {
      update({ scale }) {
        node.style.transform = `scale(${scale})`;
      }
    };
  }
</script>

<input bind:value={scale} min="0" max="3" step="0.1" type="range" />
<div use:scaleAction={{ scale }}>
  Этот элемент изменит свой размер.
</div>

В этом примере слайдер управляет параметром scale, который изменяет размер элемента.

Комбинированные действия

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

<script>
  function borderAction(node, { color }) {
    node.style.border = `2px solid ${color}`;
    
    return {
      update({ color }) {
        node.style.border = `2px solid ${color}`;
      }
    };
  }

  function shadowAction(node) {
    node.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
    
    return {
      destroy() {
        node.style.boxShadow = '';
      }
    };
  }
</script>

<div use:borderAction={{ color: 'green' }} use:shadowAction>
  Этот элемент будет с зеленой рамкой и тенью.
</div>

В этом примере элемент получает два действия: одно для изменения рамки и другое для добавления тени. Они работают независимо друг от друга и могут быть настроены по отдельности.

Сложные примеры: создание кастомных анимаций

Создание анимаций с помощью действий — это мощный инструмент. Например, можно реализовать анимацию появления элемента с эффектом разрастания.

function grow(node, { duration }) {
  node.style.transform = 'scale(0)';
  node.style.transition = `transform ${duration}ms`;

  return {
    start() {
      node.style.transform = 'scale(1)';
    },
    destroy() {
      node.style.transition = '';
    }
  };
}

В компоненте это действие будет использоваться следующим образом:

<script>
  let duration = 500;
</script>

<button on:click={() => duration = duration === 500 ? 1000 : 500}>Изменить длительность</button>

<div use:grow={{ duration }}>
  Этот элемент будет увеличиваться.
</div>

В этом примере элемент будет увеличиваться с разной длительностью анимации, в зависимости от установленного параметра duration.

Заключение

Создание пользовательских директив в Svelte позволяет легко расширять функциональность фреймворка для решения специфических задач. Механизм действий гибок и мощен, что позволяет разработчикам реализовывать различные виды взаимодействий с DOM, а также улучшать производительность и отзывчивость приложений.