5 приемов по разделению «бандла» и «ленивой» загрузке компонентов в React

Kate

Administrator
Команда форума
Разделение Javascript-кода на несколько файлов называется разделением «бандла» или сборки (bundle splitting). Это позволяет загружать только тот код, который который используется приложением в данный момент, другие части загружаются по необходимости (по запросу пользователя).


Распространенные случаи разделения сборки и «ленивой» или отложенной загрузки (lazy loading) включают в себя следующее:


  • Загрузка дополнительного кода при переходе пользователя к новому представлению (view — слой, отвечающий за визуальное отображение)
  • Загрузка такого кода может быть связана с определенным действием, таким как прокрутка или нажатие кнопки
  • Также можно реализовать предварительную загрузку определенных ресурсов, которые представляют потенциальный интерес для пользователя
  • Это приводит к тому, что когда пользователь захочет получить доступ к определенной функциональности, она уже будет готова

    59639feedd5d39606912d761e9fbb5f1.jpg

    1. Динамический импорт с помощью Webpack​


Webpack позволяет загружать модули (компоненты) динамически во время выполнения кода. Рассмотрим пример:


import { useState } from 'react'

function MainComponent() {

const [isModalDisplayed, setModalDisplayed] = useState(false)

const [ModalComponent, setModalComponent] = useState(null)

const loadModalComponent = async () => {

const loadResult = await import('./components/Modal.js')

setModalComponent(() => loadResult.default)

}

return (

<div>

{isModalDisplayed && ModalComponent ? <ModalComponent /> : null}

<button

onClick={() => {

setModalDisplayed(true)

loadModalComponent()

}}

>

Load Modal Component

</button>

</div>

)

}


Динамический импорт может использоваться в любом месте кода. Такой импорт сообщает Webpack о необходимости создания отдельной сборки для конкретного компонента. Поскольку модальное окно отображается только когда пользователь нажимает кнопку, мы можем отложить загрузку соответствующего кода.


Динамический импорт позволяет каждому компоненту выступать в роли микрофронтенда (microfrontend).


2. Split API для загрузки React-компонентов​


Пакет fusion-react предоставляет интерфейс split, компонент-обертку для отображения различных компонентов во время загрузки сборки:


  • Резервного компонента при возникновении ошибки
  • «Настоящего» компонента после загрузки сборки

Рассматриваемый интерфейс облегчает задачу разделения React-компонентов, позволяя избежать повторения шаблонного кода.


import { Link, Switch, Route } from 'react-router-dom'

import { split } from 'fusion-react'

const Loading = () => <div>Loading...</div>

const Error = () => <div>Error</div>

const Hello = split({

load: () => import('./components/hello.js'),

Loading,

Error,

})

const Root = () => (

<>

<div>

<ul>

<li>

<Link to='/'>Home</Link>

</li>

<li>

<Link to='/hello'>Hello</Link>

</li>

</ul>
</div>

<Switch>

<Route path='/' exact component={Home} />

<Route path='/hello' component={Hello} />

</Switch>

</>

)


Разделение компонентов верхнего уровня на основе маршрутов — еще одна техника оптимизации, способ уменьшения времени загрузки приложения.


В целом, отложенная загрузка кода до запроса пользователем страницы по конкретному маршруту — это хорошая идея.


Интерфейс split в приведенном примере откладывает загрузку компонента Hello до того момента, когда пользователь перейдет по соответствующему маршруту. Загружаемый компонент указывается в свойстве load.


В данном случае мы также имеем возможность использовать динамический импорт.


Прим. пер.: существуют более специализированные и популярные решения для ленивой загрузки React-компонентов, например, react-loadable или react-lazyload.


3. Создание «вендорного бандла» (vendor bundle)​


Прежде всего, вы можете спросить, для чего нам это нужно? Основная идея заключается в том, что в вендор помещается (выносится) код, который изменяется намного реже, чем код разрабатываемого приложения. Это позволяет использовать эффективные техники по кэшированию кода для повышения скорости загрузки страниц.


