Использование URQL в SvelteKit

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

Если вы не знаете, я писал об этом некоторое время назад (или, возможно, вы попали сюда из этого поста?), но если вы следите за SvelteKit уже некоторое время, то знаете, что ситуация немного изменилась.

Конечная точка GraphQL

В этот раз я буду использовать API GraphQL от Rick and Morty для получения данных, запрашивая конечную точку GraphQL с помощью URQL.

Если я перейду на конечную точку (ссылка в последнем абзаце), то смогу поиграть с запросами к данным в предоставленном проводнике GraphiQL.

Для этого я буду использовать два запроса: один для получения всех символов из API, а другой — для получения одного символа по ID.

Сначала я запрошу все символы, доступные в API, запрос будет следующим:

query AllCharacters {
  characters {
    results {
      name
      id
      image
    }
  }
}

Я помещу это в левую боковую панель в проводнике и нажму кнопку play, это вернет список всех символов в API.

Я могу добавить еще один запрос в проводник, нажав на кнопку с плюсом рядом с надписью GraphiQL в правом верхнем углу страницы. На этой вкладке я добавлю следующий запрос:

query GetCharacter($id: ID!) {
  character(id: $id) {
    name
    image
    status
    species
    location {
      name
      type
    }
    episode {
      name
    }
  }
}

Этот запрос принимает ID в качестве переменной (запрос GetCharacter($id: ID!)), я могу добавить его, щелкнув (и развернув) панель Variables в проводнике.

Если я открою несколько фигурных мальчиков (скобок) {} в панели Variables и нажму Ctrl+Space, я получу некоторую подсказку для доступных переменных, которые могут быть использованы, в данном случае это только переменная id, для которой я получу опцию. Если я выберу ее и добавлю значение, скажем, 1, а затем нажму кнопку воспроизведения, то получу следующий ответ:

Теперь у меня есть два запроса, необходимые для начала работы с данными в проекте SvelteKit.

Настройка проекта SvelteKit

Я использую pnpm для менеджера пакетов, но вы можете использовать npm или yarn. Я начну с создания нового проекта SvelteKit.

pnpm create svelte sveltekit-with-urql

Первая часть pnpm create svelte — это то, что запустит сценарий CLI для создания проекта, а часть sveltekit-with-urql — это имя папки, в которой я создам проект.

Я выберу следующие опции из CLI, если вы будете следовать за мной, выбирайте то, что вас устроит:

✔ Which Svelte app template? › Skeleton project
✔ Add type checking with TypeScript? › Yes, using JavaScript with JSDoc comments
✔ Add ESLint for code linting? … Yes
✔ Add Prettier for code formatting? › Yes
✔ Add Playwright for browser testing? › Yes

Затем я могу следовать подсказкам CLI, чтобы сменить каталог и установить зависимости для URQL и GraphQL:

# change into newly created project directory
cd sveltekit-with-urql
# install dependencies for URQL and GraphQL
pnpm i -D @urql/svelte graphql

Теперь я могу проверить, что все работает, запустив проект с помощью pnpm run dev.
Настройка клиента URQL

URQL используется на клиенте (в браузере), поэтому вызовы API могут быть сделаны в файлах Svelte +page.svelte и +layout.svelte.

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

touch src/routes/+layout.svelte

Затем я могу инициализировать клиента в файле макета, используя URQL createClient для создания клиентской переменной, которую я могу передать в функцию setContextClient.

<script>
  import { createClient, setContextClient } from '@urql/svelte'

  const client = createClient({
    url: `https://rickandmortyapi.com/graphql`,
  })

  setContextClient(client)
</script>

<main>
  <slot />
</main>

 

Все файлы макетов SvelteKit должны содержать элемент , именно здесь будет выводиться содержимое страниц.

Использование клиента URQL

Инициализировав клиента в файле макета, я могу теперь использовать его в странице Svelte.

В файле src/routes/+page.svelte я открою несколько тегов сценария и импортирую все необходимое из @urql/svelte.

Затем я создам переменную для хранения первого GraphQL-запроса, который я написал в редакторе GraphiQL на конечной точке API.

Теперь ответ от URQL-клиента будет добавлен в хранилище Svelte charactersQueryStore. Это означает, что если я захочу получить доступ к данным из этого хранилища, мне нужно будет использовать синтаксис $ для подписки на изменения хранилища.

Вы заметите, что getContextClient здесь передается в queryStore после первоначального создания клиента (setContextClient) на странице +layout.svelte.

Сейчас я просто выгружу содержимое charactersQueryStore на страницу в теге pre, чтобы убедиться в его работоспособности:

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>

{JSON.stringify($charactersQueryStore, null, 2)}

 

