08 апреля 2021

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика

Frontend-разработчик в Foquz. https://www.cat-in-web.ru/
Что делать, если скорость разработки уже не та, что раньше? Отказавшись от монолита, изменить подход к написанию кода и начать использовать его повторно!
☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика
Ваша производительность ухудшается, а скорость разработки стремительно падает? Проводите время за отловом багов, вместо того, чтобы писать новые фичи? Не можете найти нужный код в огромных файлах и делаете одно и то же снова и снова?

Вам помогут переиспользуемые блоки кода! Переиспользуемые блоки кода – то, что нужно прогрессивному разработчику!

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

Так бросим же все силы на то, чтобы научиться мыслить в компонентном стиле!

Учимся готовить компоненты

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

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

Исходный компонент

Рассмотрим неограненный алмаз, с которым мы будем работать. Этот простой компонент умеет не так уж и много:

  1. он получает с бэкенда список браузеров;
  2. отображает индикатор загрузки, пока запрос выполняется;
  3. выводит полученные данные в виде карточек;
  4. если пользователь нажимает на карточку, компонент показывает модальное окно с детальной информацией о браузере.

Этот код похож на ваш?

Browsers.js
        import React, { useEffect, useState } from 'react';
import { FlatList, Text, View, StyleSheet, Modal, TouchableOpacity } from 'react-native';

import AddModal from '../components/AddModal';
import LoadingIndicator from '../components/LoadingIndicator'
import BrowserItem from '../components/BrowserItem'

import colors from '../config/colors';

function Browsers() {

    const URL = 'https://google.com/myData.json'

    // Элемент массива
    // {"Browsers":[
    //     {
    //      "fullname": "Chrome",
    //      "linkToBrowser": "https://google.com",
    //      "image": "https://linktoimage.com/chrome.png",
    //      "minMemory": "1 GB",
    //      "currentVersion": "29.0.1",
    //      "minimumRAM": "2 GB",
    //      "description": "How much RAM do you have? Ha-ha",
    //      "windows": true,
    //      "mac": true,
    //      "linux": true,
    //      "ubuntu": true,
    //      "fedora": false,
    //      "stars": 4,
    //      "id":"chrome"
    //    },
    // ...
    // ]
    // }

    const [loading, setLoading] = useState(true)
    const [browsers, setBrowsers] = useState([])

    const [modalVisible, setModalVisible] = useState(false)
    const [description, setDescription] = useState("")

    const changeDescription = (description) => {
        setDescription(description)
        setModalVisible(!modalVisible)
    }

    const changeOpacity = () => {
        setModalVisible(!modalVisible)
        console.log('changeOpacity')
    }



    useEffect(() => {
        fetch(URL)
            .then((response) => response.json())
            .then((responseJson) => {
                return responseJson.Browsers
            })
            .then(browsers => {
                setBrowsers(browsers)
                // console.log(browsers)
                setLoading(false)
            })
            .catch(error => {
                console.log(error)
            })
            .finally(() => setLoading(false));
    }, [])


    return (
        <View style={styles.container}>
            {loading ? (
                <LoadingIndicator />
            ) : (
                    <View>
                        <AddModal 
                            modalVisible={modalVisible}
                            changeOpacity = {() => changeOpacity()}
                            description={description}
                        />
                        <FlatList
                            data={browsers}
                            keyExtractor={browser => browser.fullname}
                            renderItem={({ item }) =>
                                <BrowserItem
                                    fullname={item.fullname}
                                    image={item.image}
                                    linkToBrowser={item.linkToBrowser}
                                    minMemory={item.minMemory}
                                    currentVersion={item.currentVersion}
                                    minimumRAM={item.minimumRAM}
                                    description={item.description}
                                    windows={item.windows}
                                    mac={item.mac}
                                    linux={item.linux}
                                    ubuntu={item.ubuntu}
                                    fedora={item.fedora}
                                    stars={item.stars}
                                    changeDescription={() => changeDescription(item.description)}
                                />
                            }
                        />
                    </View>
                )
            }
        </View >
    );
};

