Создание React-компонентов из разметки безголовой CMS

Markdown стал неотъемлемой частью моего рабочего процесса как разработчика и создателя контента. Он быстр в написании, гибок в своих возможностях и имеет легко запоминающийся синтаксис.

Используя React Markdown, мы создадим пользовательские компоненты React (в приложении Next.js), используя парсинг уценки, полученной из безголовой CMS. Таким образом, мы сможем легко использовать такие передовые функции, как Next Image в теле нашего Markdown. Таким образом, мы можем брать динамический контент и формировать его так, как нам нужно, улучшая производительность, доступность и общий пользовательский опыт.

В этой статье представлен шаблон портфолио разработчика Next.js, который я создал с помощью Cosmic. Вы можете проследить за этим, посетив страницу App Template Page и импортировав шаблон в свой собственный Cosmic Bucket (создайте учетную запись, если вы хотите следовать этому пути и еще не сделали этого). Или просмотрите исходный код, если вы предпочитаете работать над собственным приложением.

Краткий обзор React Markdown

React Markdown — это React Component для рендеринга уценки, который позволяет создавать и рендерить пользовательские компоненты вместо стандартных HTML-компонентов. По умолчанию он безопасен (нет dangerouslySetInnerHTML) и позволяет использовать широкий набор плагинов из remarkjs для улучшения уценки.

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

  1. Контент markdown разбирается и превращается в дерево синтаксиса markdown
  2. Синтаксическое дерево разметки преобразуется в синтаксическое дерево HTML с помощью remark
  3. Синтаксическое дерево HTML преобразуется через rehype и преобразуется в компоненты React.

Установка пакетов

Чтобы использовать react-markdown, нам нужен только сам пакет.

pnpm add react-markdown
## or
npm install react-markdown
## or
yarn add react-markdown
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы получить данные из Cosmic, мы можем установить модуль Cosmic NPM.

pnpm add cosmicjs
## or
npm install cosmicjs
## or
yarn add cosmicjs
Войти в полноэкранный режим Выйти из полноэкранного режима

Получение содержимого в формате Markdown из безголовой CMS

В этом примере мы получаем содержимое в формате уценки из объекта Cosmic, который станет текстом статьи. В каталоге pages нашего приложения Next.js убедитесь, что у вас есть файл [slug].jsx, созданный в папке с названием posts, articles или любым другим.

Мы можем импортировать пакет cosmicjs, установить переменные окружения, а затем написать две функции, которые будут получать данные из нашей приборной панели Cosmic.

const Cosmic = require('cosmicjs')
const api = Cosmic()

const bucket = api.bucket({
  slug: process.env.COSMIC_BUCKET_SLUG,
  read_key: process.env.COSMIC_READ_KEY,
})

export async function getAllPostsWithSlug() {
  const params = {
    query: { type: 'posts' },
    props: 'title,slug,metadata,created_at',
  }
  const data = await bucket.getObjects(params)
  return data.objects
}

