Архитектура фронтенд-приложений — миф или реальность

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

3 июля прошел первый ивент Uinno meetups. Мы решили локально организовать небольшой митапчик для разработчиков с опытом. В Запорожье в целом с IT-комьюнити проблемы. Точнее, было несколько попыток его создать, но они так ни к чему и не привели. Мы заметили (еще бы), что в сети слишком много ивентов на тему «войтивайти», но, к сожалению, практически нет мероприятий для ребят «повидавших» за свою карьеру.

Мы подобрали не простые, но очень важные, на наш взгляд, темы. Две темы по hard skills, одна тема по soft skills и анонсировали событие. Рассчитывали, что к нам максимум придет человек 30, были бы рады, если бы пришло 10–15 человек и мы провели ламповую встречу. Каково же было наше удивление, когда количество регистраций уже на второй день перевалило за 30. В итоге примерно на пятый день анонса мероприятия нам пришлось закрыть форму регистрации, так как количество мест в нашем офисе было ограниченным.

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

Дисклеймер: хочу сразу предупредить, что если вы надеетесь, что, прочитав данную статью, познаете сакральные тайны бытия, то спешу вас огорчить. Это не так. Я далеко не Дядюшка Боб или Мартин Фаулер и не претендую на истину в первой инстанции. Всего лишь хочу поделиться своими мыслями. И надеюсь, что прочитанное заставит задуматься тех, кто ранее не задумывался о проблемах архитектурных решений, и придаст уверенности тем, кого этот вопрос уже гложет.

Так есть ли проблема

Базируясь на своем опыте, а также на исследовании «околодевелоперских» медиаресурсов, я могу предположить следующее. У фронтенд-инженеров (я все-таки хочу думать, что мы таки инженеры, а не фреймворк-программисты) всё-таки наблюдается перекос в сторону технологических решений, а не архитектурных.

Что я имею в виду? Например, то, что в большинстве обсуждений в интернете, будь то статьи на «Хабре», твиттер или «Медиум», в основном затрагиваются новые библиотеки, фреймворки, локальные решения. И очень редко когда можно увидеть обсуждение архитектурных проблем. Как мне кажется, данные обсуждения являются прямым отображением того кода, который пишут люди. Я много раз встречал проекты с действительно крутыми и навороченными локальными решениями, но расширять или модифицировать эти проекты приходилось не благодаря, а вопреки.

Корни проблемы

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

Но времена меняются, и после того, как Google выпустил Gmail, который, по сути, является прообразом веб-приложений, у всех резко зачесалось и бизнес начал диктовать условия, мол, тоже так хочу. И понеслось — Angular, React, Vue (это я упоминаю только самые популярные в среде разработчиков решения). Сложность приложений возрастала с каждым годом, а кому приходилось всё это воплощать в жизнь? Правильно — вчерашним верстакам-фронтендерам или, прости господи, вебмастерам, у которых при слове «архитектура» максимум возникали ассоциации с древнегреческими храмами.

Будущие фронтенд-инженеры шли по тому пути, который уважаемые господа-бэкендеры протопали еще, наверное, в 80–90 годах прошлого века. Граблей было собрано немеряно, и ещё сколько их предстоит собрать.

Вторая, но не менее важная часть — это то, что в настоящее время фронтенд стал «воротами» в мир IT. Спрос на JS-разработчиков просто устрашающий, и это сильно помогает всем желающим прям с ноги открыть дверь в манящий мир смузи, макбуков и гироскутеров.

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

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

Что я подразумеваю под архитектурой

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

Архитектура — это то, как части/компоненты/модули вашей программы взаимодействуют между собой. Например, у вас самое настоящее одностраничное веб-приложение. Что я подразумеваю под «настоящим»? То, что оно всё записано в одном файле на 25–35 тысяч строк с множеством глобальных переменных, которые мутируются из тысячи мест. Выходит, это тоже архитектура, можем даже придумать ей громкое название — «супермонолит».

Простой пример

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

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

Для описания примеров буду использовать React, так как я думаю, он будет достаточно понятен всем. Но подобные подходы с легкостью переносятся на Vue, особенно с использованием composition API.

const TodoListComponent = ({ todoListId }) => {
  const [todoList, setTodoList] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);

  const todoCreateHandler = useCallback((todo) => {
    axios
      .post("/todo", {
        // post body
      })
      .then()
      .catch();
  });

  // handle API call to update todo by id
  const todoItemCheckHandler = useCallback(/** ... */);

  useEffect(() => {
    if (!isModal) {
      axios.get(`/todo-list/${todoListId}`).then((res) => {
        const data = lodash.get(res, "data");
        setTodoList(data);
      });
    }
  }, [todoListId]);

  return (
    <div>
      <h2>{todoList.title}</h2>
      <ul>
        {todoList.todos.map(() => {
          /**
           * Here we implement the TodoItemComponent functionality
           * and handle todoItem check/uncheck
           */
        })}
        <TodoItemCreateComponent onCreate={todoCreateHandler} />
      </ul>
    </div>
  );
};

Как вы видите, тут всё в лучших традициях «обучающих статей» с medium/dev.to/etc. Мы получаем данные, используя axios прям компоненте, потом при помощи lodash достаем данные из тела ответа от API, сохраняем это дело в локальный стейт и мапим в JSX. И это всё будет работать.

А если это работает, то в чём же проблема, спросите вы? По моему мнению, проблема в том, что мы гвоздями приколотили все возможные зависимости к компоненту. Компонент знает о том, как, с помощью чего и откуда мы получаем данные, производим с ним манипуляции и потом полученную с бэкенда структуру напрямую мапим в наш компонент. А ещё нам нужно тут же обрабатывать состояние loading/fetched/error.

Давайте представим базовый случай. Бэкендеры изменили структуру ответа, и нам нужно менять компонент. Если они изменили названия полей, то нам придётся менять JSX/Template. Если мы решим использовать redux/vuex/state-manager, то нам опять же придётся переписывать компонент. Давайте пошагово рассмотрим то, что мы сможем сделать.

Делаем лучше

  • Создадим APIClient, который будет использовать axios как транспорт.
  • Так же выносим утилиты в отдельный модуль:
