Reflux vs. Redux

Thay vì giới thiệu hay chỉ ra từng điểm khác biệt (tốt hơn?) của Redux đối với Reflux thì chúng ta sẽ xem xét qua 3 ví dụ: Lưu state của UI vào store, lọc data trong store, đưa data vào store từ API. Có một lưu ý là các ví dụ được lấy từ code chuyển từ ES5 sang ES6/ES7, do đó bạn nên biết syntax của ES6 trước khi bắt đầu.

Example: Lưu state của UI vào store

Trong một list app, chungs ta sẽ phải thực hiện chức năng ẩn hiện các extra filters, bắt đầu với state là ẩn hầu hết các filters và hiện link "more filters"...

more filters

Trạng thái hiện sẽ là toàn bộ các filters và link "fewer filters"

fewer filters

Code bằng Jquery

$(".toggle-filters-link").click(function() {
  $(".hidden-filters").toggle();
});

mặc dù code bằng Jquery easy hơn trong trường hợp này, nhưng đây chỉ là ví dụ nhỏ, và trong cả hệ thống lớn thì nó vẫn sẽ được implement theo flow của Reflux hoặc Redux.

Reflux

Reflux được xây dựng trên tư tưởng của Flux, các action trong app sẽ chỉ flow theo 1 hướng, đây là sơ đồ từ tài liệu của Flux:

()[https://facebook.github.io/flux/img/flux-simple-f8-diagram-explained-1300w.png]

Tuy nhiên Reflux đã xử lý phần Dispatcher ngầm bên dưới, nên chúng ta chỉ cần tạo ra 3 thành phần, actions, store và view, dưới đây là store và action:

////////// ACTION: //////////
// src/actions/show_filters_actions.js
var ShowFiltersActions = {};
ShowFiltersActions.toggle = Reflux.createAction();
module.exports = ShowFiltersActions;

////////// STORE: //////////
// src/stores/show_filters_store.js
var ShowFiltersActions = require('../actions/show_filters_actions');

var ShowFiltersStore = Reflux.createStore({
  // kết nối với showFiltersActions:
  listenables: ShowFiltersActions,

  init: function() {
    this.showFilters = false;
  },

  // giá trị khởi tạo ban đầu là ẩn đi các filters
  getInitialState: function() {
    return false;
  },

  // Reflux tự động kết nối với actions, bạn chỉ cần định nghĩa hàm theo cú pháp "on#{actionName}":
  onToggle: function () {
    this.showFilters = !this.showFilters

    // gọi trigger để refresh, các view được kết nối với store này sẽ nhận được giá trị showFilters mới
    this.trigger(this.showFilters);
  },
});

module.exports = ShowFiltersStore;

sau đó là các view:

// src/components/show_filters_link.js
var ShowFiltersStore = require('../stores/show_filters_store');

var ShowFiltersLink = React.createClass({
  // kết nối với store, các data của store sẽ được gọi ở view thông qua this.state.showFilters
  mixins: [
    Reflux.connect(ShowFiltersStore, 'showFilters')
  ],

  // gọi thay đổi đến store thông qua actions
  toggleFilters: function() {
    ShowFiltersActions.toggle();
  },

  render: function() {
    // hiển thị text phụ thuộc vào giá trị của showFilters
    var linkText = this.state.showFilters ? 'Fewer Filters' : 'More Filters'

    return (
      <a
        onClick={this.toggleFilters}
        className="toggle-filters-link" >
          {linkText}
      </a>
    )
  },

});

module.exports = ShowFiltersLink;

// src/components/location_filters.js

var ShowFiltersStore = require('../stores/show_filters_store');
var ShowFiltersLink = require('./show_filters_link')

var LocationFilters = React.createClass({
  mixins: [
    Reflux.connect(ShowFiltersStore, 'showFilters'),
  ],

  render: function() {

    return (
      <div className='filter-labels-wrapper'>
        <div className='visible-filters'>

          // VISIBLE FILTERS CONTAINER

          <ShowFiltersLink />
        </div>

        <div className={this.state.showFilters ? 'hidden-filters visible' : 'hidden-filters'}>

          // HIDDEN FILTERS CONTAINER

        </div>
      </div>
    )
  },
});

module.exports = LocationFilters;

Với những app nhỏ, lượng store chưa nhiều thì Reflux là một lựa chọn chấp nhận được, nhưng trong app với 40 stores trở lên, và đặc biệt là các stores lồng nhau (tương ứng với các view lồng nhau) thì quá trình maintain, debug cũng như phát triển tiếp là cực kỳ phức tạp, không chỉ với người mới mà còn cả những core member đã phát triển app ngay từ đầu. Khi đó chỉ có 2 giải pháp, 1 là refactor, thậm chí là không theo 100% flow của Reflux để giảm thiểu số lượng stores, 2 là sử dụng một Framework hiệu quả hơn, Redux.

Redux

giới thiệu qua về Redux

The whole state of your app is stored in an object tree inside a single store. The only way to change the state tree is to emit an action, an object describing what happened. To specify how the actions transform the state tree, you write pure reducers.

Nghe giống với Reflux nhưng nó có một số điểm khác biệt chính sau:

  1. Chỉ có duy nhất 1 store: nó tương ứng với cấu trúc bên view khi chỉ có duy nhất một root, sau đo các data sẽ được truyền xuống các nhánh child theo props.
  2. Không còn các actions với nhiều stores: thay vì dùng actions để liên kết view và stores, Redux chỉ dùng duy nhất 1 root, khi app phình to, thay vì thêm các stores chúng ta có các reducer được chia nhỏ, đảm trách xử lý từng phần riêng biệt so với root.

Xây dựng module trong Redux

Khái niệm chỉ có một store duy nhất hơi khác lạ so với những người đã làm Reflux. Trong ví dụ lọc loctions thì cấu trúc data của nó có thể như thế này:

{
  showFilters: true,
  locations: [
    { name: "Bryant Park" },
    { name: "42nd St"},
    etc...
  ]
}

Giờ thì ta sẽ xây dựng những thành phần đầu tiên của module: actionType, action, và reducer:

// src/redux/modules/show_filters.js

// định nghĩa loại action là constant
// cấu trúc url của action nhằm mục đích cho redux logger và
// redux-devtools

const TOGGLE = 'wework.com/showFilters/TOGGLE';

// reducer có giá trị ban đầu là false, sau đó sẽ trả về giá trị
// thích hợp tương ứng với action type

export default function reducer(state = false, action = {}) {
  switch (action.type) {
    case TOGGLE:
      return !state;
    default:
      return state;
  }
}

// action truyền loại TOGGLE đến cho reducer

export function toggle() {
  return {
    type: TOGGLE
  };
}

Tiếp theo là một hàm global reducing cho toàn bộ app:

// src/redux/modules/reducer.js

import { combineReducers } from 'redux';

import showFilters from './show_filters';
import locations from './locations';

export default combineReducers({
  showFilters,
  locations
});

// sau khi combine thì store duy nhất sẽ có dạng data như này:

// {
//   showFilters: true,
//   locations: [
//     { name: "Bryant Park" },
//     { name: "42nd St"},
//     etc...
//   ]
// }

Tiếp theo là việc gắn kết module vừa tạo với Redux:

// src/redux/init.js

import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';

const reducer = require('./modules/reducer');

// chỉ sử dụng redux-logger cho development mode
const __DEV__ = // 1 loại biến môi trường;
const logger = createLogger({
  predicate: (getState, action) => __DEV__
});

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  logger
)(createStore);

