Как мы все знаем, в 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/
Добавить комментарий