Pull to refresh

Comments 27

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

Можно. Эта библиотека не заменяет (и даже не пытается делать этого) такие инструменты как MobX. Лишь выступает как небольшое добавление функционала к оригинальному react.

MobX не будет делать частичного обновления компонента, пока вы его сами не разобьёте на несколько, а тут как я понимаю, пытаются именно в частичное обновление. Это корректнее вроде бы с @preact/signals-react сравнивать. Но я пока не уверен.

@preact/signals это про управление состоянием.
Тут же единственная фича - инициализатор (или же "конструктор", как угодно), плюс функции, позволяющие в нём использовать аналогичный react-хукам функционал.
Уменьшение ререндеров следует из этой фичи. Все колбеки и переменные из инициализатора не вызывают ререндер, так как ссылка на них не меняется в течение всего жизненного цикла.


В обычном функциональном компоненте ссылка на любую функцию будет новая каждый рендер. Также все переменные будут созданы заново. При передаче в компонент они вызовут его ререндер (даже компонента в memo, исключение - строки и числа, они сравниваются по значению).
Именно по этой причине нет смысла оборачивать в memo компонент, принимающий children,так как это массив и он имеет разную ссылку при каждом рендере.

Это довольно назойливая проблема реакта, которая в один момент может заставить фризить всё приложение и придётся использовать useMemo / useCallback / useRef.

Какой смысл бороться с тем, что в основе библиотеки лежит ? На мой взгляд это очередной костыль.

Сделать хлеборезку, а потом апгрейживать ее до утюга.

Ну так тоже можно.

Если сами разработчики react предоставили инструменты для того чтобы бороться с ререндерами, то почему бы не упростить их использование?
Если взять UI либу (Antd к примеру, а уж теб более MIU), то ой как встанет вопрос о сокращении лишних отрисовок, ибо даже 20-30 дополнительных отрисовок сложных компонентов будут фризить всё приложение.

Где много отрисовок, лучше реакт вообще не использовать. Если бы разработчики предоставили метод, они бы его как контекст в библиотеку добавили.

А так, от этой проблемы только костыли. Ее невозможно в реакте решить. По крайней мере сейчас.

Звучит всё неплохо.

Из того что бросилось в глаза - изменение сигнатуры useState вместе с сохранением нейминга. Я думаю это будет путать при чтении, все привыкли что useState это value+setter, а не getter+setter. Можно сказать - ну так это же другой useState, но при чтении на месте использования не очевидно, откуда был импорт.

Также на это имя могут быть завязаны фишки IDE (например парное переименование в jetbrains) или например какие-то правила линтеров.

Я бы посоветовал посмотреть в сторону имени useSignal, сигналы как раз дают ту же сигнатуру, getter+setter.

Хорошее замечание. Обязательно подумаю над переименованием useState в useSignal

// afc компонент
const AdvancedComponent = afc(() => {
  let renderCount = 0

  return () => {
    renderCount++
    return (
      <p>
        Рендер вызван {renderCount} раз
      </p>
    )
  }
})
// Afc компонент
class AdvancedComponent extends Afc {
  renderCount = 0

  render() {
    this.renderCount++
    return (
      <p>
        Рендер вызван {this.renderCount} раз
      </p>
    )
  }
  
})

Oh, wait..

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

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

Гибкость в более простом создании переиспользуемой логики.
Для классов есть только путь HOC или заморочкой с наследованием/передачей this, что в больших объёмах понижает производительность, да и читается хуже чем обычный хук в функциональщине.

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

const App = afc(() => {
  const [getName, setName] = useState('')
  const [getAge, setAge] = useState(1)

  const onChangeName = value => setName(value)
  const onChangeAge = value => setAge(value)
  const closeWindow = () => window.close()

  const titleArgs = {
    color: 'blue',
    size: 20
  }
  
  return () => (
    <>
      <Title text='Amazing app' args={titleArgs} />
      <HardCalcHeader onExit={closeWindow} />
        
      <NameInput value={getName()} onChange={onChangeName} />
      <AgeInput value={getAge()} onChange={onChangeAge} />
    </>
  )
})
export class App extends Afc {
  @mem name = ''
  @mem age = 1

  @act onChangeName = value => { this.name = value }
  @act onChangeAge = value => { this.age = value }
  @act closeWindow = ()=> { window.close() }

  titleArgs = {
    color: 'blue',
    size: 20
  }
  
  render () {
    return <>
      <Title text='Amazing app' args={this.titleArgs} />
      <HardCalcHeader onExit={this.closeWindow} />
        
      <NameInput value={getName()} onChange={this.onChangeName} />
      <AgeInput value={getAge()} onChange={this.onChangeAge} />
    </>
  }
}

Ой, как useState прекрасно читается-то. И с this такие проблемы, ух. А уж переиспользование классов - это вообще что-то из области фантастики, да.

Как без HOC добавить классу общий функционал (применяемый в нескольких компонентах), использующий состояние или метод жизненного цикла?
Ещё и полностью типизированный ;)

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

Для добавления объекту новой функциональности есть 3 подхода - смешивание с другим объектом, обертка другим объектом, замена объектов/функций в полях-ссылках объекта на другие объекты/функции.

Вариации 3-го подхода:
Можно вынести метод в функцию вне класса и вызывать ее в соответствующем методе жизненного цикла в нужных компонентах.

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

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

Реализацию такого подхода я описывал в https://habr.com/ru/articles/545064

Мне кажется, когда встаёт вопрос про уменьшение ререндеров путём кардинального обратно-не-совместимого изменения API компонентов, проще совсем уйти от React в сторону библиотек, которые из коробки работают с ререндерами хорошо, например, Preact, Solid или Svelte. Потому что если ваше приложение написано на react-afc и вы используете другие библиотеки для React, то количество ререндеров библиотечных компонентов всё равно останется таким же и это будет бутылочным горлышком, пока создатели библиотек не перепишут их на этот же react-afc или что-то аналогичное.

Библиотечные компоненты как правило оптимизированы и сами по себе не вызывают проблем (если их правильно использовать).
Есть совместимое API у библиотеки, можно писать хуки для обычных и для afc компонентов одновременно, при этом само написание остаётся идентичным в 90% случаев.
С точки зрения использования компонента разницы нет, те же пропсы, callback'и и всё остальное. Вся работа идёт внутри, а снаружи то же самое API компонента (afc возвращает React.FC).

В статье не хватает сравнения с memo(). Как понимаю, afc выполняет схожую работу, но после прочтения я, если честно, не понял существенных различий между ними. Возможно, afc глубже работает с хуками?

memo работает с пропсами компонента, то есть позволяет не ререндерить сам компонент если входные данные идентичны.
afc же добавляет инициализатор компонента, позволяя при изменении состояния не ререндерить дочерние компоненты который в этом не нуждаются.
Также есть afcMemo, который добавляет и то и другое (afc оборачивается в memo)

Я правильно понимаю, что в отличие @preact/signals-react, afc не требует интеграции в сборку?

Вся библиотека react-afc это лишь обёртка над обычным react.
Даже сама функция afc лишь создаёт функциональный компонент с некоторым функционалом, который в качестве представления возвращает результат render-функции.

То есть да, никакая интеграция не требуется.

Прошло почти два года и пока не стандарт.
Надеюсь они делают что-то большее чем просто автодобавление даже аналогов useMemo и useCallback. Одно дело когда функция статична и никогда не вызовет ререндер, даже если в ней используется состояние. А другое когда функция на каждое изменение будет всё равно создаваться заново (использование useCallback с зависимостью в виде состояния).

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

Sign up to leave a comment.

Articles