Flux in reactjs with CRUD Example (Part-1)

Để tiếp tục loạt bài viết về reactjs mình xin chia sẻ tiếp về việc sử dụng flux trong dự án reactjs. Để dễ hiểu mình sẽ sử dụng một ví dụ nhỏ về CRUD để mọi người cùng tham khảo.
Đối với các bạn mới để hiểu rõ hơn về reactjs các bạn có thể tham khảo các bài viết đã được post trong blog này như:
1. Flux – Xương sống của Facebook React
2. Giới thiệu chung về React JS
3. React Component Lifecycle
4. Hello word with react

Quay về với chủ đề chính. Để sử dụng được flux trong dự án đầu tiên điểu cần cài đặt flux và bố cục dự án theo mô hình của nó. Nào cùng bắt đầu nhé.
Việc đầu tien bạn hãy tạo thư mục src bạn tạo các folder như sau:

crud-react
 |
 +- src
 |   |
 |   +- actions
 |   |
 |   +- components
 |   |
 |   +- dispatchers
 |   |
 |   +- stores
 |   |
 |   +- app.jsx
 | 
 +- GulpFile.js
 |
 +- index.html
 |
 +- package.json

Trong file GulpFile.js bạn tạo nội dung như sau

var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');
var rename     = require('gulp-rename');
var jsmin = require('gulp-jsmin');
var clean = require('gulp-clean');

gulp.task('build-js', function() {
    browserify('./src/app.jsx', { debug: true })
        .transform(babelify)
        .bundle()
        .on("error", function (err) { console.log("Error : " + err.message); })
        .pipe(source('app.js'))
        .pipe(gulp.dest('./build/tmp/'))
});

gulp.task('min-js', function () {
    gulp.src('build/tmp/app.js')
        .pipe(jsmin())
        .pipe(rename({suffix: '.min'}))
        .pipe(gulp.dest('./build/min/'));
});

gulp.task('clean-js', function () {
    return gulp.src('build/tmp/', {read: false})
        .pipe(clean());
});

Chắc một số bạn sẽ thắc mặc file này dùng để làm gì. Nói một cách đơn giản file này sẽ giúp chúng ta compile tất cả nội dùng file viết bằng jsx dưới dang ES6 thành file js min để tại thư mục build/min. Để hiểu rõ hơn các bạn có thể tìm hiểu thêm tại bài viết Gulp là gì?

Tiếp the tới file package.json

{
  "name": "crud-react",
  "version": "0.0.1",
  "description": "demo",
  "main": "index.js",
  "browserify": {
    "transform": [
      "babelify"
    ]
  },
  "scripts": {
    "start": "npm run build && serve  .",
    "build": "npm run build-js && npm run build-css",
    "build-js": "gulp build-js",
    "build-css": "echo \"build CSS: TODO\"",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "events": "~1.1.0",
    "flux": "~2.1.1",
    "griddle-react": "~0.3.1",
    "gulp": "~3.9.0",
    "gulp-clean": "~0.3.1",
    "gulp-jsmin": "~0.1.5",
    "gulp-rename": "~1.2.2",
    "history": "^1.17.0",
    "react": "~0.14.6",
    "react-addons-linked-state-mixin": "~0.14.6",
    "react-dom": "~0.14.6",
    "react-link": "^1.0.3",
    "react-mixin": "~3.0.3",
    "react-router": "~1.0.1",
    "reqwest": "~2.0.5",
    "when": "~3.7.5"
  },
  "author": "SEPTENI-TECHNOLOGY",
  "license": "SEPTENI",
  "devDependencies": {
    "babelify": "^6.1.0",
    "browser-sync": "^2.1.6",
    "browserify": "^8.0.3",
    "vinyl-source-stream": "~1.1.0",
    "serve": "~1.4.0"
  }
}

Với cầu hình như trên sau khi chạy npm install thì tất cả các file cài đặt cần thiết sử dụng trong ví dụ này sẽ được cài đặt.

Tiếp theo là fIle index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>CRUD EX</title>

