Reselect Trong React Là Gì, Tại Sao Lại Cần Phải Sử Dụng Nó?

Khi sài redux để quản lý cây state của bạn, để quản lý render của Component trong reactJS, bạn thường gặp:

  1. Sài PureComponent để nó tự quản lý việc render lại hay không dựa vào thay đổi state, nhưng mà kiểu dữ liệu kiểm tra phải là primary type, như kiểu Int, String, Boolean, không phải là kiểu Object hoặc Array.
  2. Kiểm tra bằng bằng tay, bằng cách overrite lại hàm shouldComponentUpdate, check cái thuộc tính có thay đổi hay không để render lại. Nhưng nếu kết quả của state trả ra là một kết quả của tính toán trả về 1 Object hoặc Array mới, thì điều này không còn đúng nữa, vì lúc nào dữ liệu của nó cũng thay đổi

Reselect ra đời để xử lý việc này, nó được gọi là bộ chọn ghi nhớ, bằng cách chọn đầu vào cho hàm xử lý và nó chỉ gọi lại hàm xử lý khi mà dữ liệu đầu vào thay đổi, và mổi lần gọi lại tính toán nó lưu vào bộ nhớ. Khi gọi lại nó trả kết quả liền không cần tính toán nữa.

Xem ví dụ bên dưới để hiểu rõ hơn:

Bạn có 1 Counter Component, Component này nó sẽ update state counter tăng 1 mổi giây, nghĩa là làm cho cây state cập nhật liên tục

import React from 'react';
import {connect} from 'react-redux';

class Counter extends React.Component {
  componentDidMount() {
    setInterval(() => {
      this.props.increment();
    }, 500);
  }
  render() {
    console.log('render Counter');
    
    return (
      <div>
        <h3>Count: {this.props.count}</h3>
      </div>
    );
  }
}

const mapState = (state) => ({count: state.count});
const mapDispatch = {
  increment: () => ({type: 'INCREMENT'}),
};


export default connect(mapState, mapDispatch)(Counter);

Và 1 Posts Component 

import React from 'react';
import {createSelector} from 'reselect'
import {connect} from 'react-redux';

let count = 0;

class Posts extends React.Component {
  render() {
    console.log(`Posts render ${++count}`);
    return (
      <div>
        <h3>Posts</h3>
        <ul>
          {this.props.posts.map(x =>
            <li key={x.id}>
              {`${x.title} - ${x.user.first} ${x.user.last}`}
            </li>
          )}
        </ul>
      </div>
    );
  }
}

const getListing = createSelector( // reselect
  state => state.postsById,
  state => state.usersById,
  state => state.postListing,
  (posts, users, listing) => listing.map(id => {
    const post = posts[id];
    return {...post, user: users[post.author]}
  })
);

const mapState = (state) => {
  const posts = state.postsById;
  const users = state.usersById;
  const listing = state.postListing;
  return {
    posts: listing.map(id => {
    const post = posts[id];
    return {...post, user: users[post.author]}
  })};
};

export default connect(mapState)(Posts);

 

Và App Component

import {Provider} from 'react-redux';
import React from 'react';
import store from './store';
import Posts from './Posts';
import PostsByUser from './PostsByUser';
import Counter from './Counter';
import './index.css';

const initial = store.getState();

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Reselect Redux</h1>
        <Posts />
        <Counter />
      </div>
    );
  }
}

export default () => <Provider store={store}><App /></Provider>;

 

Khi chạy thì Counter Component update state liên tục, việc thay đổi state liên tục làm cho Posts Component cũng gọi lại hàm render liên tục

Kết quả in ra ở console.log là

Posts render 1
Posts render 2
...

Lý do sao Posts Component lại render liên tục, vì cây state thay đổi, dẫn đến Posts Component cập nhật lại thuộc tính của nó là posts, posts là danh sách bài posts được tính toán mổi lần gọi để gắn thêm thuộc tính user đi theo bài post, hàm

listing.map(id => {
    const post = posts[id];
    return {...post, user: users[post.author]}
  })

luôn cho chúng ta 1 mảng mới dù dữ liệu là giống nhau hay thay đổi mổi khi cây state thay đổi, dẫn đến Posts Component render liên tục.

Để giải quyết vấn đề này ta sài reselect để tạo bộ chọn ghi nhớ:

const getListing = createSelector(
  state => state.postsById,
  state => state.usersById,
  state => state.postListing,
  (posts, users, listing) => listing.map(id => {
    const post = posts[id];
    return {...post, user: users[post.author]}
  })
);

Sài reselect, ta cần quan tâm là đầu vào của nó, và đầu ra. Cơ chế xử lý của reselect là dữ liệu đầu ra của nó

(posts, users, listing) => listing.map(id => {
    const post = posts[id];
    return {...post, user: users[post.author]}
  })

sẽ được tính toán lại khi mà 1 trong các dữ liệu đầu vào thay đổi, dữ liệu đầu vào ở hàm createSelector ở trên là gì, đó làm 3 dữ liệu đầu vào:

state => state.postsById,
  state => state.usersById,
  state => state.postListing,

Nghĩa là khi sài reselect thì hàm getListing nó chỉ gọi lại việc tính toán trả về đầu ra, khi mà 1 trong các dữ liệu đầu vào thay đổi, còn nếu không nó sẽ không gọi lại việc xử lý này, và trong quá trình thay đổi cây trạng thái, thì kết quả tính toán được lưu vào bộ nhớ, nên nếu gặp các lần tính toán tương tự về sau, nó sẽ lấy từ bộ nhớ ra.

Và việc dữ liệu hàm reselect không thay đổi, ReactJS nó sẽ hiểu và không cần gọi render lại.

Giờ chúng ta thực hiện thao tác là đổi lại mã chổ mapStateToProps, dữ liệu của thuộc tính posts được lấy từ bộ chọn ghi nhớ getLising

import React from 'react';
import {createSelector} from 'reselect'
import {connect} from 'react-redux';

let count = 0;

class Posts extends React.Component {
  render() {
    console.log(`Posts render ${++count}`);
    return (
      <div>
        <h3>Posts</h3>
        <ul>
          {this.props.posts.map(x =>
            <li key={x.id}>
              {`${x.title} - ${x.user.first} ${x.user.last}`}
            </li>
          )}
        </ul>
      </div>
    );
  }
}

const getListing = createSelector( // reselect
  state => state.postsById,
  state => state.usersById,
  state => state.postListing,
  (posts, users, listing) => listing.map(id => {
    const post = posts[id];
    return {...post, user: users[post.author]}
  })
);

const mapState = (state) => {
  const posts = state.postsById;
  const users = state.usersById;
  const listing = state.postListing;
  return {
    posts: getListing(state)
};
};

export default connect(mapState)(Posts);

Và chạy lại ứng dụng để xem kết quả nhé.

Rate this post

You May Also Like

About the Author: truongluu

Leave a Reply

Your email address will not be published. Required fields are marked *