React.js
React
React vs Vanilla JS:
- React code is more readable (JSX = JS + HTML in 1 file)
- Components
- JS is imperative (буквально даёшь пошаговые инструкции)
- React is declarative (описываешь что именно ты делаешь)
// Imperative const element = document.createElement('p'); element.appendText = 'example'; element.className = 'styles'; element.addEventListener('click', function(){} ); // Declarative <p className='styles' onClick={function(){}}>example</p>
Virtual DOM
- Виртуальный DOM представляет собой упрощенные Javascript объекты по сравнению с настоящим DOM.
Reconciliation
- Это процесс сверки старой и новой версий (снэпшота) Virtual DOM после изменения данных (например, массива, на основе которого отображаются элементы списка)
- На основе этой сверки, React производит точечные изменения в реальном DOM.
- В результате, пользователь видит обновление только измененного элемента списка, а не перерисовку всех элементов одновременно.
Basic hooks
useState
- it's a local state
- сравнивает предыдущее состоянии с новым и при необходимости вызывает ререндер той части UI которая от него зависит (в отличии от обычной переменной, которую мы вывели бы в JSX и изменили в event listener функции)
- correct approach to change state
setState(prev => prev + 1)
useState(compute())
you can pass function to initial state param, if you need to compute initial value once, not on every render
useRef
- можно юзать как useState, изменение которого не вызывает rerender
- можно как ссылку на dom element
useContext
- Нужен для избежания “props drilling”, когда мы вынуждены пробрасывать стейт через пропсы каждого уровня, даже там где этот стейт не нужен
- TODO: React context vs state manager libs (Redux vs Zustand vs React-Query vs React Context)
useCallback
- используется в дополнение к React.memo для функций передаваемых в дочернюю компоненту, чтобы сохранить между ререндарами родителя ту же ссылку
useMemo
use cases:
- аналогично useCallback, в дополнение к React.memo но для объектов, массивов и т.д.
- когда одно из состояний сложно вычисляется, а мы меняем другое состояние, при этом при ререндере пересчитывается сложное 1-ое
function formatData() { return data.map(item => { // difficult calculations (formatting, sorting, filtering) }) } const formattedData = useMemo(formatData, [data])
Optimisation
key
attribute
- Нужен для оптимизации обновления списка элементов, созданного с помощью метода
map
. - Если мы добавляем новый элемент в начало или середину массива,
React с использованием
key
сможет корректно сопоставить существующие элементы и обновить только изменённые, избегая полной перерисовки всех элементов.
React.lazy
- Нужен, когда хотим чтобы компонента не попадала в начальный бандл (собранный вебпаком файл из всех файлов проекта)
- Начальная загрузка будет быстрее (бандл меньше), но юзер будет ждать, когда воспользуется компонентом в lazy
- Такую компоненту нужно оборачивать в React.Suspect и в fallback указывать loader (бандл поделили на чанки)
child components rerender optimization
- Без использования PureComponent / shouldComponentUpdate / React.memo, компоненты перерисовываются каждый раз, когда их родитель перерисовывается, независимо от того, изменились ли их пропсы или нет.
// UserName will rerender every time when button clicked <> <button onClick={() => setState(prev => prev + 1)}>Add</button> <UserName name="John Doe" /> </>
PureComponent
- when we extend from
PureComponent
, it compares new and old props & state, preventing rerender if they are the same
shouldComponentUpdate(nextProps, nextState)
- does the same, but in this lifecycle method we can implement a custom comparison logic of props & state
HOC React.memo
- does the same, but for func components and only for props
- prevents rerender when props are the same
// won't rerender when parent rerenders, only when props.name is changed const UserName = React.memo((props) => { return <div>{props.name}</div> });
- !!! React.memo / PureComponent умеет делает только поверхностную сверку (примитивы и ссылки)
- Когда в пропс передан не примитивный тип данных, в дополнение к React.memo, такие данные нужно их обернуть в useMemo / useCallback
- Использование этих хуков сохраняет ссылки на функции и объекты между рендерами, что предотвращает ненужные перерисовки компонентов
const user = useMemo(() => ({ name: "John Doe" }), []); const handleClick = useCallback(() => setState(prev => prev + 1), []); return ( <> <UserName user={user} /> <button onClick={handleClick}>Add</button> </> )
Advanced hooks
useLayoutEffect
- Когда нужно сделать изменение стилей напрямую DOM дереве (ref.current.style.border = “1px solid black”)
- и хотим чтобы они применились до рендера - синхронно, а не после рендера - асинхронно, как в обычном useEffect
useImperativeHandle
- Когда нужно из родительской компоненты например очистить поля привязанные к локальному стейту дочерней
- Через ссылку в переданную из родителя через
forwardRef
в дочернуюю expose-ятся методы, в которых происходят действия доступные только в дочерней, а в родительской мы можем вызвать эти методы через ссылку
useTransition
- Используем когда есть сложный ререндеринг влияет на отзывчивость страницы.
- С помощью него можно приоритизировать состояние которое влияет на отзывчивость (текст инпут, переключение между табами, таймлайн инпут ).
- И понизить приоритетность изменения блокирующего состояния (результат пользовательского действия: рендеринг результатов фильтрация списка по инпуту, переключение контента в табе )
const [input, setInput] = useState('') const [list, setList] = useState([]) const [isPending, startTransition] = useTransition(); function handleChange (e) { setInput(e.target.value) // setting priority state like usually startTransition(() => { const filteredValue = data.filter(item => item) // some difficult filtering calculations setList(filteredValue) // setting less priority state in startTransition }) }
React-connected important libs
React-router-dom V6
TODO: Через роутер можно фетчить дату
Redux
- redux хранит state объектов в едином store
- чтобы изменить state нужно отправить action, action попадает в reducer, reducer описывает как state будет изменен
- изменения только с помощью ЧИСТОЙ функции reducer - иммутабельные, копируем и подменяем
- ACTION - plain object { type, payload }
- методы dispatch(action), getState(), subscribe(listener) принадлежит store (store.dispatch)
- mstp селектит нужный для компоненты стейт и отслеживает изменения в выбранных свойствах
- FLOW - call dispatch(action) -> reducer(current state, action) -> returns new state instance
Redux-toolkit
- Не используем ActionCreators,
- используем ThunkCreator.fulfilled/rejected успешные/неуспешные случаи выполнения санок
- настраиваем санки не на диспатч нужного AC, а на return нужных данных/rejectedWithValue(error)
Redux-thunk
- Middleware for the Redux, that allows to dispatch async actions (thunks)