Pull to refresh
502.63
Тензор
Разработчик системы СБИС

Любимая задачка на знание React

Level of difficultyEasy
Reading time3 min
Views20K

Всем привет! Меня зовут Олег и я fullstack-программист в компании Тензор. Опыт в разработке, без малого, 20 лет (как-то раз батя спаял на кухне ZX Spectrum и все заверте..., сам не понял как так вышло). В данный момент являюсь тимлидом собственной команды разработчиков, которая периодически нуждается в пополнении толковыми программистами.

Как и многие руководители, я активно принимаю участие в подборе сотрудников для себя и помогаю на собесах коллегам соседних отделов.

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

Если вы тоже в вечном поиске классных фронтендеров или сами часто проходите собесы - велком в эту статью :)

Я предлагаю написать hook для загрузки данных. Примерно так:

Напиши React hook с названием useFetch, который получает на вход URL для загрузки и возвращает полученные данные. Для загрузки данных можно использовать любое API, даже собственно придуманное. Для простоты считаем, что по указанному адресу будет JSON, загружаем методом GET, никаких других методов, заголовков и типов данных не требуется. Импорты можно не писать.

Дополнительно даю такой "шаблон" решения задачи:

function useFetch(url) {
  // TODO
}

// usage
function Component({ url }) {
  ........ = useFetch(url);

  return <>
	...
  </>
}

Я намеренно не даю информации как именно надо вернуть данные, предлагаю кандидату самостоятельно спроектировать API этого hook'а.

Задачка "многослойная". Можно начать с простого решения:

function useFetch(url) {
	const [data, setData] = useState(null);

	useEffect(() => {
		fetch(url)
			.then((res) => res.json())
			.then((respData) => setData(respData));
	}, []);

	return data;
}

Это необходимый минимум с которого надо начинать. Иногда даже это не удается получить. Соискатель пытается вернуть из hook Promise и "зарендерить" его в надежде на чудо, пытается обмазать результат хука async/await и т.д. Иногда вся логика загрузки оказывается в компоненте - тут я прошу все это инкапсулировать внутри hook.

Дальше нужно осознать, что в эффекте не хватает зависимости. Если url изменится, то запроса данных не произойдет.

function useFetch(url) {
	const [data, setData] = useState(null);

	useEffect(() => {
		setData(null); // не забыть сбросить данные перед загрузкой
		fetch(url)
			.then((res) => res.json())
			.then((respData) => setData(respData));
	}, [url]); // <-- не забыть зависимость

	return data;
}

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

function useFetch(url) {
	const [data, setData] = useState(null);
	const [isLoading, setIsLoading] = useState(false);
	const [error, setError] = useState(null);

	useEffect(() => {
		// не забыть все сбросить
		setIsLoading(true);
		setData(null);
		setError(null);
		fetch(url)
			.then((res) => res.json())
			.then((respData) => setData(respData))
			.catch((e) => setError(e)) // не забыть поймать ошибку
			.finally(() => setIsLoading(false)); // не забыть сбросить статус загрузки
	}, [url]); 

	return [data, isLoading, error];
}

Следующий уровень сложности - понять, что в этой реализации возможен race condition. Если мы последовательно запросим два URL, и так получится, что первый будет отвечать долго, а второй быстро, то мы получим сперва результат от второго адреса, а затем от первого. В итоге пользователь увидит устаревший результат. Далеко не все добираются до этого самостоятельно. Можно подсказать, предложить подумать, как будут развиваться события, если запросы будут отвечать за разное время, один быстрее, другой медленнее.

Решения могут быть через AbortController (в случае fetch или в случае использования axios), но достаточно реализовать самый простой способ - с помощью локальной переменной

function useFetch(url) {
   const [data, setData] = useState(null);
   const [isLoading, setIsLoading] = useState(false);
   const [error, setError] = useState(null);

   useEffect(() => {
      // флаг отмены
      let cancelled = false;

      setIsLoading(true);
      setData(null);
      setError(null);
      fetch(url)
         .then((res) => res.json())
         .then((respData) => {
            if (!cancelled) setData(respData);
         })
         .catch((e) => {
            if (!cancelled) setError(e);
         })
         .finally(() => {
            if (!cancelled) setIsLoading(false);
         });

      return () => {
         // выставим признак того, что запрос отменен
         cancelled = true;
      };
   }, [url]);

   return [data, isLoading, error];
}

Вот такой вариант я считаю "идеальным" (обсудим в комментариях, что можно еще улучшить?).

Задачка позволяет проверить, как кандидат понимает устройство рендеринга React, как устроено хранение состояния, когда происходят перерисовки, как заставить компонент перерисоваться в ответ на асинхронное событие, как устроена "очистка (cleanup) эффекта", как работают сайд-эффекты.

Tags:
Hubs:
Total votes 23: ↑23 and ↓0+23
Comments56

Articles

Information

Website
sbis.ru
Registered
Founded
Employees
1,001–5,000 employees
Location
Россия