Tăng tốc cho React

Về cơ bản React cung cấp kĩ thuật làm cho thời gian cập nhật lại UI tương ứng với mỗi thay đổi là nhỏ nhất. Hầu hết các ứng dụng thì React phản hồi rất nhanh đối với tương tác của người dùng khi ứng dụng không quá lớn hoặc phức tạp. Bên cạnh đó có một vài kĩ thuật để tăng tốc React

Tránh việc render lại ko cần thiết

Khi mà props hoặc state của component thay đổi. React sẽ quyết định việc có thay đổi DOM hay không bằng cách so sánh giá trị trả về hiện tại với giá trị trả về trước đó của phương thức render. Nếu chúng khác nhau React sẽ cập nhật lại DOM

Một vài trường hợp bạn cần tăng tốc độ phản hổi của trang, bạn có thể thay đổi việc một component có render lại không thông qua phương thức shouldComponentUpdate, nó được gọi trước khi tiến trình render lại (re-rendering) được chạy. Mặc định phương thức này luôn trả về giá trị là true

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

Nếu bạn chắc chăn một số trường hợp đặc biệt thì comopent của bạn không cần thiết phải render lại, bạn có thể trả về false cho phương thức shouldComponentUpdate để tránh việc component này render lại mất nhiều thời gian

shouldComponentUpdate(nextProps, nextState) {
  if (someConditions) {return false}
  return true
}

Trên đây là một ví dụ Như bạn thấy Component C1 là gốc, nếu component này thay đổi state hoặc props mặc định nó sẽ render lại kéo theo đó tất cả các component con của nó cũng render lại theo. Tuy nhiên do thay đổi này yêu cầu chỉ cần thay đổi component C3 và C6 là đủ. Như vậy khi cài đặt ta sẽ kiểm tra điều kiện sao cho phương thức shouldComponentUpdate của coponent C2, C7, C8 trả về false và C1, C3, C6 trả về true. Có thể bạn sẽ thắc mắc với component C4, C5 thì sao? Bởi vì component C2 đã không thay đổi nên kéo theo C4 và C5 cũng sẽ không thay đổi gì và phương thức shouldComponentUpdate của chúng cũng không được gọi.

Để hiểu chi tiết hơn ta có một ví dụ cụ thể như sau

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() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

Trong đoạn code này, phương thức shouldComponentUpdate chỉ trả về true trong trường hợp props.color hay state.count thay đổi. Nếu chúng ko thay đổi thì component này cũng không cần cập nhật lại. Nếu component của bạn trở nên phức tạp, bạn có thể dùng dến một pattern khác gọi là shallow comparison nó sẽ so sánh tất cả các thuộc tính của propsstate để quyết định xem component này có cần update hay không. Rất may React cũng đã cung cấp cho bạn một component gọi là React.PureComponent. Công việc rất đơn giản bạn chỉ cần kế thừa nó là được rồi. Ta có đoạn code tương ứng với code ở trên như sau

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

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

Hầu hết các trường hợp bạn có thể dùng React.PureComponent thay thế cho việc viết thủ công shouldComponentUpdate.

Đặc biệt chú ý là React.PureComponent sẽ không hoạt động đúng nếu bạn thay đổi state hoặc props một cách trực tiếp (mutated).

Vấn đề này thường dễ mắc phải với trường hợp dữ liệu khá phức tạp hoặc có cấu trúc cây (nested). Ta có một ví dụ minh họa cho việc này như sau

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>
    );
  }
}

Trong ví dụ trên bạn có thể thấy mỗi lần click vào button thì sẽ thêm một từ vào trong state.words. Tuy nhiên kết quả ta thấy sau mỗi lần click thì phần hiển thị vẫn không hề thay đổi. Có thể giải thích như sau:

Khi component WordAdder render lại trang thì gọi đến component ListOfWords. Tuy nhiên ListOfWords lại nhận props words là state.words của component WordAdder. Do chúng ta thay đổi gía trị state.words một cách trực tiếp nên khi gọi lại component ListOfWords với props words nó sẽ thấy props words này không có gì thay đổi so với lần trước bởi vì nó cùng là một đối tượng.

Sức mạnh của việc không thay đổi dữ liệu một cách trực tiếp

Để giải quyết vấn đề trên, bạn không nên thay đổi state một cách trực tiếp. Ta có thể sửa lại như sau

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

Trong ví dụ trên với array thì ta sẽ sử dụng phương thức concat thay thế cho push để không làm thay đổi gía trị của đối tượng.

Tương tự với trường hợp của object. Bạn có thể làm như sau

function updateColorMap(colormap) {
  colormap.right = 'blue';
  return colormap
}

Ta có thể sửa lại thành

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

Việc ko thay đổi dữ liệu trạng thái (state) một cách trực tiếp khiến cho dữ liệu của bạn trở nên rõ ràng hơn. Trạng thái cũ của đối tượng vẫn được giữ nguyên, như vậy ta có thể dễ dàng kiểm tra xem trạng thái mới có gì khác biệt so với trạng thái cũ, kiểm tra và bắt lỗi cũng dễ dàng hơn rất nhiều.

Cảm ơn bạn đã theo dõi bài viết !!!

Tham khảo

  1. React document

All Rights Reserved