Сильный противник Next.js идет! 💿 Ремикс официально анонсирован с открытым исходным кодом

внешний интерфейс
Сильный противник Next.js идет! 💿 Ремикс официально анонсирован с открытым исходным кодом

Недавно Remix, полнофункциональный веб-фреймворк со встроенными функциями React Router V6 на основе TypeScript и React, созданный оригинальной командой React Router, был официально открыт. В настоящее время занимает топ-3 списка трендов Github, звезда Github 5K+ Star:

После того, как Remix стал открытым исходным кодом, можно сказать, что он всколыхнул тысячу волн в области полного стека фреймворка React, и его определенно можно считать сильным противником Next.js. Особенности Remix заключаются в следующем:

  • Сделайте ставку на скорость, затем на пользовательский опыт (UX), поддержку любых SSR/SSG и т. д.
  • Основанный на базовых веб-технологиях, таких как HTML/CSS, HTTP и Web Fecth API, в большинстве случаев он может работать без JavaScript, поэтому он может работать в любой среде, такой как веб-браузер, Cloudflare Workers, Serverless или Node.js и т. д. .
  • Последовательный опыт разработки между клиентом и сервером.Код клиента и код сервера записываются в один файл, что обеспечивает бесшовное взаимодействие данных.На основе TypeScript определения типов могут совместно использоваться клиентами и серверами.
  • Встроенные файлы маршрутизации, динамической маршрутизации, вложенной маршрутизации, маршрутизации ресурсов и т. д.
  • Уничтожьте любое состояние загрузки, такое как «Загрузка», «скелетный экран» и т. д. Все ресурсы на странице могут быть предварительно загружены (Prefetch), и страница может быть загружена практически сразу.
  • Попрощайтесь с предыдущим методом сбора данных водопада, сбор данных осуществляется параллельно на стороне сервера, и создается полный HTML-документ, аналогичный функции параллелизма в React.
  • Предоставляет все состояния, необходимые для разработки веб-страниц, из коробки; предоставляет все необходимые компоненты, включая<Links>,<Link>,<Meta>,<Form>,<Script/>, для обработки метаинформации, сценариев, CSS, маршрутизации и контента, связанного с формами.
  • Встроенная обработка ошибок для обработки непредвиденных ошибок<ErrorBoundary>и разработчик выдает обработку ошибок<CatchBoundary>

Так много функций? Бессознательно! Далее мы постараемся показать особенности этих Ремиксов один за другим 🚀.

🌈Постоянный опыт разработки

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

Посмотрите на кусок кода с официального сайта:

import type { Post } from "~/post";
import { Outlet, Link, useLoaderData, useTransition } from "remix";

let postsPath = path.join(__dirname, "..", "posts");

async function getPosts() {
  let dir = await fs.readdir(postsPath);
  return Promise.all(
    dir.map(async (filename) => {
      let file = await fs.readFile(path.join(postsPath, filename));
      let { attributes } = parseFrontMatter(file.toString());
      invariant(
        isValidPostAttributes(attributes),
        `${filename} has bad meta data!`
      );
      return {
        slug: filename.replace(/.md$/, ""),
        title: attributes.title,
      };
    })
  );
}

async function createPost(post: Post) {
  let md = `---\ntitle: ${post.title}\n---\n\n${post.markdown}`;
  await fs.writeFile(path.join(postsPath, post.slug + ".md"), md);
  return getPost(post.slug);
}

export async function loader({ request }) {
  return getProjects();
}

export async function action({ request }) {
  let form = await request.formData();
  const post = createPost({ title: form.get("title") });
  return redirect(`/posts/${post.id}`);
}

export default function Projects() {
  let posts = useLoaderData<Post[]>();
  let { state } = useTransition();
  let busy = state === "submitting";

  return (
    <div>
      {posts.map((post) => (
        <Link to={post.slug}>{post.title}</Link>
      ))}

      <Form method="post">
        <input name="title" />
        <button type="submit" disabled={busy}>
          {busy ? "Creating..." : "Create New Post"}
        </button>
      </Form>
      
      <Outlet />
    </div>
  );
}

