RE: Боль и слёзы в Svelte 3

в 6:26, , рубрики: javascript, ReactJS, svelte, SvelteJs, vuejs

Вместо предисловия

Данный пост является ответом на вчерашнюю статью «Боль и слёзы в Svelte 3» и появился как следствие сильно «располневшего» комментария к оригинальной статье, который я решил оформить в виде поста. Ниже я буду использовать слово автор для отсылки к автору оригинальной статьи и позволю себе сделать некоторые уточнения по всем пунктам. Поехали!

RE: Боль и слёзы в Svelte 3 - 1

Кто такой Svelte?

Когда я увидел заголовок оригинальной статьи, сперва очень обрадовался. Вот сейчас, думаю, прочитаю какую-то глубокую и конструктивную критику. А главное интересные кейсы с подводными камнями не от «диванных экспертов», а от ребят «которые смогли». После прочтения, энтузиазма поубавилось, но все равно большое спасибо vds64_max за статью.

Несмотря на то, что не все получилось, мне кажется нужно больше статей описывающих реальные проблемы и их решения в Svelte. Хотя бы чтобы он не отставал от более именитых товарищей, таких как React или Vue. Ведь, в конечном итоге, у читателей может возникнуть ощущение, что Svelte слишком идеален, а это безусловно не так.

Tutorial

Так и не понял, что автор имел ввиду в этой части. Tutorial работал и работает прекрасно. Предполагаю, что автор лишь поверхностно ознакомился с ним из-за дедлайнов своего проекта и не смог понять принцип работы. Уже бывали случаи, когда люди не сразу улавливали смысл пошаговости туториала, когда чтобы пример заработал нужно сделать какое-то действие. Попробуйте сами!

UI Kit и Стили

RE: Боль и слёзы в Svelte 3 - 2

Поиски UI Kit для Svelte были отдельной болью для всех нас. Хотелось воскликнуть: «Хоть Material, Bootstrap… хоть что нибудь...».

Во-первых, далеко не во всех проектах в принципе применимы UI киты. Во-вторых, далеко не все UI киты для других фреймворков работают хорошо.

Основные проблемы начинаются, когда проект имеет сильно замороченный и кастомный дийзан, а киты обычно все же имеют лимитированные средства для кастомизации. Если же речь об админке, согласен, иметь UI кит это полезно, но далеко не всех пишут админки, да и профит от Svelte для backoffice будет не так заметен.

По ссылке можете ознакомиться и с другими UI китами для Svelte.

Из-за того, что Svelte работает с DOM «по другому» в MaterialUI начали вылазить всякие гадости, связанные с тем как отображаются UI компоненты которые добавляются через js в dom. Например простой спиннер отображается через раз:

Так и не понял, что означает начало предложения и как именно «по-другому» работает Svelte с DOM, но в целом весь тезис звучит, по крайней мере, не серьезно. Если вы попробуете интегрировать стороннюю либу работающую с DOM в любой из фреймворков (React/Vue/Angular/Ember), управляющих DOM, у вас будут всплывать точно те же вопросы. Сразу возникает ощущение, что автор никогда не делал этого.

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

Для примера, имплементация MDCSlider за 2 минуты: REPL
RE: Боль и слёзы в Svelte 3 - 3
Скажите, пожалуйста, куда уж проще?

Стилизация

Со стилями все предельно ясно, мы запихиваем все стили в как в Vue. Вы пишите стиль и все в порядке, и потом пишите компонент UI (так как у Вас нет UIKit) который должен принимать параметры props, например width и height, и логично делаете это так:

А… нет, в стиле у Вас не получится вставлять переменные.

Насколько мне известно, во Vue стейт также не может использоваться внутри стилей компонента, а в React вообще нет работы со стилями из коробки.

Хотя, я могу понять желание использовать динамику в стилях, но есть ряд причин почему так делать не надо:

  • Как только кусок стейта компонента появляется в стилях эти стили надо дублировать для КАЖДОГО инстанса компонента. Сразу представляем список из 1000 айтемов со сложным дизайном. Оно нам надо?
  • Использование динамики в css вызывает много лишних перерисовок и крайне не производительно.

Альтернативные способы использовать динамические стили в Svelte:

  • Смена классов с помощью директивы class:
  • Использование инлайн стилей с помощью атрибута style
  • Использование css custom properties (variables)
  • Использование css-in-js решения, таких как Emotion (статья на эту тему)

И раз уж был упомянут Vue, давайте сравним использование динамических инлайн стилей:

<button v-on:click="fontSize++">Increase font size</button>
<button v-on:click="fontSize--">Decrease font size</button>

<p v-bind:style="{ fontSize: fontSize + 'px' }">Font size is: {{ fontSize }}</p>
<button on:click={e => fontSize++}>Increase font size</button>
<button on:click={e => fontSize--}>Decrease font size</button>

<p style="font-size: {fontSize + 'px'}">Font size is: {fontSize}</p>

REPL

В целом, особых отличий я не вижу, но автор почему-то считает что в Vue все с этих хорошо, а в Svelte нет. Странно это.

Кстати, с помощью реактивных деклараций, можно довольно удобно собирать все динамические стили в одном месте:

<div {style}>....</div>

<script>
  let fontSize = 10;
  let color = 'red';
  let width = 50;

  $: style = `
    font-size: ${fontSize}px;
    color: ${color};
    width: ${width}px;
  `;
</script>

Обычно этого хватает для 99% случаев, а остальное решается с помощью тем и/или css custom properties.

Routing и ротуеры

Вообще, Svelte — это UI фреймворк и он агностичен к роутеру. Более того, внешний апи Svelte позволяет легко интегрировать любой независимый роутера буквально за пару минут и строк кода. На примере page.js:

import page from 'page';

import App from './App.svelte';

const app = new App({ 
  target: document.body 
});

page('/posts', ctx => {
   // можно даже сразу с dynamic import & code-splitting
  app.$set({ ctx, page: import('./Posts.svelte') });
});

page('/', ctx => {
  app.$set({ ctx, page: import('./Home.svelte') });
});

page.start();

export default app;
<nav>
  <a href="/">Home</a>
  <a href="/posts">Posts</a>
</nav>

<main>
{#await page}
  <p>Loading...</p>
{:then comp}
  <svelte:component this={comp.default || comp} {...ctx} />
{/await}
</main>

<script>
  export let page = null, ctx = null;
</script>

Разве это сложно? Автор столкнулся лишь с проблемами конкретной реализации, конкретного роутера. Который, кроме того, мягко говоря не самый лучший из того что имеется для Svelte, так как копирует React Router со всеми его проблемами. Можете ознакомиться с множеством других роутеров по ссылке.

Ошибки

В отличии от React который просто не даст Вам создать bundle и будет сильно «кричать» об ошибке, Svelte отлично соберёт всё с ошибкой. При этом если Вы забыли что-то npm install'нуть или export'ануть, все отлично соберется и просто покажет белый экран (Кстати говоря так было в первых версиях React).

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

Есть предположение, что автор имел ввиду также работу с пропсами, которая в Svelte ведется через export'ы из компонента, но даже тут всегда будет выведено предупреждение, если попытаться прокинуть пропс не определенный компонентом:
RE: Боль и слёзы в Svelte 3 - 4

Svelte просто необходим naming convention

C этой проблемой Вы будете сталкиваться довольно часто — с отсутствием конвенции по наименованию.

Очень понравилась эта фраза, потому что по-сути ее можно использовать для любого проекта/библиотеки/языка программирования. Лукавство здесь заключается в том, что точно также вы будете сталкиваться с этим везде. Например, в том же React:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.dialog = null;
    this.loading = false;
    this.pins = [];
    this.stitch = null;
  }
  render() {
    return <dialog ref={el=> this.dialog = el} class="mdl-dialog» />;
  }
}