</head>
<body>
<div id="body"></div>
<!-- content section -->
<script src="build/tmp/app.js"></script>
<!-- Initialize SPA -->
</body>
</html>

Cuối cùng cũng đã xong phần setup. Bây giờ vào nội dung chính.

Bài 1: Create user.

Để bắt đầu bài này bạn hãy tạo một form có 2 trường giá trị là name và sex nhé.
Tạo như thế nào nhỉ? rất đơn giản. Hãy tạo trong thư mục components 1 class CreateUserComponent nhé.

import React from 'react';
import linkedState from 'react-link';
export default class CreateUserComponent extends React.Component {
    constructor() {
        super();
        this.state = this._getState();
        this._onChange = this._onChange.bind(this);
    }

    _getState() {
        return {
            name: "",
            sex: "male"
        };
    }

    _onChange() {
        this.setState(this._getState());
    }

    componentDidMount() {
    }

    componentWillUnmount() {
    }

    render() {
        return (
            <section id="CreateUserComponent">
                <div>
                    Name:<input type="text" name="name" /><br/>
                </div>
                <div>
                    Sex:<select >
                            <option type="radio" name="gender" value="male"  > Male </option>
                            <option type="radio" name="gender" value="female" > Female </option>
                            <option type="radio" name="gender" value="other" > Other </option>
                        </select>
                </div>
                {this.state.name}<br/>
                {this.state.sex}
                <div>
                    <button type="submit" >Save</button>
                </div>
            </section>
        )
    }

}

Tiếp tục là file app.jsx

import React from "react";
import ReactDOM from "react-dom";
import CreateUser from "./components/CreateUserComponent.jsx";

ReactDOM.render(, document.getElementById('body'));

nào bây giờ hãy chạy dòng lệnh

npm run build

và mở file index.html lên. Rất dễ để nhận ra là chúng ta đã có một form thông tin điền tên và chọn giời tính. Nhưng thử thao tác xem các bạn có nhận ra điểu gì lạ không?

Rất dễ nhận thấy rằng việc input vào ô name và khi chọn giới tính cũng không thấy mục name và giới tính in ra phía dưới có thay đổi. Tại sao vậy? Lý do đơn giản là chúng ta chưa thể gán được giá trị từ ô input vào stage của react. Để thực hiện việc đó có rất nhiều cách. Các bạn có thể tham khảo thêm tại Two-Way Binding Helpers .

Bây giờ sửa lại một ít nhé

import React from 'react';
import linkedState from 'react-link';
export default class CreateUserComponent extends React.Component {
    constructor() {
        super();
        this.state = this._getState();
        this._onChange = this._onChange.bind(this);
    }

    _getState() {
        return {
            name: "",
            sex: "male"
        };
    }

    _onChange() {
        this.setState(this._getState());
    }

    componentDidMount() {
    }

    componentWillUnmount() {
    }

    render() {
        return (
            <section id="CreateUserComponent">
                <div>
                    Name:<input type="text" name="name" valueLink={linkedState(this, 'name')}/><br/>
                </div>
                <div>
                    Sex:<select valueLink={linkedState(this, 'sex')} >
                            <option type="radio" name="gender" value="male"  > Male </option>
                            <option type="radio" name="gender" value="female" > Female </option>
                            <option type="radio" name="gender" value="other" > Other </option>
                        </select>
                </div>
                {this.state.name}<br/>
                {this.state.sex}
                <div>
                    <button type="submit" >Save</button>
                </div>
            </section>
        )
    }

}

Build lại rồi xem sự khác biệt. Bây giờ mọi hành động vào các ô input giá trị để được add vào giá trị stage rồi.

Tiếp theo chúng ta sẽ add thêm action cho nut save. Action này sẽ hoạt động như sau:

Theo flux Khi click vào button save sẽ gọi đến một action trong layer actions. Layer action sau khi tương tác với api,.. sẽ tiếp tục gọi đến layer Dispatcher. Từ đây các request sẽ được gửi đến các store trong layer stores. Các store sẽ “nghe ngóng” các action được gửi đến để từ đó quyết định hành dộng nào thực hiện. Cuối cùng sẽ gửi tín hiệu cho view layer.

