3 года назад Рич Харрис создал фреймворк под названием Svelte. Наибольшее влияние на этот новый фреймворк оказало то, что он не работает с Virtual DOM, как ReactJS, VueJS или AngularJS.

Я создал точно такое же приложение списка дел как в Svelte, так и в React. Я имею в виду не только его функциональность или визуальность, но также я не использовал сторонние библиотеки и использовал их внутренние API-интерфейсы для управления рабочим приложением.

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

Исходные коды Svelte и React находятся на github.

SvelteJS

$ npx degit sveltejs/template svelte-todo-list

Краткая информация о Svelte

  • Работает с DOM, без Virtual DOM
  • Файлы Svelte имеют расширение .svelte
  • Каждый ‹style› ‹/style›, написанный в компактных файлах, имеет область видимости css.
  • Вы можете использовать его API внутреннего хранилища. Нет необходимости устанавливать внешнюю библиотеку
  • Сборка накопительным пакетом с конфигурацией по умолчанию. Однако вы также можете использовать Parsel и Webpack.
  • Вы можете легко подписаться на любую переменную, но у нее есть несколько хитрых способов обнаружения мутаций массива. (Тот, который мне не понравился)
  • Вы можете писать и javascript, и html, и стили в svelte-файлах, как если бы вы пишете html-файл. (есть небольшая разница)
  • Вы получаете доступ к событиям DOM с префиксом on:, например, ‹button on: click = {} /›
  • Вам не нужно передавать обратные вызовы от ребенка родителям. Вы можете использовать createEventDispatcher.
  • Вы начинаете блок с {#} и заканчиваете {/}, например {#if}… {/ if}

ReactJS

$ npx create-react-app react-todo-list

Краткая информация о ReactJS

  • Работает с Virtual DOM
  • Вы можете использовать внутреннее глобальное управление состоянием React, которое называется Context API.
  • Вы пишете исключительно javascript, у вас нет синтаксиса блочных элементов, специально предназначенного для React, как в Svelte (#if, #each…)
  • События DOM - это просто события javascript, поэтому вы можете использовать onClick, как это написано на чистом javascript (стандартное событие DOM для щелчка - это onclick! Однако ваш OnCLICK oNCLiCk также будет отображаться браузером.)
  • Расширение файла .js.

Некоторые сравнения кода

Давайте сравним некоторые ключевые моменты между Svelte и React.

App.svelte

<script>
  import TodoHeader from "./TodoHeader.svelte";
  import TodoList from "./TodoList.svelte";
  import { itemStore } from "./store";
</script>
<style>
  main {
    font-family: sans-serif;
    text-align: center;
  }
</style>
<main>
  <div>Total item: {$itemStore.length}</div>
  <TodoHeader />
  <TodoList />
</main>

App.js

import React from 'react';
import './App.css';
import { TodoHeader } from './TodoHeader';
import { TodoList } from './TodoList';
import { TodoListProvider } from './store';
import { Total } from './Total';
function App() {
  return (
    <TodoListProvider todoList={[]}>
      <Total />
      <TodoHeader />
      <TodoList />
    </TodoListProvider>
  );
}
export default App;

Файлы приложений являются точками входа в обе платформы. Между двумя нет большой разницы, у них обоих есть компоненты для рендеринга. Единственное отличие здесь в привязке к глобальному стилю. В React компонент-оболочка для React.ContextAPI является родительским для всех дочерних элементов, тогда как в Svelte оболочка для глобального состояния не требуется.

svelte / store.js

import { writable } from "svelte/store";
const createItemStore = () => {
    const { subscribe, update } = writable([]);
return {
        subscribe,
        addItem: newItem => update(items => {
            if (!items.find(item => item === newItem)) {
                items.push(newItem);
            }
            return items;
        }),
        removeItem: removedItem => update(items => {
            const newItems = items.filter(item => item !== removedItem);
            return newItems;
        })
    };
};
export const itemStore = createItemStore();

реагировать / store.js

import React, { useState } from 'react';
export const TodoListContext = React.createContext({});
export const TodoListProvider = ({ todoList, children }) => {
    const [todos, setTodos] = useState(todoList);
const addItem = (item) => {
        if (!todos || !todos.find(listItem => listItem === item)) {
            setTodos([...todos, item])
        }
    };
    const removeItem = (item) => {
        setTodos(
            todos.filter(listItem => listItem !== item)
        )
    };
return (
        <TodoListContext.Provider
            value={{
                todoList: todos,
                addItem,
                removeItem
            }}
        >
            {children}
        </TodoListContext.Provider>
    )
}
export const TodoListConsumer = TodoListContext.Consumer;

Эти два магазина делают одно и то же. У них есть оба метода действия для изменения данных хранилища. Однако в React использование Context API и создание такой же структуры, как в Svelte, может быть болезненным. Как показано в примере выше, сначала мы создали контекст из createContext, и в нашем Provider нам пришлось использовать хуки для локального хранения значений. В Svelte все проще, вы просто создаете записываемое хранилище, инициализируете его значение и возвращаете свое значение с его действиями.

svelte / TodoList

<script>
  import { itemStore } from "./store";
  const handleRemoveItem = item => {
    itemStore.removeItem(item);
  };
</script>
<style>
  ul {
    list-style-type: none;
  }
</style>
<ul>
  {#each $itemStore as item}
    <li>
      {item}
      <button on:click={() => handleRemoveItem(item)}>Remote Item</button>
    </li>
  {/each}
  {#if $itemStore.length === 0}
    <div>There is not any item added. Please add one</div>
  {/if}
</ul>

реагировать / TodoList

import React, { useContext } from 'react';
import { TodoListContext } from './store';
export const TodoList = () => {
    const todoListContext = useContext(TodoListContext);
    const handleRemove = (item) => {
        todoListContext.removeItem(item);
    }
const { todoList } = todoListContext;
return (
        <ul>
            {todoList && todoList.map(todoList => {
                return (
                    <li>
                        {todoList}
                        <button onClick={handleRemove.bind(null, todoList)}>Remote Item</button>
                    </li>
                )
            })}
            {(todoList.length === 0) && (<div>There is not any item added. Please add one</div>)}
        </ul>
    )
}

Что заставило меня написать свое первое приложение Svelte, так это использование условий и циклов в компоненте. Использование синтаксиса другого, а не чистого javascript для циклов или условий - это то, что мне не нравится. Но доступ к элементу магазина с помощью только импорта и отсутствие необходимости в каком-либо внешнем хуке или уровне для доступа к глобальному хранилищу - это ключевое различие между Svelte и React.

svelte / TodoHeader

<script>
  import { onMount } from "svelte";
  import { itemStore } from "./store";
  let value;
  onMount(() => {
    value = "";
  });
  const handleAddItem = () => {
    itemStore.addItem(value);
    value = "";
  };
</script>
<style>
  .disabled {
    color: graytext;
  }
</style>
<input type="text" bind:value placeholder="Item name" />
<button on:click={handleAddItem} disabled={!value} class:disabled={!value}>
  Add Item
</button>

реагировать / TodoHeader

import React, { useContext, useState } from 'react';
import { TodoListContext } from './store';
export const TodoHeader = () => {
    const todoListStore = useContext(TodoListContext);
    const [itemName, setItemName] = useState('')
const handleAddItem = (e) => {
        e.preventDefault();
        todoListStore.addItem(itemName);
        setItemName('');
    }
return (
        <>
            <input placeholder="Item name" type="text" value={itemName} onChange={(event) => setItemName(event.target.value.trim())} />
            <button onClick={handleAddItem} disabled={!itemName} className={!itemName && 'disabled'}>Add Item</button>
        </>
    )
}

В Svelte вам не нужно сохранять значение ввода во внешнем состоянии. Svelte упрощает привязку ключевого слова к любому существующему свойству, с дополнительной функцией, которая позволяет отслеживать изменения этой опоры с помощью реактивного объявления без дополнительных усилий для значений (не для массивов).

Полученные результаты

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

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

Что мне нравится в Svelte ❤️

  • Строительство действительно быстрое. Но это может быть также из-за использования Rollup в качестве сборщика, я думаю, было бы неплохо протестировать сборку также с Webpack.
  • Опять же, размер пакета действительно мал по сравнению с React. Однако это также может быть связано с производительностью Rollup «встряхивая деревья».
  • Использование магазина действительно гибкое. Вы можете адаптировать свои компоненты в магазине и легко начать подписываться или изменять значения магазина. ❤️
  • Мне понравилось, что все стили, написанные в компоненте, имеют область видимости css.
  • Связывание условного стиля имеет простой синтаксис, который не требует создания логики для связывания классов. (класс: отключено = переменная)

Что мне не нравится в Svelte 💔

  • Мне очень не нравится писать шаблоны в файл, например #each, #if. Я не предпочитаю использовать новые подходы, для которых я могу делать это исключительно с помощью JavaScript.
  • Когда вы используете реактивные объявления для подписки на изменение переменной, Svelte не может обнаруживать мутации в массивах. Svelte предлагает несколько хитростей для этой ситуации.
  • Некоторым разработчикам может понравиться написание шаблонов в компонентах, но это все еще то, что мне не нравится. Кроме того, этот подход предполагает использование шаблонов для циклов, условий и т. Д., О которых я упоминал в пункте 1.
  • Вы должны указать уникальный идентификатор для каждого элемента в списке, иначе всякий раз, когда вы захотите выполнить действие с элементом в списке, может быть выполнен неправильный элемент.
  • Мне не нравится стиль использования в событиях DOM, таких как on: click. Я предпочитаю использовать onClick как обычное событие DOM. (стандартным событием DOM для щелчка является onclick! однако ваш OnCLICK oNCLiCk также будет отображаться браузером.)

Сравнение производственной сборки

SvelteJS использует Rollup и ReactJS Webpack по умолчанию, и я не касался их конфигураций, они имеют конфигурацию по умолчанию. Время сборки Svelte составляло 1,40 с, а React - 5,49 с. Здесь Svelte побеждает! ❤️

Оба приложения являются серверными, обслуживая пакет npm. Svelte экспортирует свои пакеты в общую папку, а приложение React помещает их в папку сборки.

serve -s build  // this is for react app
serve -s public // this is for svelte app

Когда я проверил вкладку сети, пакет Svelte занимает всего 2,9 КБ после gzip, тогда как пакет React составляет всего 42,1 КБ после gzip. и здесь Svelte выигрывает своими размерами комплектации на продакшене ❤️

Если вам нравятся мои статьи, вы можете поддержать меня, хлопая в ладоши и подписываясь на меня.
Я тоже на linkedin, все приглашения приветствуются.