В общем и целом все тоже самое и нужно будет как-то «понимать» что есть рефка на элемент, а что есть объект для работы с бекендом или флажок загрузки. Казалось бы, если у вас команда, которая собралась не вчера, все эти вопросы давно должны быть решены.

В целом конечно, если у вас в проекте и команде нет naming convention тут вам Svelte ничем не поможет. Некоторые вещи которые использую в своих проектах:

<!-- шаблон сверху, хотя в доках советуют внизу -->
<Modal {open}> <!-- использовать шорткаты как можно чаще -->
  <!-- использовать модификаторы, чтобы отвязать функции от ивентов, там где это имеет смысл -->
  <form on:submit|preventDefault={save}>
     <!-- сначала директивы, потому атрибуты -->
    <input bind:value={firstName} placeholder="First name"> 
    <input bind:value={lastName} placeholder="Last name">
    <input bind:value={birthDay} use:asDatepicker placeholder="Birthday">
    <button bind:this={btnEl}>Save</button>
  </form>
</Modal>

<button on:click={e => open = true}>Open</button>

<!-- далее логика -->
<script>
  // группируем импорты по типам

  // компоненты - это классы, поэтому с большой буквы
  import Modal from '~/components/Modal';

  // для экшенов использую префик `as`, получается читаемо `use:as<something>`
  import asDatepicker from '~/actions/datepicker'; 

  // так как сторы - это observerable, использую нейминг из cycle/rx
  import user$ from '~/stores/user'; 

  import { saveUser } from '/api';

  // группируем переменные по смыслу
  let firstName = '',
      lastName = '',
      birthDay = '';

  let open = false;

  let btnEl;  // для рефов на элементы или компоненты можно давать понятные имена
  let refs = {}; // либо просто хранить их в объекте

  async function save() {
      const patch = { firstName, lastName, birthDay };
      await saveUser(patch);
      $user$ = { ...$user$, ...patch };
  }

  // мне нравится видеть весь апи компонента в одном месте
  export { open,  save };
</script>

<!-- далее стили -->
<style>
 /* ... */
</style>

Кроме того, обычно мои компоненты имеют «бюджет» в 200 строк кода вместе со стилями. Если чуть больше — не страшно. Если сильно больше, я создаю для компонента папку и начинаю выносить части в отдельный файлы с помощью svelte-preprocess, начиная со стилей. При этом сборщик настроен так, что импорты не меняются.

Что где изменилось и кто это сделал ?

RE: Боль и слёзы в Svelte 3 - 5

В итоге Вы быстро пишете компоненты но понять, что написал джуниор в этом «венегрете» через несколько дней когда он сам уже забыл (иногда даже как его зовут) бывает очень сложно. Из-за этого будет невозможно понять что внутри без четкой организации кода внутри проекта или команды.

Забавный тезис, а главное опять же применимый вообще к любому фреймворку и проекту. Более того, синтаксис хуков в современном React, так полюбившийся создателю Vue, что он даже решил срочно внедрить его во Vue 3, будет иметь все те же коллизии:

import React, { useState, useCallback } from 'react';

function MyComponent() {
  const [ loading, setLoading ] = useState(false);
  const [ status, setStatus ] = useState(0);
  const [ pins, setPins ] = useState([]);

  const ReloadPins = useCallback(async () => {
        setLoading(true);
        setPins(await getAllPins());
        setStatus(0);
  });

  return (<div></div>);
}

vs

<div></div>

<script>
  let loading = false;
  let status = 0;
  let pins = [];

  async function ReloadPins() {
    loading = true;
    pins = await getAllPins();
    status = 0;
  }
</script>

Как видите, все тоже самое, только писанины в 2 раза больше.