Вот как можно извлечь вендорный бандл из директории node_modules:


const path = require('path')

module.exports = {

entry: path.resolve(__dirname, 'src/index.js'),

output: {

path: path.resolve(__dirname, 'dist'),

filename: '[name].[contenthash].js',

},

}


Если после этого вы запустите сборку (yarn build или npm run build), то увидите что-то вроде этого:


⬡ webpack: Build Finished

⬡ webpack: assets by status 128 KiB [emitted]

asset 935.js 124 KiB [emitted] [minimized] (id hint: vendors) 2 related assets

asset main.js 3.24 KiB [emitted] [minimized] (name: main) 1 related asset

asset index.html 267 bytes [emitted]

assets by status 7.9 KiB [compared for emit]

asset main.css 7.72 KiB [compared for emit] (name: main) 1 related asset

asset 34.js 187 bytes [compared for emit] [minimized] 1 related asset

Entrypoint main 135 KiB (326 KiB) = 935.js 124 KiB main.css 7.72 KiB main.js 3.34 KiB 3 auxiliary assets

...

webpack 5.5.0 compiled successfully in 4856 ms


4. Создание нескольких вендорных бандлов​


Обычно, все модули объединяются в один вендорный бандл.


Знаете ли вы, что мы можем создать несколько таких бандлов?


Например, поскольку React не будет меняться между сборками, имеет смысл закэшировать его в виде отдельного пакета в случаях, когда вопрос производительности является критически важным.


Файл с настройками Webpack принимает свойство optimization, позволяющее разделять вендорный бандл:


module.exports = {

splitChunks: {

chunks: 'async',

cacheGroups: {

default: {

minChunks: 2,

reuseExistingChunk: true,

},

vendor_react: {

test: /.*\/node_modules\/react\/index\.js/,

name: 'vendor-react',

chunks: 'initial',

enforce: true,

},

},

},

}


После этого вендорный бандл будет разделен на client-vendor.js и clietn-vendor-react.js.


5. Ленивая загрузка компонентов с помощью React.lazy()​


React.lazy() — это функция, позволяющая рендерить динамически импортируемые компоненты как обычные компоненты.


Обычный импорт:


import MyComponent from './MyComponent'


Динамический импорт с помощью React.lazy():


const OtherComponent = React.lazy(() => import('./OtherComponent')


Компоненты, загружаемые с помощью React.lazy(), должны быть обернуты в компонент Suspense, который позволяет отображать резервный контент (например, индикатор загрузки) до полной загрузки импортируемого компонента:


import { lazy, Suspense } from 'react'

const OtherComponent = lazy(() => import('./OtherComponent'))

function MyComponent() {

return (

<>

<Suspense fallback={<div>Loading...</div>}>

<OtherComponent />

</Suspense>

</>

)

}


Проп fallback принимает любой элемент (компонент). Компонент Suspense может быть помещен на любом родительском по отношению к «ленивому» компоненту уровне.


Suspense может оборачивать как отдельный компонент, так и группу компонентов:


import { lazy, Suspense } from 'react'

const OtherComponent = lazy(() => import('./OtherComponent'))

const AnotherComponent = lazy(() => import('./AnotherComponent'))

function MyComponent() {

return (

<>

<Suspense fallback={<div>Loading...</div>}>

<section>

<OtherComponent />

<AnotherComponent />

</section>

</Suspense>

</>

)

}


Заключение​


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


Это, безусловно, положительно повлияет на пользовательский опыт работы с приложением, поскольку оно станет более быстрым и отзывчивым.


Также не стоит забывать о том, что загруженный JavaScript-код, должен быть разобран и выполнен, что также требует некоторого времени и вычислительной мощности.


Источник статьи: https://habr.com/ru/company/macloud/blog/559108/
 
Сверху