Веб-приложения становятся все популярнее день ото дня. Это растущий мир, который люди выбирают за его простоту, скорость и кроссплатформенность. Одностраничные приложения (SPA) сыграли огромную роль в этом процессе. Такие фреймворки, как Angular, Vue.js и React, помогают разработчикам обеспечить наилучшее взаимодействие с пользователем за короткий период, оставляя код поддерживаемым и расширяемым. Эти инструменты долгое время оставались самыми популярными в этой области, обладая многими преимуществами по сравнению с недавно созданными пакетами. Это было похоже на олигополию в мире SPA. Однако группа дальновидных разработчиков, нацеленных на этот рынок, может выйти с серьезным конкурентом - Svelte.

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

Архитектура

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

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

Установка

Установка Svelte невероятно проста, что делает ее использование очень приятным. Первый шаг - загрузить шаблон проекта:

npx degit sveltejs/template svelte-login-form

Выполнение указанной выше команды означает, что у нас есть шаблон проекта Svelte. На данный момент он пуст, и необходимые пакеты NPM еще не установлены. Давай исправим это.

cd svelte-login-form npm install

Теперь приложение готово к запуску с помощью следующей команды:

npm run dev

Состав

Любой компонент Svelte может содержать следующие разделы:

  • Сценарий
  • Стиль
  • Шаблон

Давайте посмотрим на пример в src/App.svelte файле.

<script>
  export let name;
</script>

<style>	
  h1 {
    color: purple;
}
</style>

<h1>{name}</h1>

Приведенный выше код содержит ровно три раздела:

  1. script, который является необязательным блоком JavaScript с объявлениями переменных и функций, которые должны использоваться внутри компонента.
  2. style - еще один необязательный блок. Это очень похоже на обычный тег стиля HTML, за исключением одного важного различия. Правила, описанные внутри этого блока, относятся только к этому компоненту. Применение стиля к элементу p не повлияет на все абзацы на странице. Это фантастика, поскольку вам не нужно придумывать имена классов, и вы никогда случайно не переопределите другое правило.
  3. Последним и единственным обязательным блоком является блок шаблона - в данном случае тег h1. Это презентация / представление вашего компонента. Он жестко привязан к блокам style и script, поскольку они определяют, как будет оформляться представление и как оно будет себя вести.

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

Возвращаясь к создаваемой нами форме входа, давайте создадим новый файл LoginForm.svelte внутри папки src со следующим содержимым:

<style>	
  form {
    background: #fff;
    padding: 50px;
    width: 250px;
    height: 400px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    box-shadow: 0px 20px 14px 8px rgba(0, 0, 0, 0.58);
  }
  label {
    margin: 10px 0;
    align-self: flex-start;	
    font-weight: 500;
  }
  input {
    border: none;
    border-bottom: 1px solid #ccc;
    margin-bottom: 20px;
    transition: all 300ms ease-in-out;
    width: 100%;
  }
  input:focus {
    outline: 0;
    border-bottom: 1px solid #666;
  }
  button {
    margin-top: 20px;
    background: black;
    color: white;
    padding: 10px 0;
    width: 200px;
    border-radius: 25px;
    text-transform: uppercase;
    font-weight: bold;
    cursor: pointer;
    transition: all 300ms ease-in-out;
  }
  button:hover {
    transform: translateY(-2.5px);
    box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.58);
  }
  h1 {
    margin: 10px 20px 30px 20px;
    font-size: 40px;
  }
</style>


<form>	
  <h1>👤</h1>

  <label>Email</label>
  <input name="email" placeholder="[email protected]" />

  <label>Password</label>
  <input name="password" type="password" placeholder="password" />

  <button type="submit">Log in 🔒</button>
</form>

Это глупый компонент, который мы сделаем умнее. Чтобы увидеть этот компонент на нашем сайте, мы должны отобразить его внутри корневого компонента - App. Пойдем и отредактируем src/App.svelte, чтобы он выглядел так:

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