После этого я могу начать использовать $charactersQueryStore для добавления разметки на страницу.

Я использую блок if для условного отображения либо параграфа Loading, либо параграфа Error в зависимости от состояния магазина.

Затем использую блок each для перебора символов в хранилище и их отображения.

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>

<h1>The World of Rick and Morty</h1>

<div>
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <img src={character?.image} alt={character?.name} />
        <h2>{character?.name}</h2>
      </section>
    {/each}
  {/if}
</div>

 

Расскажу, что здесь происходит:

  • Я использую синтаксис $, чтобы подписаться на изменения в магазине.
  • Я использую блок if, чтобы проверить, загружается ли хранилище или ошибка, а затем отображаю сообщение пользователю.
  • Я использую блок each для циклического просмотра данных в хранилище и отображения их пользователю.

Выражения в Svelte, такие как оператор if, можно понимать так: # — это начало, это продолжение, а / — это конец.

Динамическая маршрутизация страниц с помощью SvelteKit

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

Итак, в тегах <section> я оберну изображение и имя персонажа тегом <a> и добавлю атрибут href для указания на еще не созданную папку для персонажа, вот как теперь выглядит раздел:

<section>
  <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
    <img src={character?.image} alt={character?.name} />
    <h2>{character?.name}</h2>
  </a>
</section>
Вот как выглядит страница сейчас:

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>

<h1>The World of Rick and Morty</h1>

<div>
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
          <img src={character?.image} alt={character?.name} />
          <h2>{character?.name}</h2>
        </a>
      </section>
    {/each}
  {/if}
</div>

Атрибут data-sveltekit-prefetch используется для того, чтобы сообщить SvelteKit о необходимости запуска функции загрузки страницы до того, как пользователь нажмет на ссылку.

Символ и id будут формировать URL для страницы персонажа, причем id будет динамической частью URL.

Нажатие на одну из ссылок сейчас дает мне ошибку 404, это потому, что страница еще не существует. Я создам маршрут для персонажа и id для этого персонажа в качестве динамического параметра для пути:

# создаем папки для персонажа и [id].
mkdir -p src/routes/character/'[id]'
# создаем файлы svelte и js для папки [id]
touch src/routes/character/'[id]'/{+page.svelte,+page.js}

Щелкнув на первом персонаже (Rick Sanchez), я перехожу к URL http://localhost:5173/character/1. Последняя секция URL (1) здесь — это character.id и динамическая часть маршрутизации. Это то, что нужно передать в GraphQL-запрос GetCharacter.

Поэтому мне нужно каким-то образом получить этот id из URL и передать его в GraphQL-запрос GetCharacter.

Функция загрузки SvelteKit

Используя функцию SvelteKit load в файле +page.js, я могу получить параметры из URL.

Если я добавлю функцию load в файл +page.js, деструктурирую объект params из контекста и зарегистрирую params, я увижу, что id там есть:

/** @type {import('@sveltejs/kit').Load} */

export const load = async ({ params }) => {
  console.log('=====================')
  console.log(params)
  console.log('=====================')
  return {}
}

С помощью этого я могу вычленить id из объекта params и вернуть его для использования в файле +page.svelte:

/** @type {import('@sveltejs/kit').Load} */

export const load = async ({ params }) => {
  const { id } = params
  return { id }
}

Теперь в файле +page.svelte я могу использовать id для передачи в графический запрос GetCharacter GraphQL для использования с URQL.

<script>
  export let data
  let { id } = data

  import { getContextClient, gql, queryStore } from '@urql/svelte'
  const characterQueryStore = queryStore({
    client: getContextClient(),
    variables: { id },
    query: gql`
      query GetCharacter($id: ID!) {
        character(id: $id) {
          name
          image
          status
          species
          location {
            name
            type
          }
          episode {
            name
          }
        }
      }
    `,
  })
</script>

<pre>{JSON.stringify($characterQueryStore, null, 2)}</pre>

Данные export let — это то, что передается в качестве props из файла +page.js. Я деструктурирую id из объекта данных и передаю его в качестве переменной в функцию queryStore.

Наконец, я выгружаю содержимое $characterQueryStore в тег pre, чтобы еще раз проверить, что данные возвращаются.

Заключение

Я использовал URQL для создания клиента, который указывает на конечную точку Rick and Morty graphql. Создал запрос для передачи клиенту, чтобы получить данные из API и отобразить простой список персонажей из API. Затем добавили маршрутизацию на основе файлов для отображения информации об отдельных персонажах.

Вот и все!

Действительно базовая настройка, но ее достаточно, чтобы начать использовать URQL в SvelteKit.