const TodoListComponent = ({todoListId}) => {
  
	const [todoList, setTodoList] = useState([]);
	const todoCreateHandler = useCallback((todo) => {
	  apiClient.createTodo(todoListId, todo).then().catch()
	})
	
	// handle API call to update todo by id 
	const todoItemCheckHandler = useCallback(/** ... */);
	
	useEffect(() => {
	  // Here we "hide" the axios and lodash into the separate modules 
	  apiClient.getTodoList(todoListId)
	    .then(setTodoList);
	}, [todoListId])
	
	return (
		<div>
	    <h2>{todoList.title}</h2>
	    <ul>
	      {todoList.todos.map(() =>{
          /**
          * Here we implement the TodoItemComponent functionality
          * and handle todoItem check/uncheck
          */
	      })}
	      <TodoItemCreateComponent onCreate={todoCreateHandler}/>
	    </ul>
	  </div>
	)
}

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

И еще...

  • Применим что-то похожее на паттерн Repository, чтобы инкапсулировать получение данных о TodoList. Назовём его к примеру TodoListHttpRepository.
  • Если мы используем React или Vue3, то с помощью хуков/composition API сделаем переиспользуемый модуль, который будет с помощью TodoListHttpRepository получать данные, создавать из них объект/экземпляр класса, который будет следовать контракту.
class TodoList {
  constructor(todoList) {
    this.title = todoList.listTitle;
    this.todos = todoList.listTodos;
  }
}

class TodoListHttpRepository {
  // Here we will pass the axios based api client
  constructor(transport) {
    this.transport = transport;
  }

  async getById(id) {
    const res = this.transport.get(`/todo-list/${id}`);

    return new TodoList(res);
  }

  async create(todoList) {
    /** */
  }

  async updateById(id, todoList) {
    /** */
  }

  async delete(id) {
    /** */
  }
}
const todoListRepo = new TodoListRepository(apiClient);

const useTodoList = (todoListId) => {
  const [todoList, setTodoList] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    todoListRepo
      .getById(todoListId)
      .then(setTodoList)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [todoListId]);

  return { data: todoList, error, loading };
};
const TodoListComponent = ({ todoListId }) => {
  const { data, loading, error } = useTodoList(todoListId);
  const todoItemCheckHandler = useTodoUpdate(todoListId);
  const todoCreateHandler = useTodoCreate(todoListId);

  return (
    <div>
      <h2>{data.title}</h2>
      <ul>
        {data.todos.map(() => {
          /**
           * Here we implement the TodoItemComponent functionality
           * and handle todoItem check/uncheck
           */
        })}
        <TodoItemCreateComponent onCreate={todoCreateHandler} />
      </ul>
    </div>
  );
};

Теперь наш компонент не зависит напрямую от того, какой ответ нам пришлет API. Кто-то может увидеть в этом последнюю букву аббревиатуры SOLID — Devendency Inversion (инверсия зависимостей). Также наш компонент не знает, откуда он получает данные (ну практически не знает), так как на самом деле очень большая ценность состоит в том, чтобы компоненту было абсолютно всё равно, откуда к нему эти данные приходят — localstorage/http/state-manager/etc. Вы можете заметить, что useTodoList подозрительно напоминает заготовку react-query (swr, etc...), но это тема уже для отдельной статьи.

...и ещё...

Теперь физически (на уровне модулей) разделим представление (JSX/Template/etc) от логики.

const useTodoListComponentState = ({ todoListId }) => {
  const { data, loading, error } = useTodoList(todoListId);
  const updateHandler = useTodoUpdate(todoListId);
  const createHandler = useTodoCreate(todoListId);

  return { data, loading, error, createHandler, updateHandler };
};

const TodoListComponent = ({ todoListId }) => {
  const { data, loading, error, createHandler, updateHandler } =
    useTodoListComponentState(todoListId);

  return (
    <div>
      <h2>{data.title}</h2>
      <ul>
        {data.todos.map(() => {
          /**
           * Here we implement the TodoItemComponent functionality
           * and handle todoItem check/uncheck
           */
        })}
        <TodoItemCreateComponent onCreate={todoCreateHandler} />
      </ul>
    </div>
  );
};

Вы спросите, зачем это — ответ будет чуть позже.

Внезапное новое требование

Вы, наверное, думаете: «Ага, и вместо одного файла/модуля, где я компактненько всё описал, теперь нужно вот это всё писать и на каждый чих создавать новый модуль и обмазываться абстракциями».

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

А теперь самое интересное. Приходит к вам заказчик и говорит: «Приложуха огонь, выглядит суперски, но вчера меня посетила гениальная идея, пока я принимал ванну с шампанским...»

Для того чтобы сделать UX ещё круче, нужно позволить пользователю создавать TodoList, который сразу содержит задачи, и — барабанная дробь — это всё нужно делать в модалке (мы ведь обожаем модалки, не правда ли?)! Юзер нажимает кнопку, появляется модалка, в ней он вводит название списка и заполняет задачи, он может сколько угодно добавлять, менять уже добавленные или удалять задачи, а когда нажимает кнопку Create, всё это добро сохраняется в базе данных. У вас же уже всё работает и так, теперь это всё в модалочку запихнуть дело двух минут.

Как вы думаете, сколько нам бы всего пришлось переписать, если бы провели рефакторинг? Вероятно, наш компонент бы выглядел, как несколько conditional statements и некоей логикой выбора. Вам понадобится локальное состояние или некий state-manager для того, чтобы хранить промежуточное состояние данных, пока пользователь не нажал кнопку.

И если это всё совмещать в одном компоненте, то получится уже далеко не так красиво и просто, как было в самом начале. А любое изменение в существующем коде увеличивает шанс того, что что-то таки отпадет. Тем более тестами наш TodoList не покрыт, но тесты — это совсем отдельная тема для будущих митапов.

