Xây dựng ứng dụng React bằng Redux

Redux là gì?

Redux là một thư viện giúp bạn quản lí trạng thái (state) của application. Nó được thiết kế dựa trên Flux, nhưng giảm bớt những đau khổ thường gặp phải khi viết một ứng dụng Flux. Nếu bạn đã từng làm việc với Flux, bạn sẽ dễ dàng nhận ra rằng Redux đã xây dựng sẵn cho bạn rất nhiều thứ mà lẽ ra bạn tự phải làm. Ngoài ra, không giống như Flux, bạn có một state container duy nhất. Đó là một ưu điểm lớn, bởi vì nó sẽ giúp chia sẻ state và tái sử dụng code dễ dàng hơn.

Stores

Một store đơn giản là một state container. Đây là nơi lưu trữ state và nơi những action được phát đi và xử lí. Khi bạn bắt đầu xây dựng ứng dụng bằng Redux, bạn phải nghĩ bạn muốn model ứng dụng của bạn như nào và các state sẽ được lưu trữ như nào. Đây là điều rất quan trọng, bởi vì với Redux bạn chỉ nên có 1 store và state thì được chia sẻ, nên bạn cần phải suy nghĩ kĩ trước khi bắt đầu.

Actions

Action là những đối tượng mô tả cách chúng ta muốn thay đổi state. Bạn có thể hình dung action như là những API cho state của bạn. Ví dụ, một action để add thêm user có thể như sau:

{
  type: 'ADD_USER',
  data: {
    name: 'Foo',
    email: '[email protected]',
    password: 'Foobar123_'
  }
}

Để làm cho mọi thứ rõ ràng và dễ dàng tái sử dụng hơn, convention được sử dụng để xây dựng nên action object. Trong ví dụ trên, bạn có thể tạo một function như addUser(name, email, password). Như bạn thấy, action bản chất không gọi bất cứ thứ gì. Một action chỉ đơn giản là một object mô tả về cách chúng ta muốn thay đổi state.

Reducer

Action rất tuyệt vời, nhưng tự bản thân nó không thể làm nên được điều gì. Đấy là lí do chúng ta có reducer. Reducer là những action handler, nó hoạt động kết nối giữa action và store và biến thành những thay đổi trong state. Nếu chúng ta dispatch một action là ADD_USER vào trong store, chúng ta có thể có một reducer nhận action đó và thêm user mới vào trong state.

Tạo một ứng dụng Redux

Sau khi tìm hiểu những khái niệm cơ bản, chúng ta hãy bắt tay vào xây dựng một ứng dụng Redux đơn giản. Cho dễ dàng, chúng ta sẽ phát triển ứng dụng to-do. Nó sẽ giúp chúng ta hiểu hầu hết những khái niệm quan trọng trong Redux.

Để làm to-do, chúng ta cần một danh sách, và trong danh sách đó chưa những to-do item có thể thay đổi được.

Từ góc nhìn về state, chúng ta có thể tổ chức dữ liệu như sau:

{
  todo: {
    items: [
      {
        message: "Finish Redux blog post...",
        completed: false
      }
    ]
  }
}

Thêm Action

Chúng ta muốn làm gì với state? Đầu tiên, chúng ta cần tạo ra to-do item mới cho nó. Hãy tạo ra một action làm việc đó:

function addTodo(message) {
  return {
    type: 'ADD_TODO',
    message: message,
    completed: false
  };
}

Hãy chú ý vào trường type. Nó nên là một cái tên duy nhất (unique), là định danh cho action đó. Convention là viết hoa và sử dụng underscore. Sau này, bạn sẽ dùng định danh này cho các reducer nhằm xác định xem đó là hành động nào và thay đổi state tương ứng.

Sau khi thêm một to-do item, chúng ta hẳn sẽ muốn đánh dấu nó đã hoàn thành, đồng thời xoá nó, thậm chí xoá toàn bộ các item trong danh sách to-do. Hãy thêm những action đó:

function completeTodo(index) {
  return {
    type: 'COMPLETE_TODO',
    index: index
  };
}

function deleteTodo(index) {
  return {
    type: 'DELETE_TODO',
    index: index
  };
}

function clearTodo() {
  return {
    type: 'CLEAR_TODO'
  };
}

Bây giờ, sau khi xây dựng xong những action, chúng ta tiếp tục tạo ra store. Nếu bạn còn nhớ, thì store chính là trung tâm của một ứng dụng Redux, nơi tất cả state, những action được phát đi, và reducer đều nằm tại đây.

import { createStore } from 'redux';

var defaultState = {
  todo: {
    items: []
  }
};