Вышеупомянутый файл маршрутизации, если онsrc/routes/posts/index.tsxфайл, затем запускаем сервер и проходимlocalhost:3000/postsВы можете получить доступ к этому файлу, который является файлом или маршрутом, а функция Projects, экспортируемая по умолчанию, является функциональным компонентом React.Шаблон возврата этой функции представляет собой HTML-документ, который обращается к этому маршруту.

  • Каждая функция маршрутизации, такая какProjectsВы можете определить функцию загрузчика, аналогичную функции на стороне сервера, которая обрабатывает запросы GET, вы можете получить информацию о маршрутизации и предоставить данные для начального рендеринга на стороне сервера.В этой функции вы можете получить файловую систему, запросить базу данных, делать другие сетевые запросы, а затем возвращать данные. В нашем компоненте Projects он доступен через RemixuseLoaderDataХук получает данные, полученные функцией загрузчика.
  • Каждая функция маршрутизации также может определять функцию действия, которая используется для реальных операций, аналогичных функциям для обработки запросов, отличных от GET, таких как операции POST/PUT/PATCH/DELETE. файловая система и т. д., и в то же время возвращаемый результат может быть фактическими данными или перенаправлением на новую страницу, напримерredirect("/admin"). Когда функция действия возвращает данные или информацию об ошибке, мы можем предоставитьuseActionDataХук получает возвращенное сообщение об ошибке и отображает внешний интерфейс.

Стоит отметить, что функция действия находится в<Form method="post">В форме он автоматически вызывается после того, как пользователь нажимает кнопку отправки.Remix вызывается через Fetch API, а затем интерфейс непрерывно опрашивает, чтобы получить результат вызова, и автоматически обрабатывает конкуренцию, когда пользователь нажимает несколько раз.

Веб-панель вашего браузера будет отображаться следующим образом: Remix автоматически инициирует запрос POST, а затем обрабатывает перенаправление на/post/${post.id}, при загрузке соответствующего/postsа также/posts/${post.id}Соответствующее содержимое страницы маршрутизации.

через ремиксuseTransitionХук, мы можем получить статус отправки формы, когда запрос не вернул результат, мы можем передать этот статусstateОпределяет, отображать ли состояние загрузки, чтобы информировать пользователя о ходе выполнения текущего запроса.

В то же время тип сообщения находится вuseLoaderData<Post[]>()а такжеcreatePost(post: Post) можно поделиться.

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

На изображении выше видно, что даже если JavaScript отключен, наш сайт все еще работает нормально.

🤯 Мощная вложенная система маршрутизации

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

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

Возьмем, к примеру, официальный сайт:

Соответствующие отношения между вышеуказанными страницами следующие:

  • Весь модуль страницы/, что соответствует/salesЭто весь небесно-голубой контент справа,/sales/invoicesсоответствует желтой части,/sales/invoices/102000соответствует красной части в правом нижнем углу

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

Давайте посмотрим на практический пример:

// src/root.tsx
import {
  Outlet,
  
export default function App() {
  return (
    <Document>
      <Layout>
        <Outlet />
      </Layout>
    </Document>
  );
}

function Document() {}
function Layout() {}
// src/routes/admin.tsx
import { Outlet, Link, useLoaderData } from "remix";
import { getPosts } from "~/post";
import type { Post } from "~/post";
import adminStyles from "~/styles/admin.css";

export let links = () => {
  return [{ rel: "stylesheet", href: adminStyles }];
};

export let loader = () => {
  return getPosts();
};

export default function Admin() {
  let posts = useLoaderData<Post[]>();
  return (
    <div className="admin">
      <nav>
        <h1>Admin</h1>
        <ul>
          {posts.map((post) => (
            <li key={post.slug}>
              <Link to={post.slug}>{post.title}</Link>
            </li>
          ))}
        </ul>
      </nav>
      <main>
        <Outlet />
      </main>
    </div>
  );
}
// src/routes/admin/index.tsx
import { Link } from "remix";

export default function AdminIndex() {
  return (
    <p>
      <Link to="new">Create a New Post</Link>
    </p>
  );
}
// src/routes/admin/new.tsx
import { useTransition, useActionData, redirect, Form } from "remix";
import type { ActionFunction } from "remix";
import { createPost } from "~/post";
import invariant from "tiny-invariant";

export let action: ActionFunction = async ({ request }) => {
  await new Promise((res) => setTimeout(res, 1000));
  let formData = await request.formData();

  let title = formData.get("title");
  let slug = formData.get("slug");
  let markdown = formData.get("markdown");

  let errors = {};
  if (!title) errors.title = true;
  if (!slug) errors.slug = true;
  if (!markdown) errors.markdown = true;

  if (Object.keys(errors).length) {
    return errors;
  }

  await createPost({ title, slug, markdown });

  return redirect("/admin");
};

export default function NewPost() {
  let errors = useActionData();
  let transition = useTransition();

  return (
    <Form method="post">
      <p>
        <label>
          Post Title: {errors?.title && <em>Title is required</em>}
          <input type="text" name="title" />
        </label>
      </p>
      <p>
        <label>
          Post Slug: {errors?.slug && <em>Slug is required</em>}{" "}
          <input type="text" name="slug" />
        </label>
      </p>
      <p>
        <label htmlFor="markdown">Markdown:</label>{" "}
        {errors?.markdown && <em>Markdown is required</em>}
        <br />
        <textarea rows={20} name="markdown" />
      </p>
      <p>
        <button type="submit">
          {transition.submission ? "Create..." : "Create Post"}
        </button>
      </p>
    </Form>
  );
}

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

Весь веб-сайт приложения размещен на<Document>вложенный<Layout>состоит из<Outlet>Это место заполнения маршрута, которое обозначено зеленой частью на рисунке выше. когда мы посещаемlocalhost:3000/, который заполненsrc/routes/index.tsxСодержимое рендеринга, соответствующее файлу маршрутизации, и когда мы посещаемlocalhost:3000/admin, соответствующийsrc/routes/admin.tsxСодержимое рендеринга, соответствующее файлу маршрутизации.

пока мы вsrc/routes/admin.tsxпродолжал предоставлять<Outlet>Маршруты, очевидно, являются компонентами, то есть по мере того, как мы продолжаем добавлять иерархические (вложенные) маршруты, такие как посещениеhttp://localhost:3000/admin/newтогда это<Outlet>будет оказыватьsrc/routes/admin/new.tsxВ соответствии с содержимым рендеринга файла маршрутизации при доступеhttp://localhost:3000/adminчас,<Outlet>часть будет рендеритьsrc/routes/admin/index.tsxСодержимое рендеринга соответствующего файла маршрутизации показано на следующем рисунке:

И эта вложенная маршрутизация происходит автоматически, когда вы создаетеsrc/routes/admin.tsxПосле этого создается папка с таким же именем, а под папкой создаются другие файлы, тогда имена файлов этих файлов будут прописаны как следующий уровень имен вложенных маршрутов:

  • localhost:3000/adminЗарегистрируйтесь одновременноsrc/routes/admin.tsxа такжеsrc/routes/admin/index.tsx

Через такой файл идет маршрутизация, файл под одноименной папкой идет путем вложенной маршрутизации, а потом через родительскую страницу через<Outlet>Способ отображения содержимого подстраницы в соответствии с подмаршрутом значительно повышает гибкость, и каждый подмаршрут соответствует независимому файлу маршрутизации с независимой логикой обработки данных, логикой рендеринга контента и логикой обработки ошибок.

Очевидным преимуществом описанной выше вложенной маршрутизации является то, что если в какой-то части будет сообщено об ошибке, она будет упомянута позже в сочетании сErrorBoundaryа такжеCatchBoundaryЭта часть может отображать неправильную страницу, и пользователь по-прежнему может работать с другими частями без необходимости обновлять всю страницу для перезагрузки использования, что значительно повышает отказоустойчивость веб-сайта.

👋🏻 До свидания, загружается статус

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

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

А Remix обеспечивает вложенную маршрутизацию при доступе к маршрутизации.localhost:3000/admin/newПри этом страницы, соответствующие этим трем маршрутам, загружаются независимо и параллельно, и данные получаются независимо и параллельно. Наконец, клиенту отправляется полный HTML-документ. Процесс выглядит следующим образом:

Видно, что хотя получение контента на первом экране может быть медленнее, состояние загрузки нам больше не нужно До свидания, картинка хризантемы 👋🏻, до свидания, экран-скелет 👋🏻.

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

😇 Идеальная обработка ошибок

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

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

Именно потому, что ошибки возникают часто, а обрабатывать ошибки крайне сложно, в том числе различные ошибки на клиенте и сервере, в том числе ожидаемые и непредвиденные ошибки и т.д., поэтому в Remix встроен идеальный механизм обработки ошибок, обеспечивающий аналогичный ErrorBoundary of React.idea.

В Remix каждая функция маршрутизации соответствует функции ErrorBoundary:

export default function RouteFunction() {}

export function ErrorBoundary({ error }) {
  console.error(error);
  return (
    <div>
      <h2>Oh snap!</h2>
      <p>
        There was a problem loading this invoice
      </p>
    </div>
  );
}

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

При этом каждой функции маршрутизации соответствует функция CatchBoundary:

import { useCatch } from "remix";

export function CatchBoundary() {
  let caught = useCatch();

  return (
    <div>
      <h1>Caught</h1>
      <p>Status: {caught.status}</p>
      <pre>
        <code>{JSON.stringify(caught.data, null, 2)}</code>
      </pre>
    </div>
  );
}

Функция CatchBoundary соответствует ожидаемой ошибке, то есть ошибке Response, которую вы вручную выбрасываете в загрузчике, функции действия, на стороне клиента или сервера.Путь этих ошибок предсказуем.В CatchBoundary черезuseCatchЛовушка выдает эти ошибки ответа, а затем отображает пользовательский интерфейс для сообщения об ошибке.

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

🌟 Основан на базовой веб-технологии

Remix фокусируется на решении проблем с базовыми веб-технологиями, HTML/CSS + HTTP и т. д., предоставляя при этом все состояния и все основные компоненты, необходимые для полнофункциональной среды веб-разработки.

Соответствующие состояния включают:

// 加载数据的状态
useLoaderData()

// 更新数据的状态
useActionData()

// 提交表单等相关状态
useFormAction()
useSubmit()

// 统一的加载状态
useTransition()

// 错误抓取状态等
useCatch()

и основные компоненты веб-сайта:

  • <Meta>Используется для динамической установки метаинформации веб-страницы, что удобно для SEO.

  • <Script>Используется, чтобы сообщить Remix, нужно ли импортировать соответствующий JS при загрузке веб-страницы, потому что в большинстве случаев страницы, написанные Remix, могут работать без JS.

  • <Form>на замену родному<form>Удобно выполнять операции с формами на клиенте и сервере, брать на себя соответствующие функции при отправке, использовать Fetch API для инициирования запросов и т. д., а также обрабатывать состояние конкуренции при многократной повторной отправке и т. д.

При этом в файле, где находится функция маршрутизации, можно объявитьlink,meta,links,headersи другие функции для объявления соответствующих функций:

  • linksПеременная функция: указывает ресурсы, которые необходимо загрузить этой странице, такие как CSS, изображения и т. д.
import type { LinksFunction } from "remix";
import stylesHref from "../styles/something.css";

export let links: LinksFunction = () => {
  return [
    // add a favicon
    {
      rel: "icon",
      href: "/favicon.png",
      type: "image/png"
    },

    // add an external stylesheet
    {
      rel: "stylesheet",
      href: "https://example.com/some/styles.css",
      crossOrigin: "true"
    },

    // add a local stylesheet, remix will fingerprint the file name for
    // production caching
    { rel: "stylesheet", href: stylesHref },

    // prefetch an image into the browser cache that the user is likely to see
    // as they interact with this page, perhaps they click a button to reveal in
    // a summary/details element
    {
      rel: "prefetch",
      as: "image",
      href: "/img/bunny.jpg"
    },

    // only prefetch it if they're on a bigger screen
    {
      rel: "prefetch",
      as: "image",
      href: "/img/bunny.jpg",
      media: "(min-width: 1000px)"
    }
  ];
};
  • linksФункция: объявить страницу, которую необходимо предварительно загрузить, и загрузить ресурс до того, как пользователь нажмет
export function links() {
  return [{ page: "/posts/public" }];
}
  • metaфункция: с<``Meta``>Компоненты похожи, объявляя метаинформацию, необходимую странице.
import type { MetaFunction } from "remix";

export let meta: MetaFunction = () => {
  return {
    title: "Josie's Shake Shack", // <title>Josie's Shake Shack</title>
    description: "Delicious shakes", // <meta name="description" content="Delicious shakes">
    "og:image": "https://josiesshakeshack.com/logo.jpg" // <meta property="og:image" content="https://josiesshakeshack.com/logo.jpg">
  };
};
  • headersФункция: определить информацию заголовка запроса, которую эта страница принесет при отправке HTTP-запроса.
export function headers({ loaderHeaders, parentHeaders }) {
  return {
    "X-Stretchy-Pants": "its for fun",
    "Cache-Control": "max-age=300, s-maxage=3600"
  };
}

Как видите, Remix предоставляет почти все, что вам нужно для всего жизненного цикла веб-разработки с полным стеком, со встроенными передовыми практиками, которые гарантируют, что вы сможете добиться высокой производительности и опыта с минимальными усилиями!

Конечно, эта статья не содержит всех возможностей Remix.Студенты, которые все еще интересуются Remix, могут посетить официальный сайт (remix.run/) Узнайте больше~ На официальном веб-сайте представлены очень подробные практические руководства, которые помогут вам разрабатывать практические приложения с помощью Remix.

Узнав об особенностях Remix, что вы думаете о Remix? Как вы думаете, сможет ли он превзойти Next.js 🐴? Заинтересованные студенты могут присоединиться к группе программистов по обмену автобусами для совместного обсуждения ~

❤️/Спасибо за поддержку/

Выше приведено все содержание этого обмена, я надеюсь, что это поможет вам ^_^

Если вам понравилось, не забудьте поделиться, поставить лайк и добавить в избранное~

Добро пожаловать в публичный аккаунтавтобус программиста, три брата из Byte, Shopee и Zhaoyin, делятся опытом программирования, техническими галантерейными товарами и планированием карьеры, чтобы помочь вам избежать окольных путей, чтобы попасть на большую фабрику.