const TodoListComponent = ({ todoListId, isModal }) => {
  const [todoList, setTodoList] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);

  const todoCreateHandler = useCallback((todo) => {
    axios
      .post("/todo", {
        // post body
      })
      .then()
      .catch();
  });

  // handle API call to update todo by id
  const todoItemCheckHandler = useCallback(/** ... */);

  const todoListRedux = useSelector((state) => selectTodoListById(todoListId));
  const dispatch = useDispatch();

  useEffect(() => {
    if (!isModal) {
      axios.get(`/todo-list/${todoListId}`).then((res) => {
        const data = lodash.get(res, "data");
        setTodoList(data);
      });
    }
  }, [todoListId]);

  const data = isModal ? todoListRedux : todoList;
  const createHandler = isModal
    ? todoCreateHandler
    : (todo) => dispatch(createTodoAction(todo));
  // Here could be the other conditions

  return (
    <div>
      <h2>{data.title}</h2>
      <ul>
        {data.todos.map(() => {
          /**
           * Here we implement the TodoItemComponent functionality
           * and handle todoItem check/uncheck
           */
        })}
        <TodoItemCreateComponent onCreate={createHandler} />
      </ul>
    </div>
  );
};

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

Мы создаём отдельный модуль, который будет обрабатывать состояние нашего TodoList, но который находится в модалке. Теперь стоит лишь выбрать нужный модуль в зависимости от того, в каком месте находится наш компонент. Старый код не менялся, новый добавился легко и просто, так как нужно лишь соблюдать контракт между слоями.

const hooks = {
  "todo-list-page": useTodoListComponentState,
};

const useHook = ({ context, ...rest }) => {
  return hooks[context](rest);
};

const TodoListComponent = ({ todoListId, context }) => {
  const { data, loading, error, createHandler, updateHandler } = useHook({
    todoListId,
    context,
  });

  return (
    <div>
      <h2>{data.title}</h2>
      <ul>
        {data.todos.map(() => {
          /**
           * Here we implement the TodoItemComponent functionality
           * and handle todoItem check/uncheck
           */
        })}
        <TodoItemCreateComponent onCreate={todoCreateHandler} />
      </ul>
    </div>
  );
};
const hooks = {
  "todo-list-page": useTodoListComponentState,
  "create-todo-list-modal": useTodoListCreateModalState,
};

const useHook = ({ context, ...rest }) => {
  return hooks[context](rest);
};

const TodoListComponent = ({ todoListId, context }) => {
  const { data, loading, error, createHandler, updateHandler } = useHook({
    todoListId,
    context,
  });

  return (
    <div>
      <h2>{data.title}</h2>
      <ul>
        {data.todos.map(() => {
          /**
           * Here we implement the TodoItemComponent functionality
           * and handle todoItem check/uncheck
           */
        })}
        <TodoItemCreateComponent onCreate={todoCreateHandler} />
      </ul>
    </div>
  );
};

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

Выводы

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

А это значит меньше время time-to-market, что, в свою очередь, уменьшает затраты заказчика и делает его более счастливым. Но это тоже тема для отдельного обсуждения.

Кто-то может сказать, что для того, чтобы уйти от контекстов, можно использовать HOC (aka Container Components), и будет прав. Кто-то может предложить кучу других решений и тоже будет прав. Я ещё раз хочу обратить внимание на то, что показанное мною в статье не нужно пытаться скопировать в проекты, это всё лишь для того, чтобы подумать хорошенько.

Всем добра!

👍НравитсяПонравилось27
В избранноеВ избранном15
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Интересное и очень необычное архитектурное решение получилось в конце.
Никогда бы не подумал делать hasmap с хуками. Мне это кажется чем-то слишком экзотическим :)

Суть проблемы с модалкой ведь заключается в том, что у нас на 2 разные логики и только 1 презентация. Я бы просто вынес презентацию в отдельный компонент и рендерил ее в 2-х компонентах один для модалки другой для стандартного отображения.
В итоге получились бы такие компоненты:
TodoList — перезентационный компонент который принимает data, loading, error, onCreate, onUpdate
TodoListContainer — первоначальная логика + рендерит TodoList
TodoListModal — логика для модалки с redux + рендерит TodoList
Ну и с помощью хуков можно переиспользовать общую логику между TodoListContainer и TodoListModal, точно так же как это бы делалось для вашего варианта с большими хуками.

Вообщем, это то же самое только в место отделения логики в хук и подмены ее с помощью пропса который берет нужный хук из hasmap. Мы отделяем презентацию и используем ее в нужных нам контейнерах. Это тот самый подход о котором писал когда-то давно Ден Абрамов и который был многими понят не правильно. Здесь преимущество в том, что TodoList совсем ничего не знает о том, откуда идут данные и как они обробатываются, так как все идет через props и нет никаких хуков внутри.

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

Спасибо за развёрнутый и интересный комментарий.

Абсолютно с вами согласен, что можно это решить и с помощью контейнерных компонентов, и в целом суть будет та же. Я сознательно отказался тут от контейнерных компонентов в пользу хуков так как у меня в данном примере немножко другая «философия» (если конечно можно так выразиться) — компонент сам решает что ему делать в зависимости от контекста. Я не берусь утверждать что моя идея лучше или хуже, но она работает.

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

Тут всё зависит от того какие цели мы преследуем. Если просто написать проект хорошо — это одна цель. Моя же статья/выступление «родилось» в процессе того как я пытался написать некую общую абстрактную архитектуру поверх React вдохновившись практиками которые подчерпнул работая на крупных backend проектах (я имею в виду DDD, CQRS). И вот в моём случае достаточно большое количество абстракций оправдано целью описать методологию в которой можно было бы к примеру запросто менять state-manager или использовать несколько (каждый под свои нужды) без необходимости переписывать пол проекта. Я конечно понимаю как это звучит — очередной ноунейм пытается отлить серебряную пулю. Но я просто слегка устал от обилия велосипедов и хочется сделать что-то хорошее хотя бы в рамках моей компании. Получится или нет — покажет время.

Отличный план! Буду ждать следующую статью, думаю это будет очень полезно

Каждый раз заново писать хард-код. Не обращать внимания, что написанный хард-код дублирует уже написанный хард-код на 95% Колоссально удобно. Максимально быстро. Если написанный хард-код не прошел тесты после итеративной test driven разработки его (step by step) весь модуль выбросить. Ибо это произошло по фундаментальной причине — заказчик нечаянно заказал изменение основ.
Или можно пытаться предусмотреть вообще все и сразу. Успехов. Сначала так и будет, а потом начнутся костыли. Или изменения основ. Или и то и другое.
Нет, правда. В моей шутке есть какой-то смысл.
Отчего хард-код вам так плох?