function todoApp(state, action) {
}

var store = redux.createStore(todoApp, defaultState);

Thêm reducer

Bây giờ chúng ta đã có một số action và cả store. Giờ chúng ta sẽ tạo ra reducer đầu tiên. Nếu bạn còn nhớ, một reducer chỉ là một action handler, nơi dùng để xử lí action và thay đổi state. Chúng ta sẽ bắt đầu xử action ADD_TODO:

function todoApp(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      var newState = Object.assign({}, state);

      newState.todo.items.push({
        message: action.message,
        completed: false
      });

      return newState;

    default:
      return state;
  }
}

Nếu một state cần được thay đổi, thứ thực sự xảy ra là nó tạo ra một bản copy mới của state, và thay đổi trên đó, sau đó return lại. Nếu chúng ta không muốn thay đổi gì, thì có thể trả lại chính state ban đầu, mà không cần copy.

Sau khi có action handler đầu tiên, chúng ta hãy tạo ra những cái còn lại cho các action khác:

function todoApp(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      var newState = Object.assign({}, state);

      newState.todo.items.push({
        message: action.message,
        completed: false
      });

      return newState;

    case 'COMPLETE_TODO':
      var newState = Object.assign({}, state);

      newState.todo.items[action.index].completed = true;

      return newState;

    case 'DELETE_TODO':
      var items = [].concat(state.todo.items);

      items.splice(action.index, 1);

      return Object.assign({}, state, {
        todo: {
          items: items
        }
      });

    case 'CLEAR_TODO':
      return Object.assign({}, state, {
        todo: {
          items: []
        }
      });

    default:
      return state;
  }
}

Gộp tất cả lại vào React UI Chúng ta đã hoàn thành phần business logic, giờ hãy bắt tay vào viết UI. Vì các bạn đều đã có kiến thức về React, nên tôi không đi sâu vào nó.

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';

var defaultState = {
  todo: {
    items: []
  }
};

// Add the actions here that we created in the previous steps...

function todoApp(state, action) {
  // Add the reducer logic that we added in the previous steps...
}

var store = createStore(todoApp, defaultState);

class AddTodoForm extends React.Component {
  state = {
    message: ''
  };

  onFormSubmit(e) {
    e.preventDefault();
    store.dispatch(addTodo(this.state.message));
    this.setState({ message: '' });
  }

  onMessageChanged(e) {
    var message = e.target.value;
    this.setState({ message: message });
  }

  render() {
    return (
      <form onSubmit={this.onFormSubmit.bind(this)}>
        <input type="text" placeholder="Todo..." onChange={this.onMessageChanged.bind(this)} value={this.state.message} />
        <input type="submit" value="Add" />
      </form>
    );
  }
}

class TodoItem extends React.Component {
  onDeleteClick() {
    store.dispatch(deleteTodo(this.props.index));
  }

  onCompletedClick() {
    store.dispatch(completeTodo(this.props.index));
  }

  render() {
    return (
      <li>
        <a href="#" onClick={this.onCompletedClick.bind(this)} style={{textDecoration: this.props.completed ? 'line-through' : 'none'}}>{this.props.message.trim()}</a>
        <a href="#" onClick={this.onDeleteClick.bind(this)} style={{textDecoration: 'none'}}>[x]</a>
      </li>
    );
  }
}

class TodoList extends React.Component {
  state = {
    items: []
  };

  componentWillMount() {
    store.subscribe(() => {
      var state = store.getState();
      this.setState({
        items: state.todo.items
      });
    });
  }

  render() {
    var items = [];

    this.state.items.forEach((item, index) => {
      items.push(<TodoItem
        key={index}
        index={index}
        message={item.message}
        completed={item.completed}
      />);
    });

    if (!items.length) {
      return (
        <p>
          <i>Please add something to do.</i>
        </p>
      );
    }

    return (
      <ol>{ items }</ol>
    );
  }
}

ReactDOM.render(
  <div>
    <h1>Todo</h1>
    <AddTodoForm />
    <TodoList />
  </div>,
  document.getElementById('container')
);

Như các bạn thấy, xây dựng một ứng dụng Redux không hề phức tạp. Điều khác biệt duy nhất là chúng ta sử dụng store.subscribe(listener) để lắng nghe thay đổi và dùng store.getState() để lấy state. Còn lại thì nó cơ bản là giống với một ứng dụng Flux. Hi vọng các bạn thích thú với tutorial này và sẽ thấy nó hữu ích trong tương lai.

Tham khảo

https://stormpath.com/blog/build-a-redux-powered-react-application