Перейти к содержанию

Знакомство с популярными JS фреймворками - Svelte.js

В рунете пишут, что знание Svelte никому не нужно, мол, React-программисты в приоритете. Не верьте. Никогда не знаешь, что будет завтра.

В этой серии⚓︎

Вступление⚓︎

Если вы относитесь к любителям минимализма во всём, в том числе в размерах компилируемых файлов, то Svelte — ваш выбор. Почему? Читайте далее.

Подготовка⚓︎

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

npm create vite@latest svelte-todo -- --template svelte

Теперь перейдите в созданную папку svelte-todo и установите tailwindcss:

npm i -D tailwindcss@next @tailwindcss/vite@next
pnpm create vite svelte-todo --template svelte

Теперь перейдите в созданную папку svelte-todo и установите tailwindcss:

pnpm add -D tailwindcss@next @tailwindcss/vite@next
yarn create vite svelte-todo --template svelte

Теперь перейдите в созданную папку svelte-todo и установите tailwindcss:

yarn add -D tailwindcss@next @tailwindcss/vite@next
bun create vite svelte --template svelte

Теперь перейдите в созданную папку svelte-todo и установите tailwindcss:

bun add -D tailwindcss@next @tailwindcss/vite@next

и обновите vite.config.js:

import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import tailwindcss from '@tailwindcss/vite';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [svelte(), tailwindcss()],
});

В файл src/app.css замените всё содержимое на следующий код:

@import "tailwindcss";

Файл src/main.js:

import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'

const app = mount(App, {
  target: document.getElementById('app'),
})

export default app

Файл src/App.svelte:

<script>
  import TodoList from './lib/TodoList.svelte';
</script>

<TodoList title="Список дел" />

Осталось запустить dev-сервер и начать создавать компоненты:

npm run dev
pnpm run dev
yarn dev
bun run dev

Компонент TodoList⚓︎

Как и прежде, начнём с основного компонента. Создайте в директории src/lib файл TodoList.svelte:

<script>
  let { title = '' } = $props();
  let todos = $state([]);
</script>