І тут ми перескакуємо з одної крайності в іншу.

— Скажи шось по-аутсорсерськи
— Заказчик заказал

что написанный хард-код дублирует уже написанный хард-код

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

А если я предложу страшное? Автоматически отслеживать изменения в модулях сверяя hash сумму?

Будь-який пробіл або крапка з комою різниці зробить цю ідею марною.

Что нам те пробелы, если можно код упаковать особым образом, вот как табнайн упаковывает там у себя. Там наверное значение any redundant semi-colons анулируется. Такой особенный хеш также не часто считать можно, только после успешного прохождения теста, например. И одобренного пулл реквест. Вообще с этими подстановочными редакторами хардкодить это как песню петь. Буквально цельными сигнатурами жонглировать.

Ви мусите контролювати процедуру упаковки, щоб гарантувати однаковість результату.

Я не говорю что мы должны предусмотреть всё и сразу. Я пытаюсь сказать что при хорошей архитектуре нам как раз намного легче вносить изменения и дополнения.

Кроме времени на разработку архитектуры, тратится потом время еще и на изучение ее.
Не легче. Все так же тяжело. Это спроба превысить скорость света. Я не говорю что надо прямо все хардкодить, это тоже крайность, но упорядочить_все т.е. достичь 100% порядка в системе — так не может быть. Хаос тогда просто вылезет в другом месте — по закону прям сообщающихся сосудов.
И еще такой момент: предположим архитектура ок, и тут надо внести его — вожделенное дополнение. Не похоже что оно является некоторым спрятанным хардкодом? Засушенным листом на ветке красивой архитектуры — конкретная реализация всегда будет учитывать не общие моменты и связана с ними будет довольно тесно.
С другой стороны, если взять фото кошки и фото собаки и одни rgb совместить с другими то получиться котопес. А потом можно еще и поместить это все в картинку слона. И получится слонокотопес. Так вот это уже сложное создание природы будет.
И получается надо использовать три картинки если речь о задаче -животные- а не одну общую.
Имхо сложность понимания кода возрастает при его все большей универсализации, а не наоборот. Так устроены людские мозги, имхо. Чем больше вариантов предложишь, тем меньше понимания обнаружишь в итоге.
И вот поэтому я за хардкод.

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

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

Документирование решений, по моему мнению, это одна из неотъемлемых частей построения налаженных процессов в команде(ах)

Я где-то читал, что документировать никто не хочет. А значит никто не будет знать идеальную архитектуру никогда.

Как понять что фронтендер джун — он сразу хочет втянуть в проект редакс/графкл и/или верит в нужность его на проекте (в 99.9% оно вам создаёт в разы больше проблем чем решает)

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

Они свято верят, что её изучили. И свято верят, что вот это — конец эволюции и вершина прогресса. А всё что до того было — отстой, и надо «всё переписать». Думаю, если мне сделать рентген черепа, там будет чёткий отпечаток ладони от многочисленных «дежавю»

Вы так говорите как будто не были джуном :)

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

Другими словами, претензий джуны не имели, а управление персоналом производилось словами не длиннее 7 буков, дальше только многоэтажность.

А вот цена ошибок была куда жёстче. Потому как тестирование — это было что-то типа мифа.

«Колись порядок був, нах*й, не те, шо січас!»

const hooks = {
  "todo-list-page": useTodoListComponentState,
  "create-todo-list-modal": useTodoListCreateModalState,
};

От скажіть мені, який сенс використовувати тут const? Я провів багато співбесід, відповіді мене відверто шокували. Більшість розробників, які претендували на сіньйорську посаду, давали майже однакові відповіді: «так всі роблять» та «так рекомендують робити». Коли я просив пояснити, скорочення від якого слова є це ключове слово та як воно перекладається, всі оком не клипнули надаючи відповідь. Але потім починалася зрадонька. Ніхто не зміг пояснити, навіщо він використав в коді саме це ключове слово замість інших, хоча й розуміли особливості роботи JS з константами.

А тепер про архітектуру. Архітектура на фронті — це сукупність підходів, комплекс з декількох технологій, а не однієї: HTML, DOM, CSS, JS, IO, GL, etc. Та розглядати архітектуру треба тільки в комплексі, а не окремо. Якщо зациклюватися виключно на JS, то можна проґавити той момент, коли система стане розбалансованою та однобокою: речі, які елементарно робляться в CSS, будуть робитися через сраку в коді; натягнуть бізнес-логіки, яку не треба взагалі тягнути на сторону клієнта; замість перекладання роботи на сторону двигуна браузера, будуть винаходитися чергові велосипеди, тощо.

Дуже несподіване та трошки незрозуміле запитання про const :) Чи могли би ви трохи пояснити чому константа вас тут турбує?

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

Чи могли би ви трохи пояснити чому константа вас тут турбує?

Краще поясніть, який сенс ви вкладали в наведений приклад.

Серьезно? Есть простое правило, надо выбирать самый ограничивающий подход (при возможности легкого изменёния на более гибкий если надо) который удовлетворяет требованиям. Я был бы рад если тайпскрипт мог компилировать as const в object freeze.

Чем больше ограничений по умолчанию, тем лучше защищён од дурака. А дураков на проекте обычно овер дохера

Ви не від дурнів код захищайте, а прокачуйте їх до більш високого рівня. Константа не від чого не захищає, а тільки створює ряд додаткових проблем в підтримці коду. Кращий захист від джуна — не давати йому писати низькорівневий код.

Дуже цікаво, які проблеми створює використання const? При читанні коду це дуже допомагає відразу зрозуміти, що hooks ніде не буде змінюватися. A let говорить про те, що змінна може змінюватися. Якщо використовувати це правило, код стає зрозумілішим

Коли тобі треба буде поміняти поведінку змінної. Очевидно ж. Тоді тобі прийдеться робити рефакторінг, наслідки якого важко передбачити, одразу виникне бажання написати тест, провести рев’ю, та пішло поїхало. Мінімальні зміни в решті решт перетворяться на великі витрати. Гребцям-аутсорсянам на це чхати, бо замовник оплачує всі їх помилки, а в продуктовій компанії така поведінка шкідлива.