const styles = StyleSheet.create({
    container: {
        justifyContent: 'center',
        alignItems: 'center'
    },
})

export default Browsers;
    

Что с этим компонентом так?

Ну, он работает :) И использует хуки, что само по себе уже неплохо.

Что с этим компонентом не так?

Это монолит. Не получится взять его и переиспользовать в другом месте или другом приложении.

К чему мы стремимся?

Искусство быстрой разработки – это искусство создания переиспользуемых блоков кода.

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

  • Не нужно повторять одно и то же по десять раз – помним о принципе DRY!
  • Не нужно вносить изменения в нескольких местах – и лихорадочно вспоминать, где еще есть этот функционал.
  • Не нужно ловить баги по всему приложению – они смирно сидят в компоненте и ждут вас.
  • Не нужно много читать, код сразу становится короче, чище и понятнее.
  • Не нужно писать тесты для каждого места, в котором используется функциональность.

Приступаем к огранке

Начнем с самых простых приемов рефакторинга, а затем войдем во вкус и сдеаем что-то посложнее.

Шаг #1. Перенос констант в пропсы

Browsers.js
        function Browsers({url = 'https://google.com/myData.json'}) {

  const URL = url
  ...
    

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

Шаг #2. Разделение бизнес-логики и отображения

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

В чем вообще разница?

Бизнес-логика: Код, который принимает решения и изменяет состояния. Все внутри <Browsers/> до инструкции return.

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

Давайте произведем радикальное рассечение компонента на две части и убедимся, что жизнь действительно стала проще:

  • Пользовательский хук useBrowsers() с бизнес-логикой.
  • Компонент <BrowsersList /> с логикой отображения.
Browsers_splitted.js
        import React, {useEffect, useState} from 'react'
import {FlatList, StyleSheet, View} from 'react-native'

import AddModal from '../components/AddModal'
import LoadingIndicator from '../components/LoadingIndicator'
import BrowserItem from '../components/BrowserItem'

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
  },
})

function useBrowsers(url) {
  const [loading, setLoading] = useState(true)
  const [browsers, setBrowsers] = useState([])

  const [modalVisible, setModalVisible] = useState(false)
  const [description, setDescription] = useState('')

  const changeDescription = (description) => {
    setDescription(description)
    setModalVisible(!modalVisible)
  }

  const changeOpacity = () => {
    setModalVisible(!modalVisible)
    console.log('changeOpacity')
  }

  useEffect(() => {
    fetch(URL)
      .then((response) => response.json())
      .then((responseJson) => {
        return responseJson.Browsers
      })
      .then((browsers) => {
        setBrowsers(browsers)
        // console.log(browsers)
        setLoading(false)
      })
      .catch((error) => {
        console.log(error)
      })
      .finally(() => setLoading(false))
  }, [])

  return {
    loading,
    browsers,
    modalVisible,
    description,
    changeDescription,
    changeOpacity,
  }
}

function BrowsersList({
  loading,
  browsers,
  modalVisible,
  description,
  changeDescription,
  changeOpacity,
}) {
  return (
    <View style={styles.container}>
      {loading ? (
        <LoadingIndicator />
      ) : (
        <View>
          <AddModal
            modalVisible={modalVisible}
            changeOpacity={() => changeOpacity()}
            description={description}
          />
          <FlatList
            data={browsers}
            keyExtractor={(browser) => browser.fullname}
            renderItem={({item}) => (
              <BrowserItem
                fullname={item.fullname}
                image={item.image}
                linkToBrowser={item.linkToBrowser}
                minMemory={item.minMemory}
                currentVersion={item.currentVersion}
                minimumRAM={item.minimumRAM}
                description={item.description}
                windows={item.windows}
                mac={item.mac}
                linux={item.linux}
                ubuntu={item.ubuntu}
                fedora={item.fedora}
                stars={item.stars}
                changeDescription={() => changeDescription(item.description)}
              />
            )}
          />
        </View>
      )}
    </View>
  )
}

function Browsers() {
  return <BrowsersList {...useBrowsers('https://google.com/myData.json')} />
}

export default Browsers
    

