Варианты оптимизации кеширования в React
Хуки React для мемоизации
Содержание статьи:
Мемоизация — это функция, предоставляемая самим React. Как мы знаем, React создает новые ссылки при каждом повторном рендеринге. Если ваш компонент содержит обширные расчеты, они будут рассчитываться при каждом повторном рендеринге, даже если результат не изменится.
Чтобы минимизировать нагрузку на ЦП, избегая ненужных нагрузок, React предоставляет два хука, которые помогают в мемоизации. Хуки следуют процессу, в котором результаты кэшируются в памяти и возвращаются без повторных вычислений, когда мы имеем те же входные данные. В случае отличия данных, которые поступают на вход кеш становится недействительным.
useMemo()
useMemo() — это хук, предоставляемый React для мемоизации, который помогает сохранять кэшированные значения для одних и тех же предоставленных значений. Он отслеживает ввод и возвращает ранее выполненный результат.
Давайте посмотрим на пример. Предположим, что нам нужно добавить два огромных числа в компонент со следующей функцией:
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас! JavaScript const addTwoHugeNumbers=(a,b)=>{ return a+b }
123 | const addTwoHugeNumbers=(a,b)=>{ return a+b} |
Написанная выше функция сильно нагружает ЦП, и поэтому ее следует вычислять только при изменении значений a и b. Однако по умолчанию она будет запускаться при каждом повторном рендеринге.
С помощью useMemo() мы можем сохранить результат для определенных значений, то есть функция не будет вычислять, и мы получим ранее рассчитанный результат напрямую:
JavaScript const memoizedValue = useMemo(() => addTwoHugeNumbers(a, b), [a, b])
1 | const memoizedValue = useMemo(() => addTwoHugeNumbers(a, b), [a, b]) |
Значение хранится в memoizedValue. Мы передали массив зависимостей в useMemo, который сообщает ему, когда запускать расчет снова. В нашем случае он будет запущен при изменении любого из значений.
UseCallback()
С useCallback() мы также получаем возможность мемоизирования, но эта функция работает по-другому. useCallback() не запоминает значение, а вместо этого запоминает предоставленную ему функцию обратного вызова. Давайте рассмотрим небольшой пример:
JavaScript const increment = (() => { setCount(count + 1); });
123 | const increment = (() => { setCount(count + 1);}); |
При использовании useCallback() приведенная выше функция выглядит как код ниже:
JavaScript const increment = useCallback(() => { setCount(count + 1); }, [count]);
123 | const increment = useCallback(() => { setCount(count + 1);}, [count]); |
useCallback() запомнит функцию увеличения, выполняющуюся только при изменении данной зависимости. Она не отслеживает ввод или значение, возвращаемое функцией.
Ленивая загрузка компонентов React
Ленивая загрузка (Lazy loading) в React отрисовывает необходимые компоненты заранее и откладывает загрузку несущественных компонентов на конец.
Этот подход настоятельно рекомендуется для повышения производительности, особенно в больших приложениях. В React есть встроенные опции для отложенной загрузки компонентов.
Мы создали компонент с именем Artists и хотим, чтобы он загружался с помощью lazy load. Можно сделать это следующим образом:
JavaScript import { lazy } from ‘react’;
1 | import { lazy } from ‘react’; |
Сначала мы импортируем lazy из ‘react’ и используем его, как показано ниже:
JavaScript const Artists = React.lazy(() => import(‘./Artists’));function App() { return ( <div> <Artists /> </div> ); }
1234567 | const Artists = React.lazy(() => import(‘./Artists’));function App() { return ( <div> <Artists /> </div> );} |
useRef()
Мы знаем, что всякий раз, когда мы используем useState() в компоненте, он вызывает повторный рендеринг при изменении состояния. Чтобы отслеживать состояние без повторного рендеринга, React представил хук useRef().
Есть несколько сценариев, в которых useState() может оказаться неподходящим решением для вашего приложения. useRef() идеально подходит для ситуаций, когда нам нужно состояние, которое не вызывает повторного рендеринга и не влияет на видимую информацию, отображаемую компонентом. Например, вы можете использовать его для подсчета рендеров:
JavaScript function App() { const [foo, setFoo] = React.useState(false) const counter = React.useRef(0) console.log(counter.current++) return ( <button onClick={() => setFoo(f => !f)} > Click </button> ) } ReactDOM.render(<React.StrictMode><App /></React.StrictMode>, document.getElementById(‘mydiv’))
12345678910 | function App() { const [foo, setFoo] = React.useState(false) const counter = React.useRef(0) console.log(counter.current++) return ( <button onClick={() => setFoo(f => !f)} > Click </button> )} ReactDOM.render(<React.StrictMode><App /></React.StrictMode>, document.getElementById(‘mydiv’)) |
В приведенном выше коде у нас есть простой переключатель, который повторно отображает компонент. counter — это изменяемая ссылка, сохраняющая свое значение. Мы можем сделать то же самое с useState(), но это вызовет два рендера для каждого переключения.
Кэшированные селекторы Redux
Селекторы — это просто функции, которые используются для выборки данных из большего пула данных. В React селекторы широко используются для получения значений из хранилища Redux. Селекторы чрезвычайно полезны и мощны, но у них есть свои недостатки.
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
В React Redux есть хук useSelector(), который используется для получения состояния из хранилища. Проблема useSelector() заключается в том, что он запускается каждый раз при рендеринге компонента. useSelector() может быть идеальным в некоторых случаях, но не всегда. В большинстве случаев — данные, возвращаемые селекторами, не меняются, что делает вычисления ненужными. Давайте рассмотрим пример:
JavaScript import React, {useEffect,useState} from ‘react’ import {useSelector, useDispatch} from ‘react-redux’ import {getPosts} from ‘./postActions’export const List=()=>{ Const [toggle, setToggle]=useState(false) const myPosts=useSelector(state=>state.posts) const dispatch=useDispatch() return( <div> {myPosts.map(post=><p>{posts}<p/>)} <button type=»button» onClick={()=>{setToggle(!toggle)}} >Click Me!</button> <div/> ) }
12345678910111213 | import React, {useEffect,useState} from ‘react’import {useSelector, useDispatch} from ‘react-redux’import {getPosts} from ‘./postActions’export const List=()=>{ Const [toggle, setToggle]=useState(false) const myPosts=useSelector(state=>state.posts) const dispatch=useDispatch() return( <div> {myPosts.map(post=><p>{posts}<p/>)} <button type=»button» onClick={()=>{setToggle(!toggle)}} >Click Me!</button> <div/> )} |
В приведенном выше коде мы меняем состояние переключения, и компонент будет отображаться каждый раз, когда мы это делаем. Хук useSelector() также будет работать, даже если сообщения в нашем хранилище Redux не меняются.
Чтобы решить проблему, мы кэшируем результаты функции селектора. Несмотря на то, что для этого нет встроенных решений React, у нас есть много сторонних библиотек, которые позволяют нам создавать кешированные селекторы. Давайте воспользуемся Reselect, известным решением для кеширования селекторов.
Reselect
Reselect- популярная библиотека для создания мемоизированных селекторов. Вы можете установить ее в свой проект с помощью следующей команды:
JavaScript yarn add reselect
1 | yarn add reselect |
Мы можем использовать Reselect следующим образом:
JavaScript import { createSelector } from ‘reselect’ import React, {useEffect,useState} from ‘react’ import {useSelector, useDispatch} from ‘react-redux’ import {getPosts} from ‘./postActions’export const List=()=>{ Const [toggle, setToggle]=useState(false) const myPosts = createSelector(state=>state.posts) const dispatch=useDispatch() return( <div> {myPosts.map(post=><p>{posts}<p/>)} <button type=»button» onClick={()=>{setToggle(!toggle)}} >Click Me!</button> <div/> ) }
1234567891011121314 | import { createSelector } from ‘reselect’ import React, {useEffect,useState} from ‘react’import {useSelector, useDispatch} from ‘react-redux’import {getPosts} from ‘./postActions’export const List=()=>{ Const [toggle, setToggle]=useState(false) const myPosts = createSelector(state=>state.posts) const dispatch=useDispatch() return( <div> {myPosts.map(post=><p>{posts}<p/>)} <button type=»button» onClick={()=>{setToggle(!toggle)}} >Click Me!</button> <div/> )} |
В приведенном выше коде мы импортировали createSelector из Reselect, который принимает селектор и возвращает его мемоизированную версию. В мемоизированной версии компонент не будет вычислять значение селекторов даже после тысяч повторных отрисовок, если не изменится значение postReducer. CreateSelector оказался отличным решением для решения проблем с производительностью в более крупных приложениях.
Оптимизация вызовов API с помощью React Query
React по-своему обрабатывает асинхронные операции, что иногда является проблемой для разработчиков. Обычным шаблоном для асинхронных операций является выборка данных сервера в useEffect Hook, которая запускается при каждом рендеринге и каждый раз извлекает новые данные, даже если на сервере нет изменений.
С другой стороны, React Query кэширует данные и сначала возвращает их перед вызовом, но если новые данные, возвращаемые сервером, совпадают с предыдущими, React Query не будет повторно отображать компонент. Мы можем использовать React Query следующим образом:
JavaScript import React from ‘react’ import {useQuery} from ‘react-query’ import axios from ‘axios’async function fetchPosts(){ const {data} = await axios.get(‘https://jsonplaceholder.typicode.com/posts’) return data } function Posts(){ const {data, error, isError, isLoading } = useQuery(‘posts’, fetchPosts) // first argument is a string to cache and track the query result if(isLoading){ return <div>Loading…</div> } if(isError){ return <div>Error! {error.message}</div> } return( <div className=’container’> <h1>Posts</h1> { data.map((post, index) => { return <li key={index}>{post.title}</li> }) } </div> ) } export default Posts
123456789101112131415161718192021222324252627 | import React from ‘react’import {useQuery} from ‘react-query’import axios from ‘axios’async function fetchPosts(){ const {data} = await axios.get(‘https://jsonplaceholder.typicode.com/posts’) return data} function Posts(){ const {data, error, isError, isLoading } = useQuery(‘posts’, fetchPosts) // first argument is a string to cache and track the query result if(isLoading){ return <div>Loading…</div> } if(isError){ return <div>Error! {error.message}</div> } return( <div className=’container’> <h1>Posts</h1> { data.map((post, index) => { return <li key={index}>{post.title}</li> }) } </div> )} export default Posts |
Фрагменты React
Если вы разработчик React, вы, вероятно, сталкивались с ошибкой, которая требует обернуть компонент родительским div. Если дополнительный div в вашем компоненте не нужен, нет смысла его добавлять. Например, если у вас есть тысяча компонентов в приложении React, у вас будет тысяча дополнительных div, которые могут быть тяжелыми для DOM. Чтобы этого избежать, React дает вам возможность использовать фрагменты:
JavaScript const Message = () => { return ( <React.Fragment> <p>Hello<p/> <p>I have message for you<p/> </React.Fragment> ); };
12345678 | const Message = () => { return ( <React.Fragment> <p>Hello<p/> <p>I have message for you<p/> </React.Fragment> );}; |
Приведенный ниже фрагмент кода точно такой же, как и код выше, с использованием <> в качестве ярлыка для React.Fragment:
JavaScript const Message = () => { return ( <> <p>Hello<p/> <p>I have message for you<p/> </> ); };
12345678 | const Message = () => { return ( <> <p>Hello<p/> <p>I have message for you<p/> </> );}; |
При любом подходе вы избегаете добавления лишнего div, что приводит к уменьшению разметки DOM, повышению производительности рендеринга и сокращению накладных расходов на память.
Виртуальные списки React
Часто нам нужно отображать большие списки в браузере; это требует больших затрат ресурсов браузера, потому что он должен создавать новые узлы и рисовать их все на экране.
Чтобы сделать этот процесс эффективным, у нас есть возможность использовать виртуальные списки. Виртуальный список отображает только несколько элементов по мере необходимости, просто заменяя их, когда пользователь динамически прокручивает элементы.
Визуализация выполняется быстрее, чем изменение модели DOM, поэтому вы можете быстро отображать тысячи элементов списка с помощью виртуального списка. React-virtualized — отличная библиотека, которая содержит компоненты для визуализации виртуальных списков.
Функциональные компоненты
React начинался с использования базовых компонентов класса, однако теперь рекомендуется использовать функциональные компоненты из-за их легкости. Функциональные компоненты — это в основном функции, которые создавать намного быстрее, и их легче минимизировать, уменьшая размер пакета.
Заключение
В этом руководстве мы рассмотрели несколько различных решений для оптимизации управления кешем в приложениях React, таких как мемоизация, кешированные селекторы, отложенная загрузка, фрагменты React, виртуальные списки и функциональные компоненты. Каждый из этих методов может улучшить ваше приложение, уменьшив количество ненужных компонентов отрисовки, уменьшив накладные расходы и увеличив скорость.
Правильное решение будет зависеть от потребностей вашего индивидуального проекта, но, надеюсь, эта статья помогла вам познакомиться с доступными вариантами.
Автор: Kasra
Источник: webformyself.com