<style>	
  section {
    height: 100vh;
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(to right, #cd76e2, #e358ab);	
  }
</style>

<section>
  <LoginForm />
</section>

Если все было сделано правильно и приложение все еще работает, наша форма появится на localhost:5000. Давайте повысим наши навыки Svelte, сделав форму умнее.

Переход с сохранением состояния

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

Мы объявим переменные состояния email и password, в которых будут храниться значения формы для соответствующих полей. Это означает, что наши переменные email и password всегда будут синхронизированы со значениями формы, поэтому мы будем готовы отправить эти значения в любое время, не опасаясь каких-либо различий между значениями отправки и фактическими значениями в форме.

<script>
  let email = "";
  let password = "";
  let isLoading = false;
  const handleSubmit = () => {
      isLoading = true;
      // Simulate network request
      setTimeout(() => {
        isLoading = false;
        // Authorize the user
      }, 1000);
  };
</script>

<style>	
/* Style is unchanged */
</style>


<form on:submit|preventDefault={handleSubmit}>
  <h1>👤</h1>

  <label>Email</label>
  <input name="email" placeholder="[email protected]" bind:value={email} />

  <label>Password</label>
  <input name="password" type="password" bind:value={password} />

    {#if isLoading}Logging in...{:else}Log in 🔒{/if}
</form>

Переменные состояния выглядят как обычные переменные JavaScript, но чтобы синхронизировать их со значениями формы (привязать их к полям формы), необходимо использовать директиву bind:value. Также есть пара незнакомых вещей:

  • on:submit|preventDefault - это сокращение для предотвращения поведения событий по умолчанию. Так удобнее, чем писать e.preventDefault() каждый раз.
  • {#if isLoading}Logging in...{:else}Log in 🔒{/if} - это часть синтаксиса шаблона Svelte. Поскольку в блоке шаблона нет JS, существует специальный синтаксис для использования if, циклов и т. Д.

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

<script>
  let email = "";
  let password = "";
  let isLoading = false;
  let errors = {};
  const handleSubmit = () => {
    errors = {};
    if (email.length === 0) {
      errors.email = "Field should not be empty";
    }
    if (password.length === 0) {
      errors.password = "Field should not be empty";
    }
    if (Object.keys(errors).length === 0) {
      isLoading = true;
      // Simulate network request
      setTimeout(() => {
        isLoading = false;
        // Authorize the user
      }, 1000);
    }
  };
</script>

<style>	
  // Previous styles unchanged
  .errors {
    list-style-type: none;
    padding: 10px;
    margin: 0;
    border: 2px solid #be6283;
    color: #be6283;
    background: rgba(190, 98, 131, 0.3);
  }
</style>

<form on:submit|preventDefault={handleSubmit}>
  <h1>👤</h1>
  
  <label>Email</label>
  <input name="email" placeholder="[email protected]" bind:value={email} />
  
  <label>Password</label>
  <input name="password" type="password" bind:value={password} />
  
  <button type="submit">
    {#if isLoading}Logging in...{:else}Log in 🔒{/if}
  </button>
  
  {#if Object.keys(errors).length > 0}
    <ul class="errors">
      {#each Object.keys(errors) as field}
        <li>{field}: {errors[field]}</li>
      {/each}
    </ul>
  {/if}
</form>

Форма почти завершена. Единственное, что остается, - это сообщение об успешном прохождении аутентификации.

Давайте создадим переменную состояния для отслеживания успешных отправок, которая по умолчанию равна false. После успешной отправки формы значение этой переменной должно быть установлено на true.

let isSuccess = false;

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

const handleSubmit = () => {
  errors = {};
  if (email.length === 0) {
    errors.email = "Field should not be empty";
  }
  if (password.length === 0) {
    errors.password = "Field should not be empty";
  }
  if (Object.keys(errors).length === 0) {
    isLoading = true;
    // Simulate network request
    setTimeout(() => {
      isLoading = false;
      isSuccess = true;
      // Authorize the user
    }, 1000);
  }
};

Эта модификация переводит форму в состояние успешного завершения, как только отправка будет завершена.

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

<form on:submit|preventDefault={handleSubmit}>
  {#if isSuccess}
    <div class="success">
      🔓
      <br />
      You've been successfully logged in.
    </div>
  {:else}
    <h1>👤</h1>

    <label>Email</label>
    <input name="email" placeholder="[email protected]" bind:value={email} />

    <label>Password</label>
    <input name="password" type="password" bind:value={password} />

    <button type="submit">
      {#if isLoading}Logging in...{:else}Log in 🔒{/if}
    </button>

    {#if Object.keys(errors).length > 0}
      <ul class="errors">
        {#each Object.keys(errors) as field}
          <li>{field}: {errors[field]}</li>
        {/each}
      </ul>
    {/if}
  {/if}
</form>

Аннотация со свойствами

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

Объявление свойства похоже на состояние, за исключением ключевого слова export.

<script>
	export let answer;
</script>

<p>The answer is {answer}</p>
<script>
	import Nested from './Nested.svelte';
</script>

<Nested answer={42}/>

Все дело в свойствах. Объявить и передать - все, что вам нужно знать, чтобы использовать реквизит.

Но как эти свойства применяются к компоненту формы входа в систему? Props может сделать нашу форму входа более универсальной, извлекая функцию отправки в свойство. Это позволит вам использовать этот компонент с любым действием отправки, которое вам нужно (запрос на тестовый сервер, запрос на реальный сервер и т. Д.). Эта опора будет называться submit и будет функцией, которая возвращает обработанное обещание, если действие отправки прошло успешно, и отклоненное обещание, если есть ошибка. Объявим опору в примере, приведенном выше:

export let submit;

Обработчик отправки внутри формы входа также должен быть отредактирован, чтобы использовать новое свойство submit.

const handleSubmit = () => {
  errors = {};
  if (email.length === 0) {
    errors.email = "Field should not be empty";
  }
  if (password.length === 0) {
    errors.password = "Field should not be empty";
  }
  if (Object.keys(errors).length === 0) {
    isLoading = true;
    submit({ email, password })
      .then(() => {
        isSuccess = true;
        isLoading = false;
      })
      .catch(err => {
        errors.server = err;
        isLoading = false;
      });
  }
};

Компонент вроде готов. Однако, если вы вернетесь к форме и попытаетесь отправить ее, вы заметите, что состояние кнопки не изменилось с момента загрузки. Также в консоли есть исключение: Uncaught TypeError: submit is not a function. Мы, конечно, объявили опору, но забыли ее передать. Давайте объявим функцию в компоненте приложения и передадим ее в форму входа.

const submit = ({ email, password }) =>
  new Promise((resolve, reject) => setTimeout(resolve, 1000));

<section>
  <LoginForm submit={submit} />
</section>

Теперь форма работает, как задумано. Он может как отображать ошибки, так и информировать пользователя об успешном входе в систему.

Совместное использование контекста

Вроде перечислено все необходимое для сборки приложения. Со свойствами и внутренним состоянием мы готовы к работе. Однако это правда лишь отчасти. Эти два основных момента позволяют разрабатывать SPA высокой сложности. Однако если вы попытаетесь обмениваться данными между множеством различных компонентов, вам это будет очень сложно.

Самый простой пример - глобально доступная переменная user. Многие компоненты должны изменить свое поведение, связанное с пользователем, в зависимости от роли, возраста, статуса пользователя и т. Д. Однако это не СУХОЕ повторение, передавая пользователя каждому компоненту в приложении с помощью свойств.

У Svelte есть решение для этого: API контекста.

Контекстный API предоставляет механизм, позволяющий компонентам «общаться» друг с другом без передачи данных и функций в качестве свойств или отправки множества событий. Это сложная, но полезная функция.

Давайте добавим контекст пользователя в форму для входа, которую мы разрабатываем. В папке src создайте файл userContext.js со следующим содержимым:

export const key = "userContext";
export const initialValue = null;

key - это уникальный идентификатор контекста, поскольку приложение может иметь неограниченное количество различных контекстов, которые должны оставаться доступными. initialValue - это просто значение по умолчанию для контекста перед его установкой.

Следующим шагом будет добавление контекста в наше приложение. Перейдите к файлу App.svelte и добавьте 2 оператора импорта:

import { onMount, setContext } from "svelte";
import {
  key as userContextKey,
  initialValue as userContextInitialValue
} from "./userContext";

Глядя на приведенный выше код, вы можете задаться вопросом, что мы импортируем из пакета svelte. onMount - вспомогательная функция, требующая в качестве аргумента функцию обратного вызова. Этот обратный вызов будет выполнен при монтировании текущего компонента (в самом начале загрузки компонента). setContext - это функция-установщик для контекста. Ему требуется ключ к контексту и новое значение в качестве аргументов.

Давайте воспользуемся функцией onMount, чтобы установить значение по умолчанию для контекста:

onMount(() => {
  setContext(userContextKey, userContextInitialValue);
});

И измените функцию submit, чтобы установить контекст пользователя:

const submit = ({ email, password }) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      setContext(userContextKey, {
        name: "Foo",
        lastName: "Bar",
        email: "[email protected]"
      });
      resolve();
    }, 1000);
  });

Вот и все. Успешная отправка изменит контекст пользователя на фальшивый пользовательский объект, к которому может получить доступ средство получения контекста getContext:

<script>
import { getContext } from 'svelte';
import { key as userContextKey } from "./userContext";
const user = getContext(key);	
</script>

Резюме

Svelte - это мощный инструмент с высокой производительностью и гибким API. Помимо основ, описанных в этом посте, Svelte имеет следующие готовые функции:

  • Реактивные декларации и утверждения
  • Ожидайте блоки шаблона
  • Привязка размеров
  • Глобальный магазин, такой как Redux
  • Помощники анимации и переходов
  • Помощник по отладке

Подводя итог, Svelte - отличная библиотека, которая отвечает всем требованиям для создания SPA и многого другого. Он может составить конкуренцию крупнейшим игрокам рынка и даже побеждать. Однако прямо сейчас он мог бы использовать поддержку в сообществе front-end разработчиков.

Примечание: весь код в этой статье можно найти в teimurjan/svelte-login-form репозитории GitHub. Демо-версия формы входа доступна здесь.

Первоначально опубликовано на https://www.toptal.com.