Код все еще далек от идеала, но уже стал немного более удобным.

  • <BrowsersList /> теперь можно использовать с разными источниками данных (а не только с данными из HTTP-эндпоинтов).
  • useBrowsers() теперь может работать с разными представлениями или даже вообще без представления. Если вы измените дизайн приложения, этот хук продолжит работать.

Шаг #3. Разделение на файлы

Разделение монолита на две части открыло широкий простор для нового рефакторинга. Чтобы программа стала еще более читаемой и годной к переиспользованию, разделим код на отдельные изолированные модули. Как говорится, чем изолированнее, тем реюзабельнее.

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика

Это файловая структура типичного React-проекта:

  1. index.js экспортирует <Browsers/> из Browsers.jsx;
  2. строительные блоки для <Browsers/> лежат в папках components и hooks;

BrowsersList.jsx можно развить до самостоятельной папки с собственными хуками, компонентами и файлом index.js

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика

Посмотрим на BrowsersList.jsx:

        import React from 'react'
import {FlatList, StyleSheet, View} from 'react-native'
import AddModal from '../../../components/AddModal'
import LoadingIndicator from '../../../components/LoadingIndicator'
import BrowserItem from '../../../components/BrowserItem'

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
  },
})

export function BrowsersList({
  loading,
  browsers,
  modalVisible,
  description,
  changeDescription,
  changeOpacity,
}) {
  return (
    <View style={styles.container}>
      {loading ? (
        <LoadingIndicator />
      ) : (
        <View>
          <AddModal
            modalVisible={modalVisible}
            changeOpacity={() => changeOpacity()}
            description={description}
          />
          <FlatList
            data={browsers}
            keyExtractor={(browser) => browser.fullname}
            renderItem={({item}) => (
              <BrowserItem
                fullname={item.fullname}
                image={item.image}
                linkToBrowser={item.linkToBrowser}
                minMemory={item.minMemory}
                currentVersion={item.currentVersion}
                minimumRAM={item.minimumRAM}
                description={item.description}
                windows={item.windows}
                mac={item.mac}
                linux={item.linux}
                ubuntu={item.ubuntu}
                fedora={item.fedora}
                stars={item.stars}
                changeDescription={() => changeDescription(item.description)}
              />
            )}
          />
        </View>
      )}
    </View>
  )
}
    
Этот код уже почти помещается в один экран вашего редактора. Меньше хлама – легче читать!

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

Большая проблема маленьких файлов

Взгляните на сигнатуру функции <BrowsersList />. Что произойдет, если мы переименуем какой-нибудь пропс, например, changeDescription на setSelectedBrowser? Или удалим аргумент? Или добавим новый?

Все сломается!

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

Ну и где профит-то, спросите вы? Опять что-нибудь где-нибудь забудем исправить – и прощай продакшн.

Посмотрите внимательно, все ли тут в порядке?

Файл useBrowsers.js:

        ...
return {
    loading,
    browsers,
    modalVisible,
    descripton,
    changeDescription,
    changeOpacity,
  }
...
    

Файл BrowsersList.jsx:

        ...
