Dùng Imutable.js để tránh việc render lại ở React component

Trong bài này chúng ta sẽ cùng khảo sát xem cách React render component và cách dùng Immutable.js để việc render này được hiệu quả nhất. Chẳng hạn bạn muốn render một list tên người dùng.

Ta có thể có data và những component sau:

// Mutable version
let data = [
  { value: 1, name: 'Joe' }, 
  { value: 2, name: 'John'},
  { value: 3, name: 'Jacob'}
];

// User component
class UserList extends React.Component {
  
  constructor (props) {
    super(props)
    this.state = { data: data };
  }

  render () {
    return <div>{this.state.data.map(user => <User user={user} /> )}</div>;
  }
  }
  
  // User item component
  class User extends React.Component {
       
  render () {
    let user = this.props.user;
    console.log('render ' + user.name + ': ' + user.value);
    return <div>{user.name} - {user.value}</div>;    
  }
}
React.render(<UserList />, document.getElementById('root'));

Ví dụ trên cho ra output sau: Joe - 1 John - 2 Jacob - 3

Giả dụ bạn muốn thay đổi list user này sau khi đã được render, ta có thực hiện với life cycle method componentDidMount() ở UserList component.

componentDidMount () {
    setTimeout(() => {
      console.log('Adding Data');
      this.state.data.push({ value: 100, name: 'Bill'});
      this.setState({ data: this.state.data });
    }, 1000);

    setTimeout(() => {
      console.log('Updating Data');
      this.state.data[3].value = 200;
      this.setState({ data: this.state.data });
    }, 2000);
}

Out put: Joe - 1 John - 2 Jacob - 3 Bill - 200 List user được add thêm một item { value: 100, name: 'Bill'} sau giây thứ nhất và item này thay đổi giá trị thành { value: 200, name: 'Bill'} sau giây thứ hai kể từ khi user list render lần đầu tiên. Bật tab console lên ta sẽ thấy log như sau:

"render Joe: 1"
"render John: 2"
"render Jacob: 3"
"Adding Data"
"render Joe: 1"
"render John: 2"
"render Jacob: 3"
"render Bill: 100"
"Updating Data"
"render Joe: 1"
"render John: 2"
"render Jacob: 3"
"render Bill: 200"

Một điều dễ nhận thấy đó là mặc dù ta chỉ add thêm một user vào list và thay đổi user đó nhưng mỗi lần như vậy tất cả các user khác trong list đều bị render lại dù không cần thiết. Ta có thể chặn việc này lại bằng cách add thêm lifecycle method shouldComponentUpdate ở User component :

shouldComponentUpdate (nextProps) {
  return this.props.user !== nextProps.user;
}

Tuy nhiên, vì ta mutate state của component cha UserList cho nên nextProps và this.props trong method này cùng được truyền vào thông qua việc duyệt cùng một state đó ở component UserList và giá trị là giống nhau. this.props.user !== nextProps.user luôn return false do vậy việc render lại những component User đã mount trước đó không xảy ra và kết quả là item cuối cùng mặc dù đã update thành { value: 200, name: 'Bill'} nhưng component User ứng với item này không update và render lại. Out put: Joe - 1 John - 2 Jacob - 3 Bill - 100

Với sự trợ giúp đắc lực từ thư viện Immutable.js ta có thể giải quyết được vấn đề này. Phiên bản thứ hai như sau:

let data = Immutable.List.of(
  Immutable.Map({ value: 1, name: 'Joe' }), 
  Immutable.Map({ value: 2, name: 'John'}),
  Immutable.Map({ value: 3, name: 'Jacob'})
);

class UserList extends React.Component {
  constructor (props) {
    super(props)
    this.state = { data: data };
  }
  
  componentDidMount () {
    setTimeout(() => {
      console.log('Adding Data');
      let newData = this.state.data.push(Immutable.Map({ value: 100, name: 'Bill' }));
      this.setState({data: newData});
    }, 1000);

    setTimeout(() => {
      console.log('Updating Data');
      let newData = this.state.data.update(3, item => item.set('value', 200)); 
      this.setState({ data: newData });
    }, 2000);    
  }
  
   render () {
    return <div>{this.state.data.map(user => <User user={user} /> )}</div>;
  }
}

class User extends React.Component {
  shouldComponentUpdate (nextProps) {
      return this.props.user !== nextProps.user
   }
  render () {
    let user = this.props.user;
    console.log('render' + user.get('name'));
    return <div>{user.get('name')} - {user.get('value')}</div>;  
  }
}

React.render(<UserList />, document.getElementById('root'))

Theo đó khi ta change state của component UserList, thực chất ta tạo ra một instance state mới với data đã update và giữ nguyên instance state cũ. Khi đó ở mỗi component User, this.props và nextProps lúc này được truyền vào thông qua việc duyệt state cũ và state mới ở component UserList nên có thể so sánh với nhau để xác định xem component đó có cần update không. Kết quả cuối cùng là chỉ những component User có props được update mới render lại. Ở console ta có:

"render Joe: 1"
"render John: 2"
"render Jacob: 3"
"Adding Data"
"render Bill: 100"
"Updating Data"
"render Bill: 200"

Out put: Joe - 1 John - 2 Jacob - 3 Bill - 200 Hi vọng qua bài này đã có thể giúp các bạn biết cách dùng immutable state vào việc quản lý render component hiệu quả hơn. Cảm ơn vì đã quan tâm.


All Rights Reserved