Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Mở đầu

Tiếp nối bài viết về Redux cho người mới bắt đầu, trong bài viết này chúng ta sẽ cùng nhau thực hiện một Todo app để tìm hiểu cách sử dụng redux trong một project thực tế.

Trước khi bắt đầu cùng nhìn lại 1 lần các nhân vật ở bài trước :

...

Setup

Trong bài viết này mình sẽ sử dụng React + Redux. Chúng ta có thể tạo nhanh một react app thông qua create-react-app:

Code Block
sudo npm install -g create-react-app
create-react-app test-react-redux

Tiếp theo là Redux:

Code Block
cd test-react-redux
npm install --save redux react-redux

react-redux : chính là The view layer binding trong bài trước, làm nhiệm vụ kết nối cho redux và react.

Let's go

Cấu trúc thư mục

Ở phần trước chúng ta đã biết một app sử dụng Redux có 4 thành phần cơ bản action reducer storeview. Trong đó store chúng ta chỉ việc khởi tạo trong root component còn việc quản lý Redux sẽ lo. view thì bao gồm smart components (containers) những components giao tiếp với Redux và dumb components (components) những components không giao tiếp với Redux. Các action type của dụng thường là các hằng số được định nghĩa trước.

Do đó để tiện việc quản lý chúng ta có thể tạo ra các thư mục actions constants reducers containers components, app của chúng ta sẽ có cấu trúc như sau:

...

Actions

Đầu tiên chúng ta sẽ tạo ra các actions, các bạn còn nhớ The Action creators không, anh ta tạo ra những action là formated object chứa type và thông tin của action đó. Type thường sẽ là một hằng số được định nghĩa trước.

Ở đây chúng ta có TodoActions định nghĩa ra các thao tác thêm sửa xóa... công việc. Các action mang type và các thông tin id, text.

actions/TodoActions.js

Bài sau sẽ đi vào thực hành để hiểu rõ hơn luồng hoạt động của redux. Ở đây sẽ cung cấp source code, nhiệm vụ là hiểu và có thể làm được task tương tự ở các bài sau sẽ cần vận dụng.

Tài liệu

Cài đặt source code.

redux-data-flow-opt-preview.pngImage Added

Source code trên đơn giản chỉ làm nhiệm vụ đếm số sau mỗi lần click sử dụng redux.

Ở đây, ta xác định action được tạo ra là action add, do đó, ta cần khai báo action

Code Block
languagejs
import * as types from '../constants/ActionTypes';

export function 

...

add(

...

) {
    return {
        type: types.ADD

...


   

...

 

...

};
}

...

Reducers

Tiếp theo là các reducers, chúng ta cần tạo ra các sub-reducersmột root-reducer quản lý chung. Reducers là các pure function hoạt động theo nguyên lý :

(state, action) => (new state)

Vì là pure function nên các reducers sẽ không trực tiếp thay đổi state mà nó nhận được, mà tạo ra các bản copy và thay đổi trên đó. Để thực hiện điều này chúng ta có thể dùng các function filter() map() Object.assign()...

reducers/todosReducers.jsreducer, để thực hiện nhiệm vụ chuyển đổi state sau khi nhận được action add phát ra

Ta sẽ có countReducer, có nhiệm vụ biến đổi state sau khi nhận được action add:

Code Block
languagejs
import { ADD_TODO, DELETE_TODO, EDIT_TODO, MARK_TODO, MARK_ALL, CLEAR_MARKED } from '../constants/ActionTypes';

// initialState
const initialState = [{
    text: 'Use Redux',
    marked: false,
    idcount': 0
}];

export default function todoscountReducer(state = initialState, action) {
    switch (action.type) {
        case ADD_TODO:
            return [{
                id: (state.length === 0) ? 0 : state[0].id + 1,...state,
                'count' : state.count + 1
   marked: false,        }
        textdefault:
action.text            return }, ...state];
    }
}

Combine reducer vào root reducer, muc đích để có thể apply được nhiều reducer sau này khi phát triển thêm.

Code Block
languagejs
import { combineReducers case DELETE_TODO:
            return state.filter((todo) => todo.id !== action.id);

        case EDIT_TODO:
            return state.map((todo) => todo.id === action.id ? { ...todo, text: action.text } : todo);

        case MARK_TODO:
            return state.map((todo) => todo.id === action.id ? { ...todo, marked: !todo.marked } : todo);

        case MARK_ALL:
            const areAllMarked = state.every((todo) => todo.marked);} from 'redux';
import countReducer from './countReducer';

const rootReducer = combineReducers({
    countReducer
});

export default rootReducer;

Khởi tạo store, truyền 2 tham số là state khởi tạo và rootReducer, gồm các reducer được combine, ở đây ta chỉ có 1 reducer là countReducer