export function BrowsersList({
  loading,
  browsers,
  modalVisible,
  description,
  changeDescription,
  changeOpacity,
}) {
....
    

Бинго! Одна маленькая опечатка – и все идет кувырком.

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

Хватит медленно кодить на JS, давайте кодить быстро на TS

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

38% багов на Airbnb можно было предотвратить с помощью TypeScript
38% багов на Airbnb можно было предотвратить с помощью TypeScript

С TypeScript ваша IDE обогатится несколькими крутыми фичами:

  • Автодополнение для пропсов
☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика
  • Возможность автоматического переименования пропсов
  • Проверка на null/undefined
☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика
  • Валидация значений пропсов
☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика
TypeScript сэкономит вам тонну времени и километры нервов.

А вот, кстати, и наша опечатка:

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика

Шаг #4. Определение типов

Так как это не полноценный гайд по TypeScript, разбираться в основах мы сейчас не будем. Все желающие могут заглянуть на typescriptlang.org (или прочитать статью в «Библиотеке программиста» – прим. ред.). Вернемся к <BrowsersList /> и преобразуем его пропсы в симпатичные типы (не забудьте сменить расширение с .jsx на .tsx).

BrowsersList_props_types.tsx
        // ...

export type Browser = {
  fullname: string // ожидается, что поле "fullname" - это строка
  image: string
  linkToBrowser: string
  minMemory: string
  currentVersion: string
  minimumRAM: string
  description: string
  windows: boolean
  mac: boolean
  linux: boolean
  ubuntu: boolean
  fedora: boolean
  stars: number
}

export type BrowsersListProps = {
  loading: boolean
  browsers: Browser[] // ожидается, что поле "browsers" это массив элементов с типом Browser
  modalVisible: boolean
  description: string
  
  // ожидается, что changeDescription - это функция
  // которая принимает строку и ничего не возвращает
  changeDescription: (description: string) => void
  
  // ожидается, что changeOpacity - это функция
  // которая ничего не принимает и ничего не возвращает
  changeOpacity: () => void
}

export function BrowsersList({
  loading,
  browsers,
  modalVisible,
  description,
  changeDescription,
  changeOpacity,
}: BrowsersListProps) {
  return (

// ...
    

Теперь обновим сигнатуру useBrowsers():

useBrowsers.ts
        import {useEffect, useState} from 'react'
import {BrowsersListProps} from '../components/BrowsersList'

export function useBrowsers(url: string): BrowsersListProps {
  const [loading, setLoading] = useState(true)
  
 // ...
    

И теперь TypeScript будет строго следить, чтобы useBrowsers() и BrowsersList были совместимы.

Быстрая разработка архитектуры

BrowsersListProps выглядит весьма нагруженно:

props.tsx
        export type BrowsersListProps = {
  loading: boolean
  browsers: Browser[]
  modalVisible: boolean
  description: string
  changeDescription: (description: string) => void
  changeOpacity: () => void
}
    
  • Одна строчка – для состояния загрузки.
  • Одна строчка – для списка браузеров.
  • Целых четыре строчки – для отображения детальной информации о браузере в модальном окне. Есть большая вероятность, что потребуется выводить больше данных, а значит сигнатура может измениться. А это весьма беспокойное дело, как мы помним.

Мы можем уменьшить сложность этого фрагмента и отрефакторить функциональность модального окна для использования типа Browser:

TypeScript указывает на некорректность сигнатуры при ее изменении
TypeScript указывает на некорректность сигнатуры при ее изменении

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

Теперь поправим <BrowserList />, чтобы он мог работать с новой сигнатурой BrowsersListProps.

Отрефакторим <BrowserItem />. Сейчас он принимает множество пропсов, и это явный сигнал для внесения правок. Теперь он будет принимать всего 2 аргумента:

BrowsersList.tsx
        import React from 'react'
import {FlatList, StyleSheet, View} from 'react-native'
import AddModal from '../../../components/AddModal'
import LoadingIndicator from '../../../components/LoadingIndicator'
import BrowserItem from '../../../components/BrowserItem'

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
  },
})

export type Browser = {
  fullname: string
  image: string
  linkToBrowser: string
  minMemory: string
  currentVersion: string
  minimumRAM: string
  description: string
  windows: boolean
  mac: boolean
  linux: boolean
  ubuntu: boolean
  fedora: boolean
  stars: number
}

export type BrowsersListProps = {
  loading: boolean
  browsers?: Browser[]
  selectedBrowser?: Browser
  setSelectedBrowser: (browser?: Browser) => void
}

export function BrowsersList(props: BrowsersListProps) {
  const {loading, selectedBrowser, setSelectedBrowser, browsers} = props
  return (
    <View style={styles.container}>
      {loading ? (
        <LoadingIndicator />
      ) : (
        <View>
          <AddModal
            modalVisible={Boolean(selectedBrowser)}
            onClose={() => setSelectedBrowser(undefined)}
            description={selectedBrowser?.description}
          />
          <FlatList
            data={browsers}
            keyExtractor={(browser) => browser.fullname}
            renderItem={({item}) => (
              <BrowserItem
                browser={item}
                onPress={() => setSelectedBrowser(item)}
              />
            )}
          />
        </View>
      )}
    </View>
  )
}
    

Компонент стал проще и надежнее.

Шаг #5. Извлечение <UIFriendlyList />

Если вы немного помедитируете на компонент, то на вас снизойдет просветление. Список с "состоянием загрузки" – это очень крутая и востребованная функциональность, которая наверняка будет использоваться везде, где только можно.

Сейчас эта функциональность интегрирорована в <BrowsersList />. Значит надо ее извлечь. Создадим новый компонент <UIFriendlyList /> и будем использовать его вместо простого <FlatList/ >.

Как всегда начнем с определения типов:

        type UIFriendlyListProps<T> = FlatListProps<T> & {loading?: boolean}

    

Итак, что мы сделали:

  • Определили тип для пропсов нового компонента.
  • Использовали дженерик, чтобы указать, что список может содержать элементы разных типов.
  • UIFriendlyListProps расширяет встроенные класс FlatListProps из библиотеки React Native и добавляет в него состояние загрузки.

Теперь вынесем новый компонент в отдельный файл UIFriendlyList.jsx:

UIFriendlyList.tsx
        import React from 'react'
import {FlatList, FlatListProps, Text} from 'react-native'
import LoadingIndicator from './LoadingIndicator'

export type UIFriendlyListProps<T> = FlatListProps<T> & {loading?: boolean}

export function UIFriendlyList<T>(props: UIFriendlyListProps<T>) {
  if (props.loading) {
    return <LoadingIndicator />
  }

  if (props?.data && props.data.length === 0) {
    return <Text>This list is empty (</Text>
  }

  return <FlatList {...props} />
}
    

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

Компонент <UIFriendlyList /> – это крутой строительный блок, который очень легко переиспользовать в самых разных ситуациях. Добавьте его в свою коллекцию кирпичиков, чтобы стать еще быстрее.

Во что теперь превратился наш <BrowsersList />:

BrowsersList.tsx
        import React from 'react'
import {StyleSheet, View} from 'react-native'
import AddModal from '../../../components/AddModal'
import BrowserItem from '../../../components/BrowserItem'
import {UIFriendlyList} from '../../../components/UIFriendlyList'

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
  },
})