export async function getPostAndMorePosts(slug, preview) {
  const singleObjectParams = {
    query: { slug: slug },
    ...(preview && { status: 'any' }),
    props: 'slug,title,metadata,created_at',
  }
  const moreObjectParams = {
    query: { type: 'posts' },
    ...(preview && { status: 'any' }),
    limit: 3,
    props: 'title,slug,metadata,created_at',
  }

  try {
    const data = await bucket.getObjects(singleObjectParams)
    const moreObjects = await bucket.getObjects(moreObjectParams)
    const morePosts = moreObjects.objects
      ?.filter(({ slug: object_slug }) => object_slug !== slug)
      .slice(0, 2)
    return {
      post: data?.objects[0],
      morePosts,
    }
  } catch (error) {
    if (is404(error)) return
    throw error
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

В нашем файле [slug].jsx мы можем вызвать getStaticProps() и getStaticPaths(), получить данные и передать их нашему компоненту Post.

// pages/posts/[slug].jsx

const Post = ({ post }) => {
  return (
    <>
      <article>
        <PostBody content={post.metadata.content} />
      </article>
    </>
  )
}
export default Post

export async function getStaticProps({ params, preview = null }) {
  const data = await getPostAndMorePosts(params.slug, preview)

  return {
    props: {
      preview,
      post: {
        ...data.post,
      },
      morePosts: data.morePosts || [],
    },
  }
}

export async function getStaticPaths() {
  const allPosts = (await getAllPostsWithSlug()) || []
  return {
    paths: allPosts.map(post => `/posts/${post.slug}`),
    fallback: true,
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, когда мы настроили саму страницу, давайте погрузимся в наш компонент PostBody, где мы будем использовать react-markdown для рендеринга нашего контента.

Реализация React Markdown

Внутри нашего PostBody мы можем импортировать и использовать компонент ReactMarkdown. Мы просто импортируем пакет и обернем наш контент в компонент ReactMarkdown.

import ReactMarkdown from 'react-markdown'

const PostBody = ({ content }) => {
  return (
    <div className="max-w-2xl mx-auto">
      <ReactMarkdown>
        {content}
      </ReactMarkdown>
    </div>
  )
}
export default PostBody
Вход в полноэкранный режим Выход из полноэкранного режима

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

Кроме того, мы можем создавать пользовательские компоненты. Для этого мы создадим объект components над нашим компонентом PostBody и передадим его компоненту ReactMarkdown.

При создании пользовательских компонентов ключом будет HTML-эквивалент для содержимого, которое мы пишем в markdown. Параметром будет то, что вы хотите отобразить как элемент HTML, и будет иметь доступ к реквизитам этого элемента. Например, элемент a даст нам доступ к href и children, а тег img — к src и alt.

JSX, который вы напишете внутри этих функций, вернет предоставленный элемент. Вы можете написать что-то вроде h1: h1 => {return (<h2>{h1.children}</h2>)} и вы будете выводить h2 элементы для каждого h1, написанного в markdown.

import ReactMarkdown from 'react-markdown'

const components = {
  a: a => {
    return (
      <a href={a.href} rel="noopener noreferrer" target="_blank">
        {a.children}
      </a>
    )
  },
}

const PostBody = ({ content }) => {
  return (
    <div className="max-w-2xl mx-auto">
      <ReactMarkdown
        components={components}
      >
        {content}
      </ReactMarkdown>
    </div>
  )
}
export default PostBody
Вход в полноэкранный режим Выход из полноэкранного режима

Моим любимым вариантом использования до сих пор было внедрение Next Image для оптимизации размеров изображений и улучшения времени загрузки страницы.

Без использования Next Image наличие большого количества изображений в нашей разметке потребует загрузки всех изображений при запросе страницы. Это медленно и негативно сказывается на удобстве пользователей и показателях маяка.

Когда я обновляю страницу, все изображения загружаются одновременно. Вот это да!

Давайте улучшим это! Импортируйте Image из next/image, затем создайте пользовательский компонент img внутри объекта components. Помните, что у нас есть доступ к некоторым стандартным реквизитам этих компонентов, поэтому мы можем просто взять src и alt из img. Мы передаем их в наш компонент Image, написанный на JSX, определяя заданные height и width, более низкое quality для размера файла, и layout responsive для обслуживания масштабированных изображений в соответствии с размером экрана, гарантируя, что маленькие устройства не получат неоправданно большие размеры файлов.

import ReactMarkdown from 'react-markdown'
import Image from 'next/image'

const components = {
  a: a => {
    return (
      <a href={a.href} rel="noopener noreferrer" target="_blank">
        {a.children}
      </a>
    )
  },
  img: img => {
    return (
      <Image
        src={img.src}
        alt={img.alt}
        width={400}
        height={300}
        quality={50}
        layout="responsive"
        objectFit="contain"
        objectPosition="center"
      />
    )
  },
}

const PostBody = ({ content }) => {
  return (
    <div className="max-w-2xl mx-auto">
      <ReactMarkdown
        components={components}
      >
        {content}
      </ReactMarkdown>
    </div>
  )
}
export default PostBody
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

Гибкость в создании пользовательских React-компонентов для разметки очень важна, и с react-markdown мы можем добиться этого всего за несколько строк кода. Увеличение производительности, повышение безопасности ссылок и общие расширенные возможности для наших данных уценки — вот чего мы здесь добиваемся. Помимо улучшений в нашем приложении, этот компонент очень прост в использовании и подключает нас к unifiedjs (что дает нам суперсилу!).

Создавайте крутые вещи вместе с нами! Вы можете посмотреть наше новое шоу Build Time, где мы обсуждаем такие темы, как headless CMS, Next.js, React и многое другое.

Оцените статью
devanswers.ru
Добавить комментарий