Иной взгляд на React компоненты

от автора

Как мы все знаем, в React есть функциональные и классовые компоненты. Каждый вид имеет свои плюсы и минусы.

Классовые компоненты имеют меньшую производительность по сравнению с функциональными и вызывают некоторые сложности в переиспользовании одинаковой логики.

Моё мнение

Лично мне не нравятся повсеместные стрелочные функции и this.

Функциональные компоненты, в свою очередь, для оптимизации заставляют нас оборачивать объекты в useMemo, а функции в useCallback. Что уменьшает читаемость кода, а при большом количестве вызовов также понижает производительность (как бы это не было парадоксально).

Вы можете задаться вопросом: «Разве у нас есть иной вариант?». Да, он существует!
Что, если взять функциональный компонент и добавить ему функцию «конструктор», подобно одноимённому методу в классах?. Тогда нам не потребуется оборачивать в useMemo и useCallback, так как объекты и функции будут создаваться один раз. Также мы не потеряем удобное переиспользование логики и нам не потребуется this на каждой строке.

Довольно заманчивые условия, но разве это возможно сделать без «костылей»?
Я задался этим вопросом и нашёл решение: использовать замыкания для реализации «конструктора». После нескольких вечеров на просторах интернета «родился» npm-пакет react-afc.

Как мог выглядеть компонент со сложной логикой на чистом React:

import React, { useMemo, useState, useCallback, memo } from 'react' import ComplexInput from './ComplexInput' import ComplexOutput from './ComplexOutput'  function Component(props) {   const [text, setText] = useState('')      const config = useMemo(() => ({     showDesc: true,     title: 'Title'   }), [])    const onChangeText = useCallback(e => {     setText(e.target.value)   })    const onBlur = useCallback(() => {     // hard calculations   })    return <>     <ComplexInput value={text} onChange={onChangeText} onBlur={onBlur} />     <ComplexOutput config={config} />   </> }  export default memo(Component)

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

Тот же пример, но с использованием react-afc:

import React from 'react' import { afcMemo, useState } from 'react-afc' import ComplexInput from './ComplexInput' import ComplexOutput from './ComplexOutput'  function Component(props) {   const [text, setText] = useState('')      const config = {     showDesc: true,     title: 'Title'   }    function onChangeText(e) {     setText(e.target.value)   }    function onBlur() {     // hard calculations   }    return () => <>     <ComplexInput value={text.val} onChange={onChangeValue} onBlur={onBlur} />     <ComplexOutput config={config} />   </> }  export default afcMemo(Component)

Что же изменилось?

Теперь функция компонент является «конструктором» и вызывается только один раз в течение всего жизненного цикла компонента. Это значит, что onChangeText, onBlur и config одинаковые каждый рендер (без использования обёрток), то есть они не вызывают перерисовку «детей» при обновлении компонента. Конструктор возвращает рендер-функцию, которая вызывается каждый рендер.

Что насчёт производительности?

Пакет максимально переиспользует React-хуки: при нескольких вызовах useState из react-afc используется лишь один хук из React. Это ломает просмотр состояний компонентов в ReactDevtools, но такова цена производительности.

В целом, разница в производительности незначительна. Но чем сложнее компонент, тем выше разрыв между обычными и afc-компонентами (react-afc может быть до 10% быстрее).

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

Жду вашего мнения в комментариях 🙂


ссылка на оригинал статьи https://habr.com/ru/post/711940/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *