11/08/2018, 21:08

Học React/Redux qua ví dụ thực tế: Kết nối React với Redux

Hôm trước chúng ta đã setup xong Redux cho project. Tiếp theo chúng ta sẽ cùng tìm cách làm sao để kết nối được React và Redux. Vậy làm sao để kết nối React với Redux nhỉ? Hmm, search google từ khoá react redux thử xem sao. Đùa thế thôi, để kết nối React và Redux chúng ta sẽ dùng một thư viện ...

Hôm trước chúng ta đã setup xong Redux cho project. Tiếp theo chúng ta sẽ cùng tìm cách làm sao để kết nối được React và Redux.

Vậy làm sao để kết nối React với Redux nhỉ?

Hmm, search google từ khoá react redux thử xem sao. Đùa thế thôi, để kết nối React và Redux chúng ta sẽ dùng một thư viện trung gian đó là react-redux.

Vào thư mục gốc của project, cài đặt package này ngay thôi.

npm install --save react-redux

Với react-redux, chúng ta đang không cần phải care đến viêc store sẽ được subscribe như thế nào với Redux store API. Library này sẽ take care việc connect component và store để biết được khi nào component được update. Nếu bạn muốn biết rõ hơn cách nó làm việc thì chui vào source code nó đọc nhé. Hehe.

Để kết nối Redux store và component chúng ta sẽ có hai bước. Nào cùng bắt đầu với bước đầu tiên.

Gói tất cả các component lại với component Provider

Việc đầu tiên chúng ta cần làm là gói tất cả các component lại với component Provider. Tôi biết các bạn sẽ thắc mắc ủa tại sao phải làm như vậy, component Provider là cái gì, ở đâu lòi ra hay vậy?

Đây, giải thích ngay đây. Component Provider được cung cấp bởi react-redux giúp cho chúng ta có thể truy cập store cũng như tất cả những function của nó ở tất cả các component con. Điều duy nhất chúng ta phải làm đó là cài đặt store và gói tất cả các component con vào component Provider. Và store sẽ được truyền vào Provider như là một property.

Lúc này file src/index.js sẽ được sửa như sau.

import {Provider} from 'react-redux';

...

ReactDOM.render(
  <Provider store={store}>
    <TrackList />
  </Provider>,
  document.getElementById('app')
);

Như vậy chúng ta đã giúp cho tất cả các component con truy xuất được Redux store.

Làm gì tiếp theo đây?

Đâu phải component nào chúng ta cũng cần toàn bộ global store đâu đúng không, vậy thì làm sao để chỉ định cho component biết chính xác state nào mà nó cần phải lấy? Đó là bước tiếp theo chúng ta cần làm.

Nào cùng bắt đầu với việc sửa component TrackList.

import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';

class TrackList extends Component {
  ...
}

function mapStateToProps(state) {
  const tracks = state.track
  return {tracks}
}

export default connect(mapStateToProps)(TrackList)

Như các bạn có thể thấy, bản thân component sẽ không thay đổi gì cả.

Nào cùng xem helper function connect của react-redux làm gì nhé.

Nó sẽ nhận kết quả trả về của function connect là một function, function đó sẽ nhận vào tham số là component TrackList. Sau đó đem tất cả trả về cho component cha, component cha sẽ có thể truy xuất store, trong khi TrackList sẽ chỉ là component hiển thị dữ liệu.

Thêm vào đó, function connect sẽ nhận vào tham số là một function làm nhiều vụ mapping global state vào props của component, chúng ta gọi nó là mapStateToProps, function này sẽ có nhiệm vụ trả về object, là substate của global state, cái mà component cần.

Chúng ta vẫn có thể access props được truyền vào từ component cha thông qua function mapStateToProps, ví dụ.

<TrackList something={thing} />

Tham số thứ hai của function connect sẽ giúp chúng ta làm chuyện này, mà không cần phải thông qua state của application. Như sau.

function mapStateToProps(state, props) { … }

Nào giờ cùng start application xem giao diện đã hiển thị được bài hát chưa nhé!

Được rồi đúng không nào, tuy nhiên chúng ta hãy refactor code một xíu cho đẹp nhé.

export default connect(mapStateToProps)(TrackList)

