Что такое react-afc
- вторник, 2 января 2024 г. в 00:00:14
react-afc - библиотека для более простого (чем в простом react) уменьшения количества ненужных ререндеров дочерних компонентов.
В обычном react функциональный компонент вызывается каждый раз когда изменяется его состояние или пропсы, что вызывает повторное создание всех callback'ов и переменных.
Так как передаваемые данные из предыдущего и текущего рендера не равны, это порождает ререндер дочерних компонентов.
Функционал компонента не несёт конкретного смысла. Просто пример.
import { useState } from 'react'
import Title from 'title-lib'
import HardCalcHeader from './header'
import NameInput from './name-input'
import AgeInput from './age-input'
function App() {
const [name, setName] = useState('')
const [age, 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={name} onChange={onChangeName} />
<AgeInput value={age} onChange={onChangeAge} />
</>
)
}
При изменении имени происходит перерисовка Title, NameInput, AgeInput, а также HardCalcHeader, что приводит к зависанию приложения.
Для избежания этого поведения мы разбиваем логику на множество компонентов (не всегда удобно), либо используем useCallback
и useMemo
(стоит дополнительных затрат при многократном вызове, ухудшает читаемость кода и требует отслеживания зависимостей функции).
import { useState, useCallback, useMemo } from 'react'
import Title from 'title-lib'
import HardCalcHeader from './header'
import NameInput from './name-input'
import AgeInput from './age-input'
function App() {
const [name, setName] = useState('')
const [age, setAge] = useState(1)
const onChangeName = useCallback(value => setName(value), [])
const onChangeAge = useCallback(value => setAge(value), [])
const closeWindow = useCallback(() => window.close(), [])
const titleArgs = useMemo(() => ({
color: 'blue',
size: 20
}), [])
return (
<>
<Title text='Amazing app' args={titleArgs} />
<HardCalcHeader onExit={closeWindow} />
<NameInput value={name} onChange={onChangeName} />
<AgeInput value={age} onChange={onChangeAge} />
</>
)
}
Вся суть работы библиотеки лежит в одном, но значительном изменении структуры функционального компонента - добавлении аналога конструктора классового компонента.
import { afc } from 'react-afc'
// обычный компонент
function CommonComponent(props) {
// вызывается каждый рендер
// ...react-хуки
return <p>обычный компонент</p>
}
// afc компонент
const AdvancedComponent = afc(props => {
// вызывается один раз за весь жизненный цикл компонента
// afc-хуки
return () => {
// render-функция, вызывается каждый рендер
// ...react-хуки (только по необходимости)
return <p>afc</p>
}
}
Данное изменение позволяет нам во многих случаях не использовать useCallback
и useMemo
, а также не думать о зависимостях.
import { afc, useState } from 'react-afc'
import Title from 'title-lib'
import HardCalcHeader from './header'
import NameInput from './name-input'
import AgeInput from './age-input'
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} />
</>
)
})
Работает аналогично примеру с хуками, никаких лишних перерисовок.
Побочным эффектом является то, что мы больше не нуждаемся в использовании useRef
для передачи данных между рендерами.
import { useRef } from 'react'
import { afc } from 'react-afc'
// обычный компонент
function CommonComponent() {
const renderCount = useRef(0)
renderCount.current++
return (
<p>
Рендер вызван {renderCount.current} раз
</p>
)
}
// afc компонент
const AdvancedComponent = afc(() => {
let renderCount = 0
return () => {
renderCount++
return (
<p>
Рендер вызван {renderCount} раз
</p>
)
}
})
Примечание: в библиотеке имеются аналоги для useState
, useRef
, useMemo
, useEffect
, memo
. Их применение узкоспециализировано, читайте доку.
Пример работы можете найти на codesandbox.
При первом рендере библиотека вызывает переданную в afc
функцию, определяет какие хуки используются и сохраняет возвращённую рендер-функцию.
При последующих рендерах обновляются свойства в объекте пропсов (если они изменились), выполняются определённые ранее react-хуки и вызывается рендер-функция.
Принцип прост как пробка и требует незначительных вычислений только при первом рендере.