Шучу, конечно же ничего не ясно. Причем даже не понятно меняется состояние или это просто вспомогательная переменная. В React это решается state, который хоть как-то вносит ясности.

На самом деле, нет никакого смысл «понимать» какая переменная стейта компонента используется в шаблоне, а какая необходима только для работы скрипта. В Svelte вообще нет такого разделения, там переменные живут обычно JS жизнью — существуют в своем скоупе, некоторые имеют скоуп всего компонента, другие обитают в рамках одной функции или иного блока кода. Ничего нового или неожиданного. Важно понимать лишь одно — DOM пересчитывается исключительно тогда, когда изменяется связанная с ним часть стейта.

Например, в том же React, если сделать:

this.setState({ foo: 1 })

и при этом foo НЕ используется в шаблоне, механизм VDOM никак не сможет понять этого и произведет rerender/reconcile. Вы знали об этом? Поэтому, наверное, в React важно понимать, что используется в шаблонах, а что нет. В Svelte мы избавлены от подобных волнений.

В итоге, тут я могу лишь вторить автору — если у вашего проекта и команды нет четкой организации у вас будут проблемы с любым инструментом, на любом проекте. Собственно, пример проекта автора тому прямое доказательство.

Привязываем Stitch

Конечно же это связано со сборщиком. Автору не следует так легкомысленно относиться к новым инструментом. По-умолчанию, Svelte идет в поставке с Rollup, который «из коробки» поддерживает намного меньше фичей, чем Webpack, зато собирает более оптимизированные и более нативные бандлы. Любой дополнительный функционал можно донастроить с помощью плагинов. Если же не хочется париться, то можно взять официальный шаблон для Webpack, либо любой другой от сообщества, благо их прям много.

Для чего Вам пригодится Svelte ?

В этом пункте, буду более краток — для любых новых проектов, для которых достаточно React или Vue. С Angular или Ember сравнивать не имеет смысла, потому что это фреймворка другого масштаба.

Исходя из последнего я совсем не понимаю зачем мэйнтэйнеры Svelte собираются делать поддержку TypeScript? Мы же любим Svelte как раз за «стрельбу по ногам» и TypeScript на мой взгляд это как вседорожные гусеницы для спорткара Svelte. И знаете, поработав со Svelte, я понял на сколько сильно за последние годы изменился JS, и что он совсем уже не JS.

Svelte даёт возможность поработать в еще том, старом и ламповом JS, без PropTypes, Flow и TypeScript.

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

Почему НЕ стоит использовать Svelte ?

  1. Если ваш проект уже написан на другом фреймворке и работает сносно. Не нужно бежать и переписывать его.
  2. Даже в п.1 есть окно возможностей — можно переписать некоторые «тяжелые» компоненты на Svelte. Проект станет легче, а компоненты будут работать быстрее. При этом не придется переписывать все.
  3. Если вы пишете энтерпрайз, скорее всего лучше подойдет Angular или Ember. Svelte, ровно как и React и Vue, не всегда хороший выбор для этого.
  4. Если если вам нужна first-class поддержка Typescript.
  5. Если вы разрабатываете исключительно типовые решения и привыкли собирать проекты из готовых компонентов.
  6. До сих пор более слабый тулинг, чем у более матерых товарищей. Если это критично, тогда стоит подождать, либо присоединиться к нашему сообществу и развивать тулинг вместе.)))

***

В целом, к сожалению, большая часть выводов из оригинальной статьи основана на ложных предпосылках и вводит читателей в заблуждение. Учитывая, что я не видел автора в русскоязычном комьюнити Svelte в Телеграм @sveltejs (1К+ участников), делаю вывод, что автор и его команда приложили недостаточно усилий при освоения нового, неизведанного для себя инструмента. Думаю если бы ребята просто добавились в чатик, большей части проблем удалось бы избежать. В данный момент кажется ребята сделали довольно поспешные выводы, впрочем это их личное дело.

Автор: Павел Малышев

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js