Code Block
languagejs
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import {Provider} from "react-redux";
import { createStore, applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import rootReducer from './reducers/rootReducer';

// initialState
const initialState = {}

// Create store
const store = createStore(
    rootReducer,
    initialState
);

ReactDOM.render(
    <Provider store={store}>
        <div>
    return state.map((todo) => ({...todo, marked: !areAllMarked}));   <CountExample />
     case CLEAR_MARKED:  </div>
    </Provider>,
     return state.filter((todo) => todo.marked === falsedocument.getElementById('root')
);

// If you want your app to work default:offline and load faster, you can change
// unregister() to register() below. Note returnthis state;comes with some   }
}

Root-reducer sẽ tập hợp các sub-reducers lại thông qua combineReducers() của Redux.

reducers/rootReducers.js

Code Block
languagejs
import { combineReducers } from 'redux';
import todosReducers from './todosReducers';

const rootReducer = combineReducers({
    todosReducers
});

export default rootReducer;

Views

Smart Component (containers)

Containers là những component giao tiếp với Redux thông qua connect() của react-redux.

connect() nhận vào 4 tham số mapStateToProps mapDispatchToProps mergeProps options:

  • mapStateToProps(state, [ownProps]) là function. Nếu được định nghĩa, container sẽ được đăng ký (subscribe) với store. Mỗi khi store update mapStateToProps sẽ được gọi, object mà nó trả về sẽ được merge với props của container. Nếu ownProps được định nghĩa, giá trị của nó sẽ là props được gửi cho container, đồng thời mỗi khi container nhận được new props thì mapStateToProps cũng sẽ được gọi. Nếu mapStateToProps không được định nghĩa container sẽ không được đăng ký và nhận update từ store.

  • mapDispatchToPropsobject hoặc function. Nếu là object mỗi function bên trong object sẽ được coi là một action creator, đồng thời tất cả function này sẽ được tự động chay bởi bindActionCreators() và merge chúng với props của container. Nếu là function mapDispatchToProps sẽ nhận 2 tham số (dispatch, [ownProps]), chúng ta sẽ tự định nghĩa cách bind action với dispatch, chúng ta cũng có thể sử dụng bindActionCreators({action}, dispatch) để tự động bind. Nếu ownProps được định nghĩa, giá trị của nó sẽ là props được gửi cho container, đồng thời mỗi khi container nhận được new props thì mapDispatchToProps cũng sẽ được gọi. Nếu mapDispatchToProps không được định nghĩa sẽ chỉ có dispatch được merge vào props của container.

  • mergeProps(stateProps, dispatchProps, ownProps) là function. Nếu được định nghĩa, nó sẽ nhận vào tham số là kết qủa của mapStateToProps mapDispatchToPropsparent props. Object mà nó trả về là props được gửi cho container. Nếu không được định nghĩa Object.assign({}, ownProps, stateProps, dispatchProps) sẽ được sử dụng mặc định.

  • options là object. Nếu được định nghĩa sẽ điều chỉnh hành vi của connector. Chứa 2 giá trị purewithRef. Nếu pure = true thì thực thi shouldComponentUpdate() và so sánh kết qủa của mergeProps để tránh những update không cần thiết, mặc định là true. Nếu withRef = true thì lưu trữ lại ref đến container instance và có thể truy cập thông qua getWrappedInstance(), mặc định false.

Trong app của chúng ta có 1 container là TodoApp:

...

pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Trong component, map state của redux với props, map dispatch của redux với action props:

Code Block
languagejs
import React, { Fragment, Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Header from '../components/Header';
import MainSection from '../components/MainSection';
import * as TodoActionsCountActions from '../actions/TodoActionsCountActions';

class TodoAppCountExample extends Component {
    render() {
        const { todosactions, actionscount } = this.props;

        return (
            <div><Fragment>
                <Header addTodo={actions.addTodo} /><div>
                   <MainSection todos={todos} actions<button onClick={actions.add} />>Increase</button>
                </div>
         );      } }<div>
 function mapStateToProps(state) {     return {         todos: state.todosReducers {
   }; }  function mapDispatchToProps(dispatch) {     return {         actions: bindActionCreators(TodoActions, dispatch) count
     }; }  export default connect(mapStateToProps, mapDispatchToProps)(TodoApp); 

Dump Components

Là những component thông thường, chúng không giao tiếp với Redux, chỉ nhận giá trị và thao tác thông qua props.

Các xử lý hiển thị dữ liệu sẽ thực thi ở đây và các action nhận được từ container sẽ sử dụng như callback.

Trong app của chúng ta chúng là Header, MainSection .....

Root component

Trong mọi React-app đều có root component, ở app sử dụng Redux root component đảm nhận thêm việc khởi tạo store và bao các component lại với Provider của react-redux giúp component có thể giao tiếp với redux.

index.js

Code Block
languagejs
import 'todomvc-app-css/index.css'; import React from 'react'; import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux'
import TodoApp from './containers/TodoApp';
import rootReducer from './reducers/rootReducer';

// initialState
const initialState = {}

// Create store
const store = createStore(rootReducer, initialState);

const appRoot = (
    <Provider store={store}>
        <div>
            <TodoApp />
        </div>
    </Provider>
)

ReactDOM.render(appRoot, document.getElementById('root'))

Kết Vậy là chúng ta đã tạo được một ứng dụng đơn giản sử dụng React-Redux. Kết hợp với Data Flow trong phần 1 hi vọng các bạn phần nào hình dung được cách mà Redux hoạt động và cách sử dụng Redux trong một ứng dụng.

Redux còn rất nhiều khái niệm khác như Async Middleware... các bạn có thể tìm hiểu thêm ở trang chủ của Redux.

Tài liệu

...

}
                </div>
            </Fragment>
        );
    }
}

function mapStateToProps(state) {
    return {
        count: state.countReducer.count
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(CountActions, dispatch)
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(CountExample);

Chú ý mapStateToPropsmapDispatchToProps

map state với props, map state count trong store ứng với props count của count component

map dispatch với props, để khi gọi this.props.actions.add ta sẽ kích hoạt action add của redux.

Khi onclick, sẽ kích hoạt this.props.add => qua mapDispatchToProps, sẽ dispatch action add.

Action add được đưa vào reducer, xử lý tăng state lên 1 đơn vị

State cập nhật qua mapStateToProps, sẽ trả vào CountExample component, cập nhật props và hiển thị ra màn hình