Существует токсичный стереотип, что FullStack разработчики не могут ни в фронт, ни в бек. Как минимум, так как объем работ большой, часто, программирование фронта на React превращается в формошлепство с сомнительным качеством кода. Код копируется без создания компонентов, нет глобального состояния приложения.

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

Лучшее состояние UI для FullStack - отсутствие состояния

Так как состояние фронта дублирует структуры данных, объявленные на стороне backend, причиной появления плохого кода скорее всего будет желание сэкономить на их фронтовых описаниях. Как вариант, рискну предложить возродить использование stateless подхода для простых форм React приложений.

Функция createProvider

Позволяет вынести какой-либо объект в React контекст. Вместе с Mobx может быть использована для создания общего controller-а для вложенных view-шек. В методах анонимного класса getPosts и getPostById используется функция fetchApi- алиас для fetch(...).then((data) => data.json())

import { createProvider, fetchApi } from 'react-declarative';

const [BlogApiProvider, useBlogApi] = createProvider(
  () => new class {

    getPosts = () =>
      fetchApi("https://jsonplaceholder.typicode.com/posts")

    getPostById = (id) =>
      fetchApi(`https://jsonplaceholder.typicode.com/posts/${id}`)

  }
);

export { BlogApiProvider, useBlogApi };

Компонент FetchView

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

import { FetchView } from 'react-declarative'

const PostList = () => {

  const { getPosts } = useBlogApi();

  const state = [
    getPosts,
  ];

  return (
    <FetchView state={state} animation="fadeIn">
      {(posts) => (
        <div>
          {posts.map((post, idx) => (
            <p key={idx}>
              <b>{post.title}</b>
              {post.body}
            </p>
          ))}
        </div>
      )}
    </FetchView>
  );
};

Компонент Async

В отличие от FetchView умеет показывать спиннер (см CircularProgress), сигнализирующий пользователю о активной загрузке данных. Если id изменится, то запрос выполнится повторно задействуя пропсу payload

import { Async } from 'react-declarative'

import { CircularProgress } from '@mui/material'

const PostItem = ({
  id,
}) => {

  const { getPostById } = useBlogApi();

  return (
    <Async payload={id} Loader={CircularProgress}>
      {async (id) => {
        const { title, body } = await getPostById(id);
        return (
          <div>
            <p>{title}</p>
            <p>{body}</p>
          </div>
        );
      }}
    </Async>
  );
};

Компонент Switch

Роутер, с использованием которого легко настроить кеширование и инвалидацию кеша. Для примера, реализуем ролевую модель.

import { createProvider, fetchApi, singleshot } from 'react-declarative';

import sleep from '../utils/sleep';

const roleApiManager = new class {

  getRoles = singleshot(async () => {
    await sleep(5_000);
    return [
      'admin',
      ...
    ]
  });

  hasRole = async (role) => {
    const roles = await this.getRoles();
 		return roles.includes(role);
  };

  unload = () => {
    this.getRoles.clear();
  };

};

const [RoleApiProvider, useRoleApi] = createProvider(
	() => roleApiManager
);

export { roleApiManager, RoleApiProvider, useRoleApi };

На странице'/sample-page' список ролей может быть потребован в нескольких участках кода, но запрос должен выполниться только один раз. После того, как пользователь покинет страницу, кешированный список ролей должен сброситься.

import { Switch } from 'react-declarative';

...

const routes = [
  {
    path: '/sample-page',
    unload: roleApiManager.unload,
  },
];

...

const App = () => (
  <Switch history={history} items={routes} />
);

Компонент If

Этот компонент осуществит ветвление представления исходя из истинности промиса в condition. Он так же умеет перезапрашивать данные при изменении payload

import { If } from 'react-declarative'

const ProfilePage = () => {
  const { hasRole } = useRoleApi();
  return (
    <If condition={() => hasRole("admin")}>
      <button>Кнопка только для админа</button>
    </If>
  );
};

Краткость сестра таланта

Если данные компоненты вызовут интерес среди сообщества, я напишу цикл статей, посвященной упрощению рутины на стороне фронтенда.

Комментарии (0)