Что такое react-afc

от автора

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-хуки и вызывается рендер-функция.

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


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


Комментарии

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

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