export type Browser = {
  fullname: string
  image: string
  linkToBrowser: string
  minMemory: string
  currentVersion: string
  minimumRAM: string
  description: string
  windows: boolean
  mac: boolean
  linux: boolean
  ubuntu: boolean
  fedora: boolean
  stars: number
}

export type BrowsersListProps = {
  loading: boolean
  browsers?: Browser[]
  selectedBrowser?: Browser
  setSelectedBrowser: (browser?: Browser) => void
}

export function BrowsersList(props: BrowsersListProps) {
  const {loading, selectedBrowser, setSelectedBrowser, browsers} = props
  return (
    <View style={styles.container}>
      <AddModal
        modalVisible={Boolean(selectedBrowser)}
        onClose={() => setSelectedBrowser(undefined)}
        description={selectedBrowser?.description}
      />
      <UIFriendlyList
        loading={loading}
        data={browsers}
        renderItem={({item}) => (
          <BrowserItem
            key={item.fullname}
            browser={item}
            onPress={() => setSelectedBrowser(item)}
          />
        )}
      />
    </View>
  )
}
    

Сравните его с исходным вариантом. Он почти в два раза меньше и гораздо проще для понимания. К тому же в качестве бонуса вы получили отличный компонент <UIFriendlyList />, который в будущем сэкономит вам кучу времени. Можно пойти еще дальше и выделить, например, в отдельный кирпичик логику Модальное Окно со Списком, но давайте пока остановимся.

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

Мы закончили с логикой отображения, пора вернуться к бизнес-логике.

Шаг #6. Рефакторинг useBrowsers()

Хук useBrowswers() должен возвращать валидный объект с типом BrowsersListProps:

useBrowsers.ts
        import {useEffect, useState} from 'react'
import {Browser, BrowsersListProps} from '../components/BrowsersList'