Дивно, що у вас такі великі проблеми через заміну const / let. На моїй практиці заміна let на const та навпаки носило швидше косметичний характер. Це дає змогу легше читати код, тому що коли ми бачимо const, то розуміємо, що ця змінна ніколи не буде змінюватися, навіть якщо компілятор перетворить це на var. Це швидше для зручності розробників, тому що прискорює читання і розуміння коду. І на мою думку, любий джуніор повинен розуміти, що const не спасає від мутування об’єкту.

«Не буде змінюватися» — отут проблема. В будь-який момент часу ви можете захотіти змінити поведінку змінної. Наприклад, під час рефакторінгу або внесенні змін в бізнес-лозіці компоненту. Ось тоді й виникає додана вартість.

Ви так говорите, ніби якщо змінити поведінку let змінної, то і тести не прийдеться писати, рев’ю проводити не треба і т. д.. Я в першу чергу вважаю це питанням стилістики, а ви, наскільки я розумію, технічним. Чесно, не знаю, який код ви пишите, якщо заміна const на let викликає у вас такі великі проблеми.

Если так рассуждать, то можно вообще весь код писать в одном файле, а то вдруг я вынесу функцию в отдельный файл, а потом, когда она вдруг перестанет быть нужна, то файл придётся удалить. Это же невероятный ущерб для проекта.

От скажіть мені, який сенс використовувати тут const? Я провів багато співбесід, відповіді мене відверто шокували. Більшість розробників, які претендували на сіньйорську посаду, давали майже однакові відповіді: «так всі роблять» та «так рекомендують робити». Коли я просив пояснити, скорочення від якого слова є це ключове слово та як воно перекладається, всі оком не клипнули надаючи відповідь. Але потім починалася зрадонька. Ніхто не зміг пояснити, навіщо він використав в коді саме це ключове слово замість інших, хоча й розуміли особливості роботи JS з константами.

Зустрічне питання: що дасть якшо замінити його на let? код стане кращим, швидшим, чи ще якімось? та ніфіга.

Тут проблема в том, что const как правило используется для каких-нибудь конфигурационных данных, например const G = «9.8»;
Таким образом мы видим и понимаем, что это часть конфигурации и мы можем ее менять на входе и это окажет некий эффект на всю аппликуху.
В остальных случаях прежпочтительнее let.

Если смотреть с этой стороны, то я тут использую const так как не собираюсь изменять значение переменной. Я прекрасно понимаю что в этом случае я могу изменять значение полей объекта или добавлять новые поля. Конкретно в этом случае это наверное больше «семантика» нежели нечто обусловленное производительностью или чем-то подобным

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

то я тут использую const так как не собираюсь изменять значение переменной

Бугагашеньки три рази. В JS const імутабельною зробить лише посилання на об’єкт. А поля об’єкту можна міняти скільки завгодно.

Бугагашеньки три рази. В JS const імутабельною зробить лише посилання на об’єкт. А поля об’єкту можна міняти скільки завгодно.

Так он же написал это вторым предложением)
Складывается ощущения что ты просто хвастаешься что знаешь такие базовые вещи, не видя ничего вокруг))

Складывается ощущения что ты просто хвастаешься

Складається враження, що в людей мозок перестав критично ставитися до інформації. Стереотипне мислення в повний зріст.

Прочитай ещё раз то что ты комментируешь и пойми что предложение абсолютно правильное.

А так, для общего развития, посмотри как к примеру работает final в Java, да и в других языках константы. Константа != иммутабелтный объект

Я спеціально виділив речення, щоб прокоментувати саме його. Та дивакувато, що люди не бачять ніякої проблеми між значенням слова константа (величина, що не змінює свого значення протягом певного процесу) та можливістю змінювати величину (мутабельність об’єкту).

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

Тобто мутабельна константа для вас норма?

В случае объектов мы говорим о константной ссылке. Ссылка константа. А то на что она ссылается это другой разговор.

Тут одна зрадонька затісалася. Скаляри в JS в деяких моментах поводяться як об’єкти, а не саме скаляри. Виявляється, що const fck = " wtf? "; fck.trim(); не працює (хоча операція проводиться над об’єктом, а не скаляром), а от const sht = [ "bull" ]; sht.push("sht"); буде спокійно працювати. Якщо ви рознесете декларацію від виклика функції в різні місця, то з самого коду ви ніколи не зрозумієте, який буде результат цього виклику. :D JavaScript is awesome!

String в js это value type. Value type все иммутабельны (кстати в джава стринг тоже иммутабельно). Вы процитируете свой опыт другого языка на джс, а потом удивляетесь что это не так.

Якщо б це був чистий value type, то у них не було б цілої низки методів, які дозволяють такі операції "a".concat("b"), а були б класичні функції по типу String.concat("a","b"). Саме дуалізм скалярних типів і є «проблемою» поточної реалізації констант в ECMA 6.

Так методы есть и на number и на boolean, и им точно так же можно расширить прототипы, string ничем от них принципиально не отличается. String.concat vs String.prototype.concat это никакая не проблема а дело стилистики. Есть конечно люди считающее что «методов» в джс не должно быть, но const там со всем не причём, и это другая тема.

Скаляри в JS не є чистими скалярами. Деякі операції, такі як property access, перетворюють скаляр на відповідний об’єкт. Якщо цей скаляр був задекларований як константа, то перетворення його на об’єкт зробило б імутабельним посилання на об’єкт, а значить решта операцій могла модифікувати значення, наприклад trim. Але ніт, хоч об’єкт ми й створили, але він залишається імутабельним ;) Синтаксис в методах у скаляра та об’єктів буде однаковим, а результати неочікуванно різними. Про що я й писав вище.

в JS? ну хз, посмотреть даже топовые либы там просто везде будут фигарить const по умолчанию. Для реальных констант будет юзаться uppercase. Где нужно будет менять саму переменную, там уже let будет.

Навіть саму топову лібу може написати нетоповий програміст, який не сильно розібрався, в чому різниця між let, var, const.

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