export default function createApiClientStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

Trong đoạn code trên, Redux cho chúng ta một thứ thực sự hữu ích là Middleware. Không những có thể include những thư viện middleware như redux-thunk hay redux-logger, mà việc viết các custom middle và tích hợp và redux cũng tương đối dễ dàng. Redux-thunk là một middleware cho API.

Tiếp theo là kết nối componnet chính <App /> với store bằng

.

// src/containers/root.js

import React, { Component } from 'react';
import App from './app';
import { Provider } from 'react-redux';
import createApiClientStore from '../redux/init';

// Import the store created in init.js
const store = createApiClientStore();

export default class Root extends Component {
  render() {
    return (
      <Provider store={store}>
        {() => <App /> }
      </Provider>
    );
  }
}

Sau cùng là truyền data xuống cho các components con:

// src/containers/app.js

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

import {toggle as showFiltersToggle} from '../redux/modules/show_filters';

import ShowFiltersLink from '../components/show_filters_link';

class App extends Component {
  render() {
    const {
      showFilters,
      dispatch
    } = this.props;

    const actions = bindActionCreators({
      showFiltersToggle
    }, dispatch)

    return (
      <div className="app-wrapper locations-wrapper">
        <section className="location-filters-section">
          <ShowFiltersLink showFilters={showFilters} actions={actions} />
        </section>
      </div>
    );
  }

}

export default connect(
  state => ({
    showFilters: state.showFilters
  })
)(App);

Trong các component con các props sẽ nhận data như các framework khác:

// src/components/show_filters_link.js

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

class ShowFiltersLink extends Component {
  render() {
    const {actions, showFilters} = this.props;

    const linkText = showFilters ? 'Fewer Filters' : 'More Filters'

    return (
      <a
        onClick={actions.showFiltersToggle}
        className="toggle-filters-link" >
          {linkText}
      </a>
    )
  }
}

ShowFiltersLink.propTypes = {
  actions: PropTypes.object,
  showFilters: PropTypes.bool
};

export default ShowFiltersLink;

Vậy là chúng ta vừa mới làm xong 1 app đơn giản, một phần nhỏ để demo hướng giải quyết của Redux, và nó có hiệu quả hay không còn tuỳ thuộc vào việc bạn hiểu nó đến đâu, cũng như việc app của bạn có phù hợp với nó hay ko?


tham khảo: http://engineering.wework.com/process/2015/10/01/react-reflux-to-redux/