export function useBrowsers(url: string): BrowsersListProps {
  const [loading, setLoading] = useState(false)
  const [browsers, setBrowsers] = useState<Browser[]>([])
  const [selectedBrowser, setSelectedBrowser] = useState<Browser | undefined>(
    undefined,
  )

  useEffect(() => {
    setLoading(true)
    fetch(url)
      .then((response) => response.json())
      .then((responseJson) => {
        return responseJson.Browsers
      })
      .then((browsers) => {
        setBrowsers(browsers)
      })
      .catch((error) => {
        console.log(error)
      })
      .finally(() => setLoading(false))
  }, [url])

  return {
    loading,
    browsers,
    selectedBrowser,
    setSelectedBrowser,
  }
}
    

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

Как всегда, сначала типы. Нам нужно, чтобы useFetch сохранял данные из эндпоинта и отображал состояние загрузки. Формат данных будет задан отдельно, для этого используем дженерик:

useFetch.ts
        export type FetchBrowsersResults = {
  Browsers: Browser[]
}

export type UseFetch<T> = {
  loading: boolean
  
  // We use Generic. T - is a type argument that can be any type.
  // We can useFetch() with any type
  // ? means, that T can be undefined
  data?: T
}

export function useFetch<T>(url: string): UseFetch<T> {}
    

Таким образом, useFetch можно будет использовать с любыми типами данных.

Теперь сама функция:

useFetch.ts
        import {useEffect, useState} from 'react'
import {Alert} from 'react-native'

export type UseFetch<T> = {
  loading: boolean
  data?: T 
}

export function useFetch<T>(url: string): UseFetch<T> {
  const [loading, setLoading] = useState<boolean>(false)
  const [data, setData] = useState<T | undefined>(undefined)

  useEffect(() => {
    setLoading(true)
    fetch(url)
      .then((response) => response.json())
      .then(setData)
      .finally(() => setLoading(false))
      .catch((error) => Alert.alert('Fetch error', error))
  }, [url])

  return {
    loading,
    data,
  }
}
    

Для полноты картины добавили Alert при ошибке запроса.

Теперь окончательно отрефакторим useBrowsers():

useBrowsers.ts
        import {useState} from 'react'
import {Browser, BrowsersListProps} from '../components/BrowsersList'
import {useFetch} from '../../../hooks/useFetch'

export type FetchBrowsersResults = {
  Browsers: Browser[]
}

export function useBrowsers(url: string): BrowsersListProps {
  const {loading, data} = useFetch<FetchBrowsersResults>(url)
  const [selectedBrowser, setSelectedBrowser] = useState<Browser | undefined>(
    undefined,
  )

  return {
    loading,
    browsers: data?.Browsers,
    selectedBrowser,
    setSelectedBrowser,
  }
}
    

Сравните с исходным вариантом.

Кажется, здесь больше нечего извлекать :)

4 простых совета для ускорения разработки

1. Никогда не форматируйте код вручную.

Используйте возможности IDE, ESLint и Prettier.

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика

2. Никогда не импортируйте модули вручную

Использование автоматического импорта сэкономит много времени.

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика

3. Научитесь ориентироваться среди множества файлов

Разделяя проект на маленькие переиспользуемые кирпичики вы неизбежно приходите к монструозной файловой структуре вроде этой:

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика

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

4. Используйте линтинг

Это поможет найти самые хитрые и вредные ошибки:

☕ Пиши на React в два раза быстрее! Простые трюки для крутого разработчика
***

Искусство быстрой разработки

Чтобы быть быстрым, не потеряв при этом в качестве, вам нужно освоить несколько вещей:

  • Отделять бизнес-логику от отображения.
  • Разделять код на маленькие реюзабельные компоненты.
  • Использовать TypeScript и начинать работу с определения типов.
  • Практиковаться в рефакторинге.
  • Использовать плюшки вашей IDE.

Насколько вы постигли дзен быстрой разработки? Признавайтесь, часто рефакторите?

Дополнительные материалы:

Источники

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
PHP Developer
от 200000 RUB до 270000 RUB
Golang разработчик (middle)
от 230000 RUB до 300000 RUB
Аналитик данных
Екатеринбург, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