Loading Routes trong React

Là một developer, khi chúng ta build app cho user trên internet, điều quan trọng nhất là bảo đảm tốc độ xử lí và phục vụ cho người dùng vẫn nhanh nhất có thể.

Khi build một React app, ta thường hay bắt gặp việc kích thước của ứng dụng bị tăng lên nhanh chóng bởi số lượng dependencies được sử dụng. Nó xảy ra khi một phần của app (hoặc route) có thể đã import một lượng lớn các components không cần thiết khi nó mới load lần đầu tiên, dẫn tới việc tăng thời gian load của app.

Vậy làm sao để chúng ta tiếp cận vấn đề này? Liệu có cách nào để chỉ load những thứ cần thiết và tránh một đống code thừa thãi?

Chúng ta có thể đạt điều đó nhờ vào Lazy Loading. Nó là một cách thức rất hay để tối ưu hóa website của bạn, nó thực hiện điều đó bằng cách chia code của bạn tại những điểm breakpoints, và sau đó load khi user tương tác và cần tới một đoạn code mới. Giúp cho tăng tốc độ load cho app cũng như rút bớt kích thước của ứng dụng do có nhiều đoạn code không hề được load.

Trong React, chúng ta lazy load components và routes bằng cách chia cắt code nhờ vào Webpack. Khi chia app của bạn thành những phần nhỏ và chỉ load những thứ cần cho page render.

Vậy thì hãy thử một React app thật đơn giản và xem thử cách chúng ta có thể lazy load routes.

Bootstrap một React app với create-react-app

Chúng ta sẽ sử dụng create-react-app CLI để bootstrap một React app mới. CLI, vốn được build bởi Facebook giúp developer bằng cách tạo ra một React app hoạt động trơn tru mà không cần bất cứ build configuration nào.

Cái đặt create-react-app tool với command sau:

npm install -g create-react-app

Sau khi qua trình cài đặt đã xong, bạn giờ đây đã có thể tạo một React app bằng command  create-react-app lazy-loading-routes

Nó sẽ tạo ra một new folder với tất cả file cần để chạy React app. Bạn giờ đã có thể đã có thể chạy mọi commands sau:

npm start
npm run build
npm test

 npm start command chạy app trong development mode, command npm run build sẽ build app trong folder  build, và  npm test command sẽ chạy test watcher trong interactive mode.

React app mà chúng ta build sẽ có routes/components sử dụng một hoặc hai React plugins.

Thiếu code splitting, toàn bộ React code và plugins sẽ bị bundled vào thành một JavaScript file, nhưng với code splitting, chỉ component/plugin sẽ cần để load.

Quay trở về việc build một app,  create-react-app CLI sẽ tạo ra một React app như đã nói trên và cho phép chúng ta bắt đầu build ngay lập tức.

Trước hết, hãy set up những routes đơn giản chúng ta sẽ cần tới cho React app. Cho routing, chúng ta sẽ sử dụng react-router. bạn có thể thêm react-router package vào app bằngnpm install react-router-dom trong terminal.

Sau khi quá trình cài đặt đã hoàn thành, chúng ta có thể bắt đầu với việc tạo những components, vốn sẽ đóng vai trò như các routes. Cho app này, chúng ta sẽ dùng 4 routes;  HomeMapsBlog và một route dành cho 404 page  NotFound.

Vào folder  src trong project directory và chạy commands sau:

mkdir Home Maps Blog NotFound

Nó sẽ tạo folder cho nhiều components khác nhau để có thể dùng về sau. Đây là một cách phân chia React app.

Trước khi chúng ta tạo components, hãy  edit App.js file và set up các route đơn giản trước. Mở  App.js file và edit những dòng code sau:

// Import React and Component
import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Route,
    Switch,
    Link
} from 'react-router-dom'

// Import the Home component to be used below
import Home from './Home/Home'
// Import the Maps component to be used below
import Maps from './Maps/Maps'
// Import the Blogs component to be used below
import Blog from './Blog/Blog'
// Import the NotFound component to be used below
import NotFound from './NotFound/NotFound'
 // Import CSS from App.css
import './App.css';
import createBrowserHistory from 'history/createBrowserHistory';

const history = createBrowserHistory();
class App extends Component {
    render () {
        return (
            <Router history={history}>
                <div>
                    <header className="header">
                        <nav className="navbar container">
                            <div className="navbar-brand">
                                <Link to="/">
                                    <span className="navbar-item">Lazy Loading Routes</span>
                                </Link>
                            </div>

                            <div className="navbar-end">
                                <Link to="/maps">
                                    <span className="navbar-item">Maps</span>
                                </Link>
                                <Link to="/blog">
                                    <span className="navbar-item">Blog</span>
                                </Link>
                            </div>
                        </nav>
                    </header>
                    <section className="content">
                        <Switch>
                            <Route exact path="/" component={Home} />
                            <Route path="/maps" component={Maps} />
                            <Route path="/blog" component={Blog} />
                            <Route path="*" component={NotFound} />
                        </Switch>
                    </section>
                </div>
            </Router>
        )
    }
}

export default App;

