Optimizing React Performance with Stateless Functional Components

React Component

Trong React, chúng ta xây dựng trang web sử dụng những thành phần (component) nhỏ và ghép chúng lại. Chúng ta có thể tái sử dụng một component ở nhiều nơi, với các trạng thái hoặc các thuộc tính khác nhau, trong một component lại có thể chứa thành phần khác. Khái niệm component trong React là một trong những thành phần quan trọng nhất của React. Chúng ta đã biết có ít nhất 3 cách để tạo 1 component trong React:

  • Sử dụng React.createClass
const React = require('react');
export const Cell = React.createClass({
    render() {
        const cls = this.props.highlighted ? 'highlight' : '';
        return (
            <span className={cls} onClick={this.props.userSelected}>
                {this.props.value}
            </span>
        );
    }
});
  • Sử dụng ES2015 Class Component
import React, { Component } from 'react'

class Cell extends Component {
    render() {
        const { value, highlighted, userSelected } = this.props;
        const cls = highlighted ? 'highlight' : '';
        return (
            <span className={cls} onClick={event => { userSelected() }}>
                {value}
            </span>
        );
    }
}
  • Sử dụng Stateless Functional Component
const Cell = ({ value, highlighted, userSelected }) => {
    const cls = highlighted ? 'highlight' : '';
    return (
        <span className={cls} onClick={event => { userSelected() }}>
            {value}
        </span>
    );
}

Ngoài ra, từ Stateless Functional Component chúng ta có thêm Presentational Component:

const Cell = ({ value, highlighted, userSelected }) => (
    <span className={highlighted ? 'highlight' : ''} onClick={event => { userSelected() }}>
        {value}
    </span>
);

Optimize Component

Giả sử chúng ta có một mảng 1000 phần tử chứa các con số [1...1000]. Chúng ta cần in 1000 số này ra, mỗi số sẽ là 1 Cell ở trên. Khi click vào Cell nào thì Cell ấy sẽ được highlight;

import React from 'react';
import Cell from './Cell';

class Board extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            selectedValue: 0,
            start: 1,
            end: 1000
        }
    }

    cellClickHandler(selectedValue) {
        this.setState({selectedValue});
    }

    render() {
        const {start, end, selectedValue} = this.state;
        let cells = [];
        for (let i = start; i <= end; i++) {
            cells.push(
                <Cell key={i} value={i} userSelected={this.cellClickHandler} highlighted={i === selectedValue}/>
            );
        }

        return (
            <div className='board'>
                {cells}
            </div>
        );
    }
}

export default Board;

Khá là đơn giản. Việc set highlight cho 1 Cell chỉ tác động tới 2 Cell (1 Cell đang được highlight và 1 Cell chuẩn bị được highlight). Tức là chúng ta chỉ cần render lại cho 2 Cell này. Tuy nhiên với 4 kiểu định nghĩa Cell ở trên, chúng ta vẫn phải render lại 1000 Cell mỗi lần click chứ không phải chỉ là 2 Cell.

May mắn cho chúng ta là React cung cấp phương thức cho phép chúng ta can thiệp vào quá trình render lại của 1 component: shouldComponentUpdate(nextProps, nextState).

shouldComponentUpdate(nextProps, nextState) {
    // Skips render() if returns false
    // If shouldComponentUpdate returns false, then render() will be completely skipped until the next state change.
    // In addition, componentWillUpdate and componentDidUpdate will not be called.
    // Hàm này thực hiện khi state và props thay đổi
    // Hàm này sẽ trả về kết quả true/false, bạn sẽ cần sử dụng đến hàm này để xử lý xem có cần update component không
    return true;
}

Tuy nhiên không phải lúc nào chúng ta cũng có thể sử dụng shouldComponentUpdate, chỉ có 2 cách tạo component đầu tiên mới có thể sử dụng được nó, React.createClassES2015 Class.

shouldComponentUpdate(nextProps) {
    return nextProps.value !== this.props.value || nextProps.highlighted !== this.props.highlighted
}

Đối với stateless functional components chúng ta không thể sử dụng được nó (chính xác hơn là không có để mà dùng).

Note: Từ React 15.3, chúng ta có thêm 1 kiểu component nữa, đó chính là PureComponent. PureComponent dựa trên phương thức shouldComponentUpdate để quyết định xem nó có cần phải render lại component hay không dựa trên việc so sánh this.propsnextProps. Tuy nhiên, việc so sánh này chỉ ồn thỏa khi props đơn giản, khi props phức tạp việc so sánh có thể dẫn đến sai sót và component vẫn được render lại.

Stateless Functional Components

Như đã nói ở trên, việc sử dụng stateless functional components khiến chúng ta không thể can thiệp vào quá trình re-render của nó bằng việc sử dụng shouldComponentUpdate. Vậy nếu chúng ta vẫn muốn dùng Stateless Functional Components và vẫn có thể can thiệp được vào quá trình render của components thì sao?

Một trong các cách có thể giải quyết được vấn đề trên đó chính là sử dụng higher-order components, tuy nhiên việc này khiến chúng ta phải tạo thêm 1 component khác cho mỗi stateless components. Khá là mất công. Chúng ta không muốn viết thêm 1 component chỉ wrap 1 component khác. Vậy hãy sử dụng recompose.

Recompose is a React utility belt for function components and higher-order components. Think of it like lodash for React.

Cài đặt recompose khá là đơn giản

npm install recompose --save
# hoặc
yarn add recompose

recompose cung cấp cho chúng ta khá nhiều phương thức hay khi xử lý 1 stateless functional component. Nó cho phép chúng ta tạo ra defaultProps hay initialState cho stateless component và quan trọng hơn nữa là nó cung cấp các phương thức để optimize rendering performance.

  • pure: chức năng tương tự với PureComponent đã nói ở trên.
  • onlyUpdateForKeys: nó chỉ so sánh các keys mà chúng ta cung cấp và render lại component khi các keys đó thay đổi giá trị.
import {pure, onlyUpdateForKeys} from 'recompose';

// Cách 1: sử dụng trong quá trình khai báo component
// sử dụng pure
export default const Cell = pure(({ value, highlighted, userSelected }) => (
    <span className={highlighted ? 'highlight' : ''} onClick={event => { userSelected() }}>
        {value}
    </span>
));

// sử dụng  onlyUpdateForKeys
const enhance = onlyUpdateForKeys(['value', 'isSelected']);
export default const Cell = enhance(({ value, highlighted, userSelected }) => (
    <span className={highlighted ? 'highlight' : ''} onClick={event => { userSelected() }}>
        {value}
    </span>
));

//Cách 2: Sử dụng sau khi khai báo component
export default pure(Cell);
export default enhance(Cell);

Nguồn tham khảo: https://www.sitepoint.com/optimizing-react-performance-stateless-components/

All Rights Reserved