<div class="max-w-sm md:max-w-lg mx-auto my-10 bg-white rounded-md shadow-md overflow-hidden">
  <h1 class="text-2xl font-bold text-center py-4 bg-gray-100">{title}</h1>
  {#if todos.length}
    <ul class="list-none p-4">

    </ul>
  {/if}
</div>

Наш компонент будет принимать входящий параметр title, поэтому в скрипте Svelte мы пишем let { title = '' } = $props();. Привыкайте.

Далее устанавливаем переменную todos, которая будет массивом, хранящим список дел. Для этого обернём начальное значение с помощью $state:

  let todos = $state([]);

Примечание

В Svelte такие выражения, начинающиеся со знака $, называются рунами: $state, $props и т. д.

А в разметке с помощью условия if отображаем список, если массив todos не пустой:

{#if todos.length}
  <ul class="list-none p-4">

  </ul>
{/if}

Теперь создайте заготовки компонентов TodoItem.svelte и TodoForm.svelte и импортируйте их, а затем используйте в разметке:

<script>
  import TodoItem from './TodoItem.svelte';
  import TodoForm from './TodoForm.svelte';

  // ...
</script>

<div class="max-w-sm md:max-w-lg mx-auto my-10 bg-white rounded-md shadow-md overflow-hidden">
  <h1 class="text-2xl font-bold text-center py-4 bg-gray-100">{title}</h1>
  {#if todos.length}
    <ul class="list-none p-4">
      {#each todos as todo (todo.id)}
        <TodoItem {todo} />
      {/each}
    </ul>
  {/if}
  <TodoForm />
</div>

Здесь мы видим each — аналог цикла for. Обратите внимание, в каком порядке указаны переменные, а также индекс (todo.id):

{#each todos as todo (todo.id)}
  <TodoItem {todo} />
{/each}

Примечание

В Svelte подобные конструкции начинаются с #, а заканчиваются /. В цепочке условий if-else ещё добавляется и двоеточие: {#if x}<span>Х больше 0</span>{:else}<span>X меньше 0</span>{/if}

Теперь добавим метод для запроса списка задач с JSON-сервера, а также настроим выполнение этого метода при загрузке страницы:

<script>
  import TodoItem from './TodoItem.svelte';
  import TodoForm from './TodoForm.svelte';

  let { title = '' } = $props();
  let todos = $state([]);

  $effect(() => {
    (async () => {
      await fetch('https://dummyapi.online/api/todos')
        .then((response) => response.json())
        .then((data) => {
          todos = data.slice(0, 10);
        });
    })();
  });
</script>

Осталось реализовать методы удаления и добавления задач, которые мы будем передавать в компоненты TodoItem и TodoForm, соответственно:

<script>
  // ...

  const addTodo = (title) => {
    if (!title) return;

    todos = todos.concat({
      id: crypto.randomUUID(),
      title: title,
      completed: false,
    });
  };

  const toggleTodo = (id) => {
    todos = todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t));
  };

  const deleteTodo = (id) => {
    todos = todos.filter((todo) => todo.id !== id);
  };
</script>

<div class="max-w-sm md:max-w-lg mx-auto my-10 bg-white rounded-md shadow-md overflow-hidden">
  <h1 class="text-2xl font-bold text-center py-4 bg-gray-100">{title}</h1>
  {#if todos.length}
    <ul class="list-none p-4">
      {#each todos as todo (todo.id)}
        <TodoItem {todo} toggle={() => toggleTodo(todo.id)} remove={() => deleteTodo(todo.id)} />
      {/each}
    </ul>
  {/if}
  <TodoForm submit={addTodo} />
</div>

Примечание

Как вы могли заметить, в Svelte реактивность включается только при прямом присваивании. Например, чтобы обновить наш список дел, недостаточно использовать обычные методы типа push и т. п., нужно обязательно использовать знак равенства: todos = новое значение. Именно поэтому реактивные переменные объявляются с помощью let, а не const.

Компонент TodoItem⚓︎

Теперь допишем компонент TodoItem. Нам нужны методы переключения и удаления задач:

<script>
  let { todo = $bindable(), toggle, remove } = $props();
</script>

Осталось связать написанные методы с соответствующими кнопками в разметке:

<li class='flex items-center mb-2 hover:cursor-pointer' onclick={toggle}>
  <input type='checkbox' class='mr-2' bind:checked={todo.completed} />
  <span class:line-through={todo.completed}>{todo.title}</span>
  <div class='ml-auto'>
    <button class='text-gray-400 hover:text-gray-600' onclick={remove}>
      <svg
        xmlns='http://www.w3.org/2000/svg'
        fill='none'
        viewBox='0 0 24 24'
        stroke-width='1.5'
        stroke='currentColor'
        class='w-6 h-6'
      >
        <path
          stroke-linecap='round'
          stroke-linejoin='round'
          d='M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0'
        />
      </svg>
    </button>
  </div>
</li>

Здесь мы знакомимся с директивой bind:, которая связывает атрибут checked чекбокса с соответствующим значением todo.completed.

Также здесь можно увидеть изменение класса в зависимости от значения переменной: <span class:line-through={todo.completed}>.

Компонент TodoForm⚓︎

И напоследок реализуем добавление задачи. Нам опять понадобится передача события.

<script>
  let { submit } = $props();
  let input = $state();

  const onclick = () => {
    submit(input.value);

    input.value = '';
    input.focus();
  };
</script>

А теперь привяжем с помощью bind: состояние input к элементу input:

<div class='p-4 bg-gray-100'>
  <div class='flex items-center'>
    <input
      bind:this={input}
      type='text'
      class='flex-1 mr-2 py-2 px-4 rounded-md border border-gray-300'
      placeholder='Новая задача'
      autofocus
    />
    <button
      class='bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-md'
      {onclick}
    >
      Добавить
    </button>
  </div>
</div>

Документация⚓︎

Если вы заинтересовались Svelte, загляните на этот сайт.

Заключение⚓︎

Итак, мы закончили наше простое приложение TODO на Svelte:

  • успешно адаптировали исходную разметку
  • познакомились с некоторыми директивами

В следующей части мы познакомимся с таинственным солидным конкурентом.


Скачать готовый проект

Комментарии