Trong dòng code trên, chúng ta imported React và Component module của nó bằng ES6 import, đồng thời bạn cũng cần  BrowserRouterRouteSwitch và  Link từ react-router. Trong  render() function, đầu tiên ta sẽ tạo ra view  cho user dùng để navigate các route khác nhau và sau đó  <Switch> component sẽ giữ những routes khác cũng như component phản ứng đến chúng.

 App.cssfile nên được edit với dòng code sau:

Hãy tiếp tục với components, vào Home folder và tạo ra những file sau: Home.js và  Home.css. Mở  Home.js file và edit những dòng code sau:

import React, { Component } from 'react'
import './Home.css'
import Button from '../NavButton/NavButton'

class Home extends Component {
    render () {
        return (
            <div className="container">
                <section className="section">
                    <div className="container">
                        <h1 className="title">Lazy Loading</h1>
                        <h2 className="subtitle">
                            A simple app to demonstrate how lazy loading routes in React works.
                        </h2>
                        <section className="bottom">
                            <Button name="Go to About page" link="/about" />
                            <Button name="Go to Blog page" link="/blog" />
                        </section>
                    </div>
                </section>

            </div>
        )
    }
}

export default Home

Trong đoạn mã trên, chúng ta đơn giản là tạo ra view cho Home component. Một Button  component được dùng, nó lấy  prop của name và  link. Chúng ta cũng import style từ  Home.css file. Hãy thử viết CSS cho file.

Tiếp theo là Maps route, bạn có thể hiểu map page sẽ đơn giản là hiển thị Google map của một vị trí sử dụng google-map-react React plugin. Vào  Maps folder và tạo những files tiếp theo:  Maps.js và  Maps.css. Mở Maps.js file và edit những code sau:

import React, { Component } from 'react'
import './Maps.css'
import GoogleMapReact from 'google-map-react';

const MapsView = ({ text }) => (
    <div style={{
        position: 'relative', color: 'white', background: 'red',
        height: 40, width: 60, top: -20, left: -30, textAlign: 'center', paddingTop: '5px'
    }}>
        {text}
    </div>
);

class Maps extends Component {
    static defaultProps = {
        center: {lat: 6.5244, lng: 3.3792},
        zoom: 11
    };

    render () {
        return (
            <div className="container">
                <p>This page is simply a page that shows a Google Map view of a location. Play around with the coordinates to get a different view</p>
                <div className="map-container">
                    <GoogleMapReact
                        defaultCenter={this.props.center}
                        defaultZoom={this.props.zoom}
                    >
                        <MapsView
                            lat={6.5244}
                            lng={3.3792}
                            text={'Your Location'}
                        />
                    </GoogleMapReact>
                </div>
            </div>
        )
    }
}

export default Maps

Trong những code trên, đầu tiên ta import  React, và Component module của nó. google-map-react plugin cũng sẽ được import. MapsView() function lấy tham số của text và để nó vào trong một  div.

Kế tiếp, ta có ES6 class tên làMaps với khả năng mở rộng component module từ react. Trong  Maps component, ta set giá trị  props bằng cách sử dụng object defaultProps và  render() function chứa view và  GoogleMapReact component. Nếu bạn muốn đọc thêm về plugin google-map-react bạn có thể vào đây xem.

Hãy thử viết CSS cho  Maps.css file. Mở file và viết code như sau:

Component tiếp theo là Blog component, vốn dùng một React plugin gọi là react-markdown để render markdown vào trong các pure React component. Mở  Blog folder và tạo  Blog.js file. Mở  Blog.js file và edit như sau:

import React, { Component } from 'react'
import ReactMarkdown from 'react-markdown'

class Blog extends Component {
    constructor(props) {
        super(props);

        this.state = {
            markdownSrc: [
                '# Lazy Loading Routes with React\n\nWhy do we need to lazy load routes?.\n\n* Reduce code bloat\n* Avoid loading all components at the same time ',
                '\n* React app loads faster',
                '\n* Load only the component that is needed and preload the others\n',
                '\n## A quote\n\n<blockquote>\n    A man who splits his code ',
                'is a wise man.\n</blockquote>\n\n## How about some code?\n',
                '```js\nimport React, { Component } from \'react\';\nimport asyncComponent from \'./AsyncComponent\'',
                '\n\nimport {\n' +
                '    BrowserRouter as Router,\n' +
                '    Route,\n' +
                '    Switch,\n' +
                '    Link\n' +
                '} from \'react-router-dom\'\n```\n\n\n'
            ].join(''),

            htmlMode: 'raw'
        };
    }
    render () {
        return (
            <div className="container">
                <ReactMarkdown source={this.state.markdownSrc} />
            </div>
        )
    }
}

export default Blog

Trong đoạn code trên, ta sẽ imported react-markdown plugin và dùng nó để render markdown trong  state.markdownSrc vào pure React component trong render() function.

Component cuối cùng là  NotFound route, vào  NotFound folder và tạo  NotFound.js file. Mở  NotFound.js file và edit đoạn code sau:

Ta dùng  Button component trong  Home route trên, và tạo ra một component mới. Trong  src folder, tạo một folder với tên là  NavButton, sau đó tạo một file  NavButton.js trong folder đó. Mở  NavButton.js file và edit như sau:

Trong code trên, ta đã tạo một functional stateless component để tạo view cho một nút. Component là một nút có chức năng giúp navigation trong React app, đạt được điều này là nhờ vào  react-router, vốn được import trên top của file.  Button component nhận vào hai  props;  link và name.  link prop được dùng để xác định route nào để navigate, và  nameprop được dùng để hiển thị đoạn text trong button.

Bây giờ thì bạn đã có thể chạy app để xem tiến triển của mình. Để xem app trong development mode, hãy dùng command npm start trong terminal của bạn và sẽ thấy một homepage tương tự như sau:

Giờ thì ta đã biết  app chạy tốt, hãy thử phân tích và xem app load toàn bộ JavaScript code mà chúng ta đã viết ra như thế nào. Chạy command  npm run build trong terminal để build app:

Bạn có thể thấy toàn bộ JavaScript code đều được nằm trong một file main.....js với kích thước khá nhỏ. Nhưng nếu ta tăng qui mô của app lên thì điều này sẽ cực kì rắc rối bởi kích thước của chúng cũng tăng lên chóng mặt. Do đó ta sẽ dùng tới code splitting.

Code Splitting

Đây chính là lí do vì sao mà ta trải qua mọi thứ. Làm cách nào để áp dụng code splitting trong React app? Đó là nhờ vào Webpack và bởi vì  create-react-app  đã được ship với Webpack, ta sẽ cần thêm config hay eject create-react-app.

Hãy thử xem routes setup mà chúng ta đã defined ở trên:

Với setup như vậy,  Switch component sẽ  render route phù hợp với path mà users navigate thông qua component trên. Vì ta đã import tất cả các component, điều đó có nghĩa là chúng đều sẽ load khi user đến một route nhất định, bao gồm cả những component không cần thiết.

Đó là lúc code splitting tỏa sáng. Code splitting giúp import components và chỉ load chúng khi cần và nhờ đó loại trừ những JavaScript code không cần thiết. Vậy làm cách nào để dùng nó?

Tạo một file tên là  AsyncComponent.js trong  src folder và edit như sau:

asyncComponent()function chọn một tham số, , getComponent vốn là một function sẽ import  một component được cho sẵn. Nó sẽ không được called cho đến khi mount lần đầu tiên. Trên componentWillMount, ta sẽ đơn giản call  getComponent function được pass và lưu loaded component trong state. Cuối cùng, ta render component nếu nó đã load hoàn toàn, còn không thì ta đơn giản render null.

Giờ đã xong  asyncComponent, hãy thay đổi cách import component trong  App.js file bằng cách import chúng  asyncComponent() function. Đoạn code dưới đây trong App.js file sẽ được thay bằng đoạn thứ 2 nếu bạn làm đúng.

Như vậy, kết quả cuối cùng của App.js sẽ trông như thế này:

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Route,
    Switch,
    Link
} from 'react-router-dom'

import NotFound from './NotFound/NotFound'
import './App.css';
import createBrowserHistory from 'history/createBrowserHistory';
import asyncComponent from './AsyncComponent'

const Home = asyncComponent(() =>
    import('./Home/Home').then(module => module.default)
)

const Maps = asyncComponent(() =>
    import('./Maps/Maps').then(module => module.default)
)

const Blog = asyncComponent(() =>
    import('./Blog/Blog').then(module => module.default)
)

const history = createBrowserHistory();

class App extends Component {
    render () {
        return (
            <Router history={history}>
                <div>
                    <header className="header">
                        <nav className="navbar container">
                            <div className="navbar-brand">
                                <Link to="/">
                                    <span className="navbar-item">Lazy Loading Routes</span>
                                </Link>
                            </div>

                            <div className="navbar-end">
                                <Link to="/maps">
                                    <span className="navbar-item">Maps</span>
                                </Link>
                                <Link to="/blog">
                                    <span className="navbar-item">Blog</span>
                                </Link>
                            </div>
                        </nav>
                    </header>

                    <section className="content">
                        <Switch>
                            <Route exact path="/" component={Home} />
                            <Route path="/maps" component={Maps} />
                            <Route path="/blog" component={Blog} />
                            <Route path="*" component={NotFound} />
                        </Switch>
                    </section>
                </div>
            </Router>
        )
    }
}

export default App;

Xin chúc mừng, bạn đã áp dụng thành công code splitting, hãy thử thực hiện một số phân tích và xem app chạy như thế nào bằng command  npm run build:

Bạn có thể thấy rằng code đã được chia thành nhiều phần khác nhau , và giờ React sẽ chỉ load những component cần cho path thay vì load mọi thứ.

Lời kết

Code splitting giúp cho việc code không bị phình bự lên. Nếu bạn muốn nhanh hơn nữa thì có thể dùng react-loadableplugin. Nó là một component cao cấp với khả năng load component với promise.

Nguồn: TopDev via scotch

Tham khảo các vị trí việc làm React hấp dẫn