У вас вельми цікаве розуміння топовості...

Якщо ти не плануєш переприсвоювати значення тоді краще використовувати const для додаткового захисту. Звісно, це не такий великий ризик, але який сенс писати let, якщо повторне присвоєння не планується?

І саме так: const по дефолту, let тільки там, де треба. Це вже давно стало правилом хорошого тону в JS.

Це вже давно стало правилом хорошого тону в JS.

Мільйони мух не можуть помилятися! Ні!
Чим вам var не догодив?

Чим вам var не догодив?

Ні, я на такий примітивний тролінг не поведуся.

Так чим? Ви використовуєте константи, значить ви, як досвідчені розробники, мусите чітко усвідомлювати, яку поведінку та патерни ви закладаєте в код. Якщо ви робите це тільки тому, що так кажуть в інтернетах, то ви просто бездумно розповсюджуєте шкідливі паттерни.

Чувак, я не вірю шо ти оце всерйоз. Але нехай.

1. Ти «use strict» пишеш усвідомлено чи тому шо так кажуть в інтернетах? Бо апелювання до var змушує задуматись.
2. let безпечніше за var, а const безпечніше за let. Тому що вони дають обмеження, а з обмеженнями важче вистрілити собі в ногу. Краще хай лінтер чи навіть рантайм вивалиться з явною помилкою, ніж шукати шо ти там ненароком переприсвоїв з верхнього скоупа. Захочеш переприсвоїти — поміняєш на let, це діло кількох секунд.
3. «Кажуть в інтернетах» це був би хороший аргумент, якби ти працював лише сам. А так культура коду і слідування правилам доброго тону робить код більш підтримуваним після тебе, наступний розробник швидше розбереться.

1. Я усвідомленно його не пишу
2. Безпека, про яку ви тут розповідаєте, це ваш персональний біль на проектах. Не треба переносити його на всіх інших. Кожне використання var/let/const мусить нести певну логіку, і це не тупе «не дати довбням щось зламати». Ви можете закладати більше сенсу в код, тому що у вас є як мінімум три варіанти, які саме властивості надавати змінним. А ви все до тупняку приводите, до самого простого варіанту.
3. Більш підтримуваний код, це не той, в якому всі змінні задекларовані через const.

1. Ти або троль або рідкий гість в JS.
2. Захист від дурня це value для бізнесу, чиї проблеми я вирішую. Тобі таке важливо чіплятися до назв операторів? Окей: «справжні» константи пишуться аперкейсом, а все решта через const це одноразові змінні, яких в робочому коді 99%.
3. Це той, який важче зламати.

1. Ти або троль або рідкий гість в JS.

Або я його вчив ще тоді, коли сучасні сіньйори ходили в садочок ;)

Захист від дурня це value для бізнесу

Це витратна частина для бізнесу. А витрати за замовчуванням не можуть бути value. А от швидкі внесення змін в ваш код, оце як раз справжня цінність. Зроблена вона дурнем чи сіньйором — не важливо. Не треба розповідати про технічний борг тільки, він тут не принциповий.

3. Це той, який важче зламати.

Важче за все зламати відсутність коду. При чому сам код може існувати, але бути прихованим від «ламальників».

Вивчив раз в нульових і вистачило? Мені ні.

«Потрібно бігти з усіх ніг, щоб тільки залишатися на місці, а щоб кудись потрапити, треба бігти хоча б удвічі швидше!»

Було б що вчити... Ви просто не помічаєте, як по колу ходите. Всі сучасні технології не нові та базуються на тих принципах, які ще в 70-ті роки були закладені розумними дідусями. Приходять молоді, гарячі, з порогу заявляють, що «Міша, все фігня, давай все по-новому», беруться писати чергову маячню, потім носяться з нею, як дурні з макитрями. Через років 5 їх попускає, вони розуміють, що срібної кулі не існує, та розчаровані йдуть писати щось «принципово нове». Але все їх нове — гарно забуте старе. Наша пісня гарна й нова, починаймо її знову. ;)

Або я його вчив ще тоді, коли сучасні сіньйори ходили в садочок ;)

По судженнях і помітно що тільки й тоді вчилися. Взагалі, після заяв типу «а я 30 років вже працюю, тому знаю як правильно», можна взагалі не читати, в 99% випадків там буде bullshit

Булшіт буде скоріш за все в тих, хто страждає на Даннінга-Крюгера. А я таким не страждаю.

Насчёт правил хорошего тона.
Почитай дебилушку Даню Абрамова, он на полном серьезе топит за let, но чего собственно ожидать от человека причастного к редакс, единственное проблема в том что его словам верят куча народа как истинне в последней инстанции

Let не такий вже й поганий. Нагадування зайвий раз програмісту, що він щось переускладнює.

А чего плохого в let, расскажи человеку который юзает JS только по назначению (в смысле, заскриптовать простые действия, а не строить монстров).

Олексію, ви навіщо одразу отак з козирів заходите? :D

Нічого поганого там нема, це просто var для якого діє temporary dead zone щоб викидати ексепшн при спробі прочитати значення до того, як воно було задефайнене. Ну і скоупи звужені до блоків коду, що реально зручніше при написанні циклів чи свічів.

Мінусів там ніяких нема, таку поведінку можна було зробити у var, питання тільки в зворотній сумісності.

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

Не понимаю, в чём ограничение джунов, или правильнее сказать, косяк языка (раз его код понимается неоднозначно)?

Насколько я понимаю, let позволяет ограничить зону видимости, и по глупости не наклепать глобальных переменных и не отравить большой объект. И как ты сам сказал, «с const так не получится», именно это ты и сделаешь: создашь объект в зоне видимости кода, а зона видимости — не всегда очевидна.

Для let зона видимости как раз привычна: это блок кода, где эта переменная объявлена, это дефолтное правило для ДРУГИХ языков программирования. Почему и спрашиваю, что плохого в let? Чем уже зона видимости, тем короче и проще можно именовать переменные, не заморачиваясь что они с чем-то пересекутся ИЛИ будут не так поняты.