Trong folder Dispatchers ta tạo một file Dispatcher.jsx với nội dung như sau

import Flux from 'flux';

export default new Flux.Dispatcher();

OK đã xong! “Cái quái gì đây? thế thôi à?”. Tuy trông rất đơn giản nhưng nó lại là thành phần không thể thiếu được trong mô hình này.

Bây giờ tới actions layer thôi.

Tại folder actions chúng ta sẽ tạo ra file UserActions.jsx

import Dispatcher from '../dispatcher/Dispatcher.jsx'
export default  {
    create: (user) => {
        console.log("create user");
        Dispatcher.dispatch({
            actionType: 'CREATE_USER',
            user: user
        });
    }
}

Tại stores sẽ lắng nghe các event từ dispatcher. vì thế cúng ta sẽ tạo ra một file base chứa các action chung của lớp này như sau:
BaseStore.jsx

import EventEmitter from 'events';
import Dispatcher from '../dispatcher/Dispatcher.jsx';

const CHANGE_EVENT = 'change';

export default class BaseStore extends EventEmitter {
    constructor() {
        super();
    }

    subscribe(actionSubscribe) {
        this._dispatchToken = Dispatcher.register(actionSubscribe());
    }

    emitChange() {
        this.emit(CHANGE_EVENT);
    }

    addChangeListener(callback) {
        this.on(CHANGE_EVENT, callback);
    }

    removeChangeListener(callback) {
        this.removeListener(CHANGE_EVENT, callback);
    }

}

Tiếp theo là UserStore.jsx

import BaseStore from './BaseStore.jsx'

class UserStore extends BaseStore{
    constructor() {
        super();
        this.userIndex = 0;
        this.listData = {};
        this.subscribe(() => this.handler.bind(this));

    }

    /**
     * Register callback to handle all updates
     *
     * @param  {Object} action
     */
    handler(action) {
        console.log("I see you!");
        switch (action.actionType) {
            case 'CREATE_USER':
                this.listData[this.userIndex] = action.user;
                this.userIndex = this.userIndex + 1;
                console.log(this.listData);
                this.emitChange();
                break;
            default :
        }
    }

    getUserList() {
        return this.listData
    }
}

const userStore = new UserStore();

export default userStore;

Cuối cùng là add action vào nút save và thêm action “lắng nghe” UserStore khi khởi tại CreateUserComponent nữa là ok.
Ta sửa lại code của CreateUserComponent như sau

import React from 'react';
import linkedState from 'react-link';
import userAction from '../actions/UserActions.jsx';
import UserStore from '../stores/UserStore.jsx';
export default class CreateUserComponent extends React.Component {
    constructor() {
        super();
        this.state = this._getState();
        this._onChange = this._onChange.bind(this);
    }

    _getState() {
        return {
            name: "",
            sex: "male"
        };
    }

    _onChange() {
        this.setState(this._getState());
    }

    componentDidMount() {
        UserStore.addChangeListener(this._onChange);
    }

    componentWillUnmount() {
        UserStore.removeChangeListener(this._onChange);
    }

    render() {
        return (
            <section id="CreateUserComponent">
                <div>
                    Name:<input type="text" name="name" valueLink={linkedState(this, 'name')}/><br/>
                </div>
                <div>
                    Sex:<select valueLink={linkedState(this, 'sex')} >
                            <option type="radio" name="gender" value="male"  > Male </option>
                            <option type="radio" name="gender" value="female" > Female </option>
                            <option type="radio" name="gender" value="other" > Other </option>
                        </select>
                </div>
                <div>
                    <button type="submit" onClick={this.createUser.bind(this)}>Save</button>
                </div>
            </section>
        )
    }

    createUser() {
        userAction.create(this.state)
    }

}

Cuối cùng cũng tới lúc tận hưởng thành quả. Bây giờ hãy build lại và tận hưởng(Nhớ mở console lên nhé).

OK hôm nay chúng ta kết thúc phần 1 ở đây. Bài sau sẽ cùng nhau làm phần list, delete và sử dụng thêm cả router nữa nhé.

Add a Comment

Scroll Up