Optimizing Performance trong ReactJS

Trong React, nó sử dụng những kĩ thuật thông minh nhằm tối thiểu số lượng các DOM được thực thi để update UI. Trong nhiều ứng dụng, việc sử dụng React tăng tốc độ UI mà không cần phải làm nhiều việc để tối ưu hóa performance. Tuy nhiên, vẫn có một số cách để tăng tốc độ cho ứng dụng của bạn

I, Use The Production Build

Nếu bạn đang gặp những vấn đề về performance trong ứng dụng React của bạn, hãy chắc chắn rằng bạn đang test với production-build được rút gọn:

  • Đối với việc tạo ứng dụng React, bạn cần chạy
    npm run build

và làm theo hướng dẫn

  • Đối với việc build một single-file, chúng ta nên yêu cầu production một version .min.js

  • Với trình duyệt, bạn cần run nó với

    NODE_ENV=production
  • Với Webpack, bạn cần add đoạn này đến plugins của production config:
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    new webpack.optimize.UglifyJsPlugin()

II, Avoid Reconciliation

React xây dựng và duy trì một đại diện bên trong của việc render UI. Nó bao gồm các phần tử React được return từ component của bạn. Những đại diện này giúp React tránh việc tạo các node DOM, và truy cập vào các phần tử đang tồn tại khi cần thiết, việc này có thể chậm hơn so với thực thi một đối tượng Javascript. Đôi khi, nó được biết đến như một DOM ảo, nhưng nó làm việc như một React bình thường.

Khi một props hoặc state của component thay đổi, React quyết định xem DOM thực sự nào cần thiết update bằng việc so sánh những phần tử được return mới so với cái cũ. Khi nó không giống nhau, React sẽ update DOM đó.

Trong một số trường hợp, component của bạn có thể tăng tốc bằng việc overriding một hàm lifecycle của state shouldComponentUpdate, Cái mà sẽ được trigger trước khi tiến trình re-rendering bắt đầu. Mặc định của nó là true, có thể update lại bằng:

    shouldComponentUpdate(nextProps, nextState) {
      return true;
    }

Nếu bạn chắc chắn rằng, trong một vài tính huống, component của bạn không cần được update, bạn có thể return false từ shouldComponentUpdate, để bỏ qua toàn bộ tiến trình rendering bao gồm việc gọi render() ở trên component này và bên dưới:

III, shouldComponentUpdate In Action

Đây là một subtree của các components. Với mỗi component, thành phần SCU(shouldComponentUpdate?) sẽ biểu thị giá trị trả về của shouldComponentUpdate, và thành phần vDOMEq(are virtual DOMs equivalent?) biểu thị liệu các phần tử React được render có tương đương với các phần tử cũ hay không?

1.png

Mỗi khi shouldComponentUpdate trả về false cho subtree của root tại điểm C2, React sẽ không render tại C2 nữa, và kéo theo đó nó sẽ không nhắc lại shouldComponentUpdate tại C4 cũng như C5.

Ở C1 và C3, shouldComponentUpdate trả về true, vì vậy React phải đi xuống từng cây con và kiểm tra chúng. Tại C6, shouldComponentUpdate trả về true, và các phần từ được render không tương đương cái cũ nên React phải update lại DOM trong trường hợp này.

Điều thú vị cuối cùng là trường hợp tại C8. React phải render component này, nhưng các phần tử React lại tương đương với giá trị cũ nên nó sẽ không phải update lại DOM.

IV, Examples

Giả sử Component của bạn chỉ thay đổi khi props.color hoặc state.count thay đổi, bạn có thể check shouldComponentUpdate:

    class CounterButton extends React.Component {
      constructor(props) {
        super(props);
        this.state = {count: 1};
      }

      shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
          return true;
        }
        if (this.state.count !== nextState.count) {
          return true;
        }
        return false;
      }

      render() {
        <button
          color={this.props.color}
          onClick={() => this.setState(state => ({count: state.count + 1}))}>
          Count: {this.state.count}
        </button>
      }
    }

Trong đoạn code trên, shouldComponentUpdate chỉ kiểm tra rằng nếu có bất kì thay đổi của props.color hay state.count. Nếu những giá trị đó không thay đổi, component không update. Nếu component phức tạp hơn, bạn có thể sử dụng pattern "shallow comparison" giữa các trường của props và state để xác định component nên được update không. Pattern được React cung cấp một helper để sử dụng các logic, được kế thừa từ React.PureComponent. Do đó, đoạn code sẽ được biểu đạt một cách đơn giản hơn:

    class CounterButton extends React.PureComponent {
      constructor(props) {
        super(props);
        this.state = {count: 1};
      }

      render() {
        <button
          color={this.props.color}
          onClick={() => this.setState(state => ({count: state.count + 1}))}>
          Count: {this.state.count}
        </button>
      }
    }

Hầu hết, bạn có thể dùng React.PureComponent để thay thế việc viết shouldComponentUpdate. Nó chỉ là một "shallow comparison", vì vậy bạn không thể sử dụng nó nếu props hoặc state được thay đổi theo những cách mà shallow comparison không support.

Điều này có thể là một vấn đề đối với các cấu trúc dữ liệu phức tạp. Ví dụ, bạn muốn một ListOfWords component để render một danh sách các từ riêng biệt, với một component cha là WorldAdder, cái mà bạn có thể click vào một button để add một từ đến list. Đoạn code này sẽ không đúng:

    class ListOfWords extends React.PureComponent {
      render() {
        return <div>{this.props.words.join(',')}</div>;
      }
    }

    class WordAdder extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          words: ['marklar']
        };
        this.handleClick = this.handleClick.bind(this);
      }

      handleClick() {
        // This section is bad style and causes a bug
        const words = this.state.words;
        words.push('marklar');
        this.setState({words: words});
      }

      render() {
        return (
          <div>
            <button onClick={this.handleClick} />
            <ListOfWords words={this.state.words} />
          </div>
        );
      }
    }

Vấn đề này là PureComponent sẽ làm một so sánh đơn giản giữa giá trị cũ và mới của this.próp.worlds. Mỗi khi đoạn code này thay đổi mảng worlds bằng method handleClick của WordAdder, giá trị cũ và mới của this.props.words sẽ so sánh với nhau, ListOfWords sẽ không được update mặc dù mảng words đã thay đổi.

V, The Power Of Not Mutating Data

Cách đơn giản nhất để tránh vấn đề này là tránh việc thay đổi giá trị của props hay state. Ví dụ, method handleClick ở trên có thể được viết lại bằng việc sử dụng concat:

    handleClick() {
      this.setState(prevState => ({
        words: prevState.words.concat(['marklar'])
      }));
    }

ES6 đã support cú pháp spread cho mảng nên bạn có thể viết lại theo cách dễ dàng hơn:

    handleClick() {
      this.setState(prevState => ({
        words: [...prevState.words, 'marklar'],
      }));
    };

VI, Using Immutable Data Structures

Immutable.js là một cách khác để giải quyết vấn đề này. Nó cung cấp các collection immutable, persistent:

  • Immutable: Mỗi khi được tạo, collection không thể thay đổi vào bất cứ lúc nào.

  • Persistent: collection mới có thể được tạo từ collection trước. Collection cũ vấn valid sau khi collection mới được tạo.

  • Structural Sharing: collection mới được tạo sử dụng các cấu trúc của các collection cũ nếu có thể, giảm việc copy đến tối thiểu nhằm cải thiện performance.

Chi tiết hơn về Immutable.js, có thể tham khảo tại:

https://github.com/facebook/immutable-js

Nguồn: https://facebook.github.io/react/docs/optimizing-performance.html