Для поддержки как раз let идеальны — ты глазами видишь, где оно себя проявляет, и для чего ЗАДУМЫВАЛОСЬ. То есть при чтении кода, если тебе в блок лезть не надо, ты просто эти мегатонны деталей пропускаешь. А вот с var так не получится.

Так что грубо говоря, я бы наоборот, приучал использовать let всегда, без модификатора — при объявлении полей объекта, а var — явно символизирует о попытке динамически добавить что-то в объект, чего там не было запланировано изначально. Соответственно от лишних var нужно избавляться, а при рефакторинге менять на let c целью упростить читаемость кода.

const же в принципе применяется достаточно редко, ибо СМЫСЛА как такового в программировании в нём нет, от слова «совсем»: если ты сам пишешь код, то ты сам же и знаешь что ты делаешь. Ты же в квартире мусорное ведро скотчем не приклеиваешь, хотя у него постоянное место. Так и с «константами» — в них просто нет нужды.

Константа — это иллюзия. Она нужна только математикам — именно в математике переменные не являются переменными, они не могут менять значения после присвоения. Лично я считаю математику во многом устаревшей наукой, в частности по способности ОБЪЯСНЯТЬ закономерности. Программный код читается на 4 порядка легче математических выкладок. Во многом «благодаря» их системе именований сущностей и операций.

If a variable is never reassigned, using the const declaration is better.

Класичний опис константи. Скалярної константи. Та рекомендація від сторонньої бібліотеки, яка слідкує за «якістю» коду...
В одноразовому коді, який пишуть сучасні веб-розробники на сучасних веб-фреймворках, дійсно константами буде 99.9% коду, бо він одноразовий. Але це не доводить того, що крізь треба використовувати саме const. Це скоріше рахітектура, а не архітектура.

Скажу коротше: констант не існує. А «одноразовим» є все, пинання лише у критерії, яким ти класифікуєш використання як «один раз», не поділяючи на кілька окремих.

Інструкції ISA є константами ;) Та все, що вшито програматором...

У флеш-пам′ять з ресурсом від 10 000 до 100 000 записів. Звідки для використання всі «константи» читаються в оперативну пам′ять. Тими самими модулями із флеш—пам’яті, які виконуються послідовно, тобто кожен наступний може зробити будь що із даними попередніх.

ПЗУ може бути не тільки на NVRAM... ;) В ОЗУ читати не завжди треба дані з ПЗУ, бо це така сама пам’ять.

В ОЗУ читати не завжди треба дані з ПЗУ, бо це така сама пам’ять

Гарний міф. Він реальний тільки для вузького кола мікроконтролерів, які можуть дозволити собі пинати органи розмноження, доки флеш-пам′ять народить дані.

То вже оптимізації... ;) Бо в порівнянні з L0 кешом ОЗУ ще той слоупок...

Все дуже просто: люди взагалі роблять більшість речей так, а не інакше, бо всі так роблять. Це настільки абсолютне явище, що навіть не піддається класифікації як добро чи зло — окрім випадків професійних знань, де люди мають обирати конкретні речі свідомо, заважаючи на ризик.

Можу сказати, нащо const у більшості випадків в Java (для прикладу): щоб передати цю «константу» як параметр анонімному класу, і зробити це під килимом, навіть не проводячи її через синтакс мови програмування. Тобто, середовище виконання таким чином покладається на механізм захисту «від дурня», що не дозволить маніпулювати змінною у зоні видимості, де вона була створена, дозволяючи передати контроль над нею в зону видимості іншого коду — зокрема того, що может виконуватися в окремому потоці.

Фактично це щось на кшталт передачі параметра не по ссилці (як зазвичай), а по значенню. А взагалі нема жодних гарантій, що ти передаси таку «константу» лише одному класу. Тому вся суть const зводиться до аналогу анотації компілятору «я знаю, що роблю, зроби за мене обгортку цього в кошерний вигляд».

Тобто, насправді констант не існує. Все це лише засіб застереження, щоб не вистрілити собі в ногу. Для читабельності коду краще зайвого маразму не писати, не зміниться аж нічого. Але в деяких (бюрократичних) випадках це вимагається. Згодом, з розвитком мов, від цією бюрократії відходять, дозволяючи синтаксичний цукор. Але ж «методології» еволюціонують значно повільніше, аніж кодова база.

Тому, чесно кажучи, сучасні сеньйори це лише треновані джуни. Нічого нового вони не вміють, лише добре повторювати старі фокуси, та іноді вихвалятися яке вивчили «новий» фокус, який є тим самим, просто інакше назвали.

А чого чекати від аутсорсу? Що до нас прийде справжня інженерна робота? Тримайти кишеню ширше, та весло сильніше.

Дякую що розповіли про те як воно в Java працює, цього я не знав. Але в нашому випадку js. Не могли б ви тоді сказати чим використання var у цьому випадку будо б краще?

С приводу аутсорсу я згоден лише частково, бо є насправді аутсорс здорової людини. А те що ви про весла — то аутсорс курця ;)

Це аутсорс країн третього світу. І що далі, то буде тільки гірше.

Топик архитектуры
Ожидания от комментов: прочитать, что вкладывают в понятие архитектуры в разрезе фронта, примеры решений, которые можно считать архитектурными
Реальность: холивар const vs let

В будь-якому холіварі можна знайти істину. А щоб спілкуватися про архітектури на фронті, треба бути як мінімум архітектором. Сучасний веб не сильно стимулює цей напрямок розвитку спеціалістів.

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

Наконец-то кто-то заговорил об архитектуре фронта.

Обычно комплексные веб-приложения, где действительно присутствует архитектура, используют react/vue/etc уже на самом высоком уровне.

До него идет API Level (особенно актуально для serverless), где может происходят API коллы, авторизации и тд, с подмешиванием данных из локальных хранилищ/сервисворкеров/чегоугодно;

Когда данные получены и собраны воедино, они попадают Business Layer, где мы приводим данные в порядок, вероятно создаем модели и определяем логику поведения данных, и после этого уже подготовленные данные появляются в Presentation Layer. Модели попадают в vuex/redux или другие сторы и используются для отрисовки интерфейса.

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

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

Disclaimer: названия слоев условные.

Золотые слова ;)

100%!