Đây là hiện tại, chúng ta có thể apply arrow function, cùng với destructuring parameter của ES6 để làm cho function gọn hơn, như thế này.

export default connect(({track}) => ({tracks: track}))(TrackList)

Như vậy chúng ta không cần phải khai báo mapStateToProps một các tường minh nữa. Cơ mà nhìn nó vẫn kì kì. Sao không phải là.

export default connect(({tracks}) => ({tracks}))(TrackList)

Có lẽ như vậy thì code sẽ đẹp hơn, well, để làm như vậy chúng ta sẽ phải đổi state.track thanh state.tracks, nó nằm ở đâu nhỉ?

Ah nhớ rồi, nó sẽ nằm ở reducer. Trong src/reducers/index.js sửa reducer track thành tracks. Import vẫn kì kì đúng không, thôi đổi luôn filename src/reducers/track.js thành src/reducers/tracks.js cho đẹp. Kết quả ta sẽ có file src/reducers/index.js như sau.

import {combineReducers} from 'redux';
import tracks from './tracks';

export default combineReducers({
  tracks
});

Ngoài ra nếu bạn có dùng decorator trong ES6, component có thể được khai báo gọn hơn bằng cách.

@connect(({tracks}) => ({tracks}), {})
export default class TrackList extends Component {}

Ok cool. Giờ nhìn có vẻ ổn hơn rồi, nhớ test lại đảm bảo là application chạy được nhé. Tôi biết các bạn đang nghĩ, có tí xíu này thôi mà nói giông dài quá trời, tuy nhiên tôi muốn các bạn hiểu được rằng đằng sau function connect nó sẽ làm nhiệm vụ map global state vào props của component, và cách thức refactor từng phần nhỏ của code như thế nào.

Ơ khoan, sao test không chạy được nữa?

Component TrackList bây giờ đang mang trong mình hai nhiệm vụ, đầu tiên là connect một vài state, cụ thể là tracks, thứ hai là render. Để dễ dàng cho việc testing, chúng ta nên chia component ra làm hai phần, gồm container và presenter, container sẽ làm nhiệm vụ connect với Redux, trong khi presenter sẽ chỉ đơn giản là render, và chúng ta sẽ chỉ đơn giản là test trên presenter.

Nào cùng tiếp tục refactor, hiểu lý do tôi cố tình bắt các bạn làm quen với việc refactor ở trên rồi chứ.

Đầu tiên chúng ta sẽ structure lại các package. Trong thư mục src/components các bạn hãy tạo thư mục TrackList, đây sẽ là nơi chứa container và presenter của component TrackList.

Tạo file src/components/TrackList/index.js file này sẽ làm nhiệm vụ là container của component, move TrackList.js vào bên trong folder TrackList, file này sẽ là presenter, cũng đừng quên move file .spec.js vào nhé! Cuối cùng chúng ta sẽ có cấu trúc folder như sau.

Nhìn có vẻ ngon lành rồi đây!
Mở file src/components/TrackList/index.js lên.

import {connect} from 'react-redux';
import TrackList from './TrackList';

export default connect(({tracks}) => ({tracks}))(TrackList);

Tiếp theo sửa file src/components/TrackList/TrackList.js.

import React, {Component, PropTypes} from 'react';

export default class TrackList extends Component {
  static propTypes = {
    tracks: PropTypes.array
  }

  static defaultProps = {
    tracks: []
  }

  render() {
    return (
      <div>
      {
        this.props.tracks.map((track, key) => {
          return <div key={key}>Track: {track.title}</div>;
        })
      }
      </div>
    )
  }
}

Xong, như vậy là chúng ta đã tách biệt được cách thành phần kết nối đến Redux và các thành phần render ra. Yay giờ thì test chạy lại ngon lành rồi.

Source code trong bài các bạn có thể tìm thấy ở https://github.com/codeaholicguy/react-redux-tutorial/tree/master/connect-react-with-redux.

Những bước cuối cùng để hoàn thiện các thành phần development với Redux đã xong, còn chờ gì nữa mà không lao đầu vào code với data thật thôi, hãy cùng tạo một SoundClould Client thực thụ với SoundCloud API nào. Cùng đón chờ bài viết kế tiếp nhé.

Hẹn gặp lại!

Bài gốc: Codeaholicguy

0