+1

React Redux Starter Kit

Chắc hẳn các bạn đã quen với react.js và redux. React Redux Starter Kit là một bộ công cụ giúp chúng ta code Front-end với rất nhiều các công nghệ mới. Nó giúp cho chúng ta phát triển, quản lý Front-end một cách dễ dàng hơn. Bắt đầu chính là bước cài đặt và bật server lên để chạy thử. Tất cả đều có tại github. Bây giờ hãy lần lượt làm theo các bước ở đó.

Yêu cầu

  • node ^4.5.0
  • yarn ^0.17.0 or npm ^3.0.0

Ở đây thì mính sử dụng npm để thực hiện việc demo này

Cài đặt và khởi động

Clone project về

git clone https://github.com/davezuko/react-redux-starter-kit.git <my-project-name>

Khi đã lấy được code về thì việc của các bạn là cài đặt nó

cd <my-project-name>
npm install

Sau đó bật server lên:

npm start

Như các bạn nhìn thấy, dưới đây chình là trang chủ http://localhost:3000/ khi khởi động và chạy ứng dụng. Dưới đây là trang Counter có sẵn trong project: Trong đó có 2 chức năng chính là Increment để tăng Counter lên 1 đơn vị và Double để nhân đôi Counter. Giờ chúng ta hãy xem để làm được việc render view và thực hiện 2 hàm đó thì chúng ta cần những gì ở trong project nhé.

Header

Để biết được trang nào được dùng những thành phần nào thì ta thường sẽ tìm ở trong routes. Với bộ công cụ này thì nó được đặt ở src/routes. Khi mở thư mục này ra ta sẽ thấy như thế này

├── Counter
│   ├── components
│   │   └── Counter.js
│   ├── containers
│   │   └── CounterContainer.js
│   ├── index.js
│   └── modules
│       └── counter.js
├── Home
│   ├── assets
│   │   └── Duck.jpg
│   ├── components
│   │   ├── HomeView.js
│   │   └── HomeView.scss
│   └── index.js
└── index.js

File src/routes/index.js là file khai báo các routes của project.

# src/routes/index.js
import CoreLayout from '../layouts/CoreLayout'
import Home from './Home'
import CounterRoute from './Counter'


export const createRoutes = (store) => ({
  path        : '/',
  component   : CoreLayout,
  indexRoute  : Home,
  childRoutes : [
    CounterRoute(store)
  ]
})

export default createRoutes

Như các bạn thấy nội dung của file này:

  • import các thành phần cần sử dụng
  • khai báo indexRoute và các childRoutes

Hầu như cấu trúc nội dung các file js đều như vậy, đều phải import các thành mình muốn dùng vào chứ không có tự động load. Với mỗi routes con thì đề để trong thư mục của nó và có file index.js trong đó để định nghĩa. Ví dụ như

# src/routes/Home/index.js
import CoreLayout from '../layouts/CoreLayout'
import Home from './Home'
import CounterRoute from './Counter'


export const createRoutes = (store) => ({
  path        : '/',
  component   : CoreLayout,
  indexRoute  : Home,
  childRoutes : [
    CounterRoute(store)
  ]
})

export default createRoutes

# src/routes/Counter/index.js
import { injectReducer } from '../../store/reducers'

export default (store) => ({
  path : 'counter',
  getComponent (nextState, cb) {

    require.ensure([], (require) => {
      const Counter = require('./containers/CounterContainer').default
      const reducer = require('./modules/counter').default

      injectReducer(store, { key: 'counter', reducer })

      cb(null, Counter)

    }, 'counter')
  }
})

Giờ chúng ta nhìn vào thư mục layouts:

# index.js
import CoreLayout from './CoreLayout'

export default CoreLayout
#CorLayout
import React from 'react'
import Header from '../../components/Header'
import './CoreLayout.scss'
import '../../styles/core.scss'

export const CoreLayout = ({ children }) => (
  <div className='container text-center'>
    <Header />
    <div className='core-layout__viewport'>
      {children}
    </div>
  </div>
)

CoreLayout.propTypes = {
  children : React.PropTypes.element.isRequired
}

export default CoreLayout

Cũng giống như layout của các framword khác. Ở đây ta thiết lập view default như header, footer ... và render các thành phần con trong một thẻ div riêng.

<Header />

Dòng này chính là gọi ra Header đã import ở trên import Header from '../../components/Header' Khi khởi động server thì hệ thống sẽ load file src/components/Header/index.js để build. Và khi có thay đổi trong file đó thì hệ thống sẽ build lại nhưng mà bạn xóa nó đi thì hệ thống sẽ lấy config cũ chứ ko load lại file này. Chính vì thế mà khi bạn xóa hay code lỗi ở đó thì sẽ có bug nhưng khi bạn xóa nó đi thì sẽ không ảnh hưởng gì trừ khi bạn khởi động lại server. Và layout này sẽ được render vào trong div id=root trong file:

# src/index.html
<!doctype html>
<html lang="en">
<head>
  <title>React Redux Starter Kit</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
<body>
  <div id="root" style="height: 100%"></div>
</body>
</html>

Counter

├── Counter
│   ├── components
│   │   └── Counter.js
│   ├── containers
│   │   └── CounterContainer.js
│   └── modules
│       └── counter.js
│   ├── index.js
# src/routes/Counter/components/Counter.js
import React from 'react'

export const Counter = (props) => (
  <div style={{ margin: '0 auto' }} >
    <h2>Counter: {props.counter}</h2>
    <button className='btn btn-default' onClick={props.increment}>
      Increment
    </button>
    {' '}
    <button className='btn btn-default' onClick={props.doubleAsync}>
      Double (Async)
    </button>
  </div>
)

Counter.propTypes = {
  counter     : React.PropTypes.number.isRequired,
  doubleAsync : React.PropTypes.func.isRequired,
  increment   : React.PropTypes.func.isRequired
}

export default Counter

Đây chính là file bạn định nghĩa cấu trúc của 1 Counter và view để render vào phần {children} trong CoureLayout. Như các bạn đã thấy thì ở đây 1 đối tượng Counter có 3 thành phần chính.

  • counter: biến có kiểu là number
  • doubleAsync: hàm chức năng
  • increment: hàm chức năng

Và ở phần view hiển thị sẽ sử dụng những biến và hàm này để thực hiện. Ta xem nội dung ở 2 file sau:

# src/routes/Counter/containers/CounterContainer.js
import { connect } from 'react-redux'
import { increment, doubleAsync } from '../modules/counter'

import Counter from '../components/Counter'


const mapDispatchToProps = {
  increment : () => increment(2),
  doubleAsync
}

const mapStateToProps = (state) => ({
  counter : state.counter
})

export default connect(mapStateToProps, mapDispatchToProps)(Counter)
# src/routes/Counter/modules/counter.js
// ------------------------------------
// Constants
// ------------------------------------
export const COUNTER_INCREMENT = 'COUNTER_INCREMENT'
export const COUNTER_DOUBLE_ASYNC = 'COUNTER_DOUBLE_ASYNC'

// ------------------------------------
// Actions
// ------------------------------------
export function increment (value = 1) {
  return {
    type    : COUNTER_INCREMENT,
    payload : value
  }
}

export const doubleAsync = () => {
  return (dispatch, getState) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        dispatch({
          type    : COUNTER_DOUBLE_ASYNC,
          payload : getState().counter
        })
        resolve()
      }, 200)
    })
  }
}

export const actions = {
  increment,
  doubleAsync
}

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [COUNTER_INCREMENT]    : (state, action) => state + action.payload,
  [COUNTER_DOUBLE_ASYNC] : (state, action) => state * 2
}

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = 0
export default function counterReducer (state = initialState, action) {
  const handler = ACTION_HANDLERS[action.type]

  return handler ? handler(state, action) : state
}

Như ta thấy, các hàm incrementdoubleAsync được định nghĩa trong # src/routes/Counter/modules/counter.js. Vậy khi trên view gọi nó có gọi vào đó ko? Câu trả lời là: Có, nhưng nó gọi thông qua connect(mapStateToProps, mapDispatchToProps)(Counter) trong # src/routes/Counter/containers/CounterContainer.js. Nó sẽ coi như bạn gửi 1 yêu cầu với 2 tham số là actionstate. Khi bạn gọi props.increment hay props.doubleAsync thì nó sẽ truy nhập vào

# src/routes/Counter/containers/CounterContainer.js
const mapDispatchToProps = {
  increment : () => increment(2),
  doubleAsync
}

hay props.counter

# src/routes/Counter/containers/CounterContainer.js
const mapStateToProps = (state) => ({
  counter : state.counter
})

Biến counter ở đấy là của props chứ ko phải là đối tượng Counter. Giá trị của counter trong Counter khác với trong props. Ví dụ khi ta thay đổi như sau:

# src/routes/Counter/containers/CounterContainer.js
const mapStateToProps = (state) => ({
  counter : state.counter + 3
})

=>

  • Nếu counter trong Counter có giá trị là 1 thì counter trong props có giá trị là 4

=> Trên view sẽ hiển thị là 4 Video demo dưới đây thể hiện rõ điều mình nói ở trên.

Tài liệu tham khảo

[https://github.com/davezuko/react-redux-starter-kit] (https://github.com/davezuko/react-redux-starter-kit)


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí