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.
// 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 не требует интеграции в сборку?
Прошло почти два года и пока не стандарт.
Надеюсь они делают что-то большее чем просто автодобавление даже аналогов useMemo
и useCallback
. Одно дело когда функция статична и никогда не вызовет ререндер, даже если в ней используется состояние. А другое когда функция на каждое изменение будет всё равно создаваться заново (использование useCallback
с зависимостью в виде состояния).
Почитал отчёт о нём за прошлый год. Рад что они занимаются этой проблемой. И да, это большее чем я написал.
Правда ещё не ясно можно ли его будет использовать на разных версиях react
Что такое react-afc