якщо не можна викинути реакт і переписнуть апку на інше за місяць, то у вас ковбаса з шарів. а так про яку архітектуру може бути мова, якщо вона вся розкидана по реакту (ще і залежить від реакта :) )

Тут конечно уже множество копий сломано в спорах о том создавать ли новый обработчик при обновлении функции или заворачивать его в useCallback.

Но есть точно один случай когда useCallback становится явно оправданным — в случае если вы будете вешать его на компонент а не на «тэг». В этом случае useCallback будет возвращать одну и ту же ссылку на функцию и в следствии этого компонент на котором висит этот обработчик не будет обновляться потому что обновилась ссылка на функцию. Конечно же для того чтобы он действительно не ререндерился нужно ещё несколько условий выполнить, но это уже другая история.

Но в примеры useCallback просочился видимо по привычке :)

у вас самое настоящее одностраничное веб-приложение === оно всё записано в одном файле на 25–35 тысяч строк с множеством глобальных переменных, которые мутируются из тысячи мест

Это вы так пошутили или действительно именно это подразумеваете под одностраничным веб-приложением?

Конечно пошутил :) Просто как раз перед написанием статьи обсуждали с коллегами самые страшные случаи. И мне рассказали про Angular.js приложение то ли на 25 то ли на 35 тысяч строк которое было реально в одном файле.

Тогда ок. А то прям споткнулась об это предложение и решила выяснить, стоит ли читать дальше :)

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

Я далёк от того чтобы сказать что у меня есть конкретное решение и оно прям «топчик». Конкретно в статье я говорю про подходы и про то что нужно менять мышление. Возможно через год моё видение проблемы поменяется, а возможно наоборот кристаллизируется во что-то типа методологии на подобии feature-sliced

Да нет к тебе претензий. Это вообще о том, что все эти организации, архитектуры, поддерживаемость, расширяемость и пр. заканчивается очередным «переписыванием на реакт».

Есть вполне себе живые примеры. Например вот github.com/...​ontend-clean-architecture

Да и вообще рекомендую почитать Александра Беспоясова если интересуетесь архитектурой да и в целом написанием хорошего кода. Вот его личная страница. bespoyasov.ru

Хорошая статья, да все верно описали, сам сталкивался. Но есть еще плохое решение, когда берут Redux и хранят весь стейт там, в дополнение к этому ReduxSaga для взаимодействия с API, в результате получается просто немыслимое количество одинакового кода, вместо того что бы использовать react-query или graphql (для более сложных решений).

Мне кажется что со использование Redux/Vuex/etc для хранение всего и вся со временем останется в прошлом. Мы же (фронтендеры я имею в виду) нащупывали пути. Я сейчас сам стараюсь использовать react-query/vue-query там где это уместно. Ну а конкретно состояние приложения можно рулить уже чем душа пожелает.

Будет время посмотрите пожалуйста на мое решение, может что подскажете: github.com/...​ter/src/pages/ProductPage
Интересно ваше мнение.

Ответил вам в личное сообщение.

Я сейчас сам стараюсь использовать react-query/vue-query там где это уместно.

ничего страшного, через год будет новый тренд и вы перепишете аппку уже на него

Ну я уж точно не гонюсь за трендами. Я достаточно долго присматривался к SWR подходу. Потом поэкспериментировал и понял что действительно разделение Знания и Состояния отлично работает.

Так что тут точно дело не в хайпе и трендах.

Вы в примерах дергаете axios прямо из компоненты (приходит секьюрити архитект и ставит задачу вылогинивать пользователя при любой 401й ошибке, и вы идете и переписываете каждый компонент), рядом лежат хуки из Redux, а слепое прокидывание isModal и context заканчивается тем что все ваше дерево компонентов начинает насквозь зависеть от них (приходит ПО и говорит, что в модалке форма должна иметь +3 поля, и 2 существующих работать должны иначе)
Я не знаю, были ли эти ошибки сделаны из-за того, чтобы сделать статью чуть более доступной, но если вы так пишете в продакшен коде, то вам точно рано писать статьи с подобным названием.

Ну как раз те компоненты где я дергаю Аксиос напрямую и являются утрированными примерами «плохого» кода. Я даже упоминаю что специально написал плохо.

Понятное дело что все примеры, даже те что я называю «улучшенными» можно уличить в несостоятельности если {подставьте какое нибудь условие}. Но универсальные примеры привести сложно, да и это не было моей целью.

Так есть ли проблема

Да, в статье описана ложная проблема

Корни проблемы

Кто-то решил, что познал фронт с высока, пытаясь рассказать про архитектуру.

Что я имею в виду? Например, то, что в большинстве обсуждений в интернете, будь то статьи на «Хабре», твиттер или «Медиум», в основном затрагиваются новые библиотеки, фреймворки, локальные решения. И очень редко когда можно увидеть обсуждение архитектурных проблем.

Любая «библиотека» по факту и есть архитектурное решение. Взять тот же Redux, который вносит экшены, редюсеры, мутации, сайд эфекты, иммьютбл, стейт менеджмент.
А это только одна из десятка, а то и сотни в типичном проекте для:

клепали формочки
Будущие фронтенд-инженеры шли по тому пути, который уважаемые господа-бэкендеры протопали еще, наверное, в 80–90 годах

Фу, серверным пришлось трогать клиент?

SOLID
Теперь физически (на уровне модулей) разделим представление (JSX/Template/etc) от логики.

Эх, как говорится не опять, а снова!.

Да, в статье описана ложная проблема

Как я уже говорил в статье — эту проблему я вижу на основании собственного опыта. Буду рад если на самом деле весь остальной фронтенд, который я не видел, написан качественно и круто.

Кто-то решил, что познал фронт с высока, пытаясь рассказать про архитектуру.

Не смотря на негативную коннотацию, скажу что это не только моё мнение, и я не в коем случае не причисляю себя к гуру или «просвещённым» фронтендерам :)

Любая «библиотека» по факту и есть архитектурное решение. Взять тот же Redux, который вносит экшены, редюсеры, мутации, сайд эфекты, иммьютбл, стейт менеджмент.
А это только одна из десятка, а то и сотни в типичном проекте для

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

Эх, как говорится не опять, а снова!.

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

Подписаться на комментарии