Một số bạn mới tiếp cận với react hoặc react hook sẽ cảm thấy việc truyền dữ liệu qua các component thật phức tạp.

Vậy liệu có cách nào đó có thể lấy dữ liệu trực tiếp từ một “nơi nào đó“, và thay đổi store tổng từ bất kỳ “nơi nào đó” mà không cần truyền qua lại giữa các component không?

Câu trả lời chắc chắn là có rồi, ai đã từng làm reactjs mà không dùng qua redux để quản lý store một lần thì thật thiếu sót.

React Redux cung cấp một tập hợp các Hook như một sự thay thế connect() higher order component. Các Hook này cho phép bạn kết nối với Redux store và gửi các actions mà không cần phải bọc component trong connect ().

Hướng dẫn này sẽ trình bày cách triển khai React-Redux Hooks, useSelector và useDispatch, trong ứng dụng của bạn.

Chuẩn bị

Khởi tạo project

npm install redux
npm install react-redux

 

Đây là hình ảnh demo cho project:

Techmaster Vietnam

Có hai trạng thái state riêng biệt, một để theo dõi bộ đếm và một để theo dõi người dùng đã đăng nhập hay chưa. Chúng ta sẽ có các tệp riêng biệt để xử lý từng trạng thái.

Tạo folder actions và reducers

Techmaster Vietnam

 

 

Actions

Tại actions của bộ đếm counterActions.js. chúng ta tạo 2 action cần thiết, tăng increment và giảm decrement. Chúng ta sẽ export chúng ra

const increment = () => {
    return {
        type: "INCREMENT"
    }
}

const decrement = () => {
    return {
        type: "DECREMENT"
    }
}

export default {
    increment,
    decrement
}

Tương tự, tại userActions.js, chúng ta cũng tạo 2 action setUser và logOut và cũng export chúng ra.

const setUser = (userObj) => {
    return {
        type: "SET_USER",
        payload: userObj
    }
}

const logOut = () => {
    return {
        type: "LOG_OUT"
    }
}

export default {
    setUser,
    logOut
}

Chúng tôi nhập hai tệp này vào một vị trí, tệp index.js trong thư mục .actions. Và tất nhiên, chúng ta sẽ tạo 1 biến allActions để chứa tất cả các actions rồi export ra để sử dụng.


import counterActions from './counterActions'
import userActions from './userActions'

const allActions = {
    counterActions,
    userActions
}

export default allActions

Reducers

Tương tự như cách xây dựng actions, chúng ta tạo cấu trúc cho reducers. Hãy bắt đầu với counter reducer, counter.js

Một funtion của reducer có 2 tham số, state và action, giá trị mặc định của state không nhất thiết phải là một object, trong trường hợp này giá trị mặc định là một number.

Như chúng ta đã định nghĩa trước đó, một action sẽ trả về một object chứa 2 key giá trị “ type “ và “payload". Dựa vào action type thì giá trị của state sẽ được thay đổi phù hợp. Hãy nhớ rằng luôn có default case trong trường hợp action type được gọi không tồn tại trong reducer để app của chúng ta không bị chết.

const counter = (state = 1, action) => {
    switch(action.type){
        case "INCREMENT":
            return state + 1
        case "DECREMENT":
            return state - 1
        default: 
            return state
    }
}

export default counter

Đối với user reducer, currentUser.js, state sẽ được set một object trống chứa keys user và loggedIn. Để ý sự khác nhau của giá trị trả về giữa counter và currentUser. Trong tất cả trường hợp, counter reducer luôn trả về một number, còn user reducer luôn trả về object.

const currentUser = (state = {}, action) => {
    switch(action.type){
        case "SET_USER":
            return {
                ...state,
                user: action.payload,
                loggedIn: true
            }
        case "LOG_OUT":
            return {
                ...state,
                user: {},
                loggedIn: false
            }
        default:
            return state
    }
}

export default currentUser;

 

Chúng ta cần combine gộp tất cả các reducer này vào 1 file, Trong reducer/index.js,  tất cả các file vào combineReducers như bên dưới.

import {combineReducers} from 'redux'

 

import currentUser from './currentUser'
import counter from './counter'
import {combineReducers} from 'redux'

const rootReducer = combineReducers({
    currentUser,
    counter
})

export default rootReducer

Combine reducer có 1 tham số, đó là một object chứa các tập tin. Đến đây, để các actions và reducers của chúng ta hoạt động, hãy triển khi redux của chúng ta vào trong app

Thực hiện redux

Tại file src/index.js, chúng ta import các file như bên dưới

import {Provider} from 'react-redux';

import {createStore} from 'redux'

import rootReducer from './reducers'

Một redux store sẽ được tạo với phương thức createStore. Hàm này có 2 phương thức, rootReducer hàm đã combined các reducers của chúng ta và Redux devtools extension

import {createStore} from 'redux'
import rootReducer from './reducers'
import {Provider} from 'react-redux'

const store = createStore(
    rootReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 
)

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));

Cuối cùng, chúng ta bọc App với Provider component của react-redux. Provider component truyền một props là store, store này sẽ được set tới store của chúng ta thông qua createStore.

Thực hiện useSelector/useDispatch

Trước kia, ta phải import connect() từ react-redux và bọc component vào để map state to props và map dispatch to props,  bây giờ mọi chuyện đơn giản hơn nhiều

useSelector

Tương tự  việc map dispatch to props, thì bây giờ chúng ta có useSelector. Nó nhận trong một đối số hàm trả về một phần của trạng thái mà bạn muốn. Trong trường hợp này, chúng ta có các khóa sau từ trạng thái được xác định, counter and currentUser. 

const counter = useSelector(state => state.counter)
// 1
const currentUser = useSelector(state => state.currentUser)
// {}

Do đó, các biến counter và currentUser được đặt thành trạng thái được xác định từ các bộ giảm tương ứng của chúng.

useDispatch

Tương tự  việc map dispatch to props, thì bây giờ chúng ta có hook useDispatch. Ta sẽ gọi useDispatch và lưu chúng bằng một biến tên là dispatch. Dispatch sẽ hoạt động với allActions được nhập từ actions folder. 

Ví dụ, trong useEffect gọi một dispatch với action allActions.userActions.setUser(user). 

Với user như sau:

const user = {name: "Rei"}

Hãy nhớ rằng, allActions là một đối tượng với 2 cụm action userActions và counterActions làm key. 

ở đây chúng ta có functions setUser trong userActions.js

const setUser = (userObj) => {
   return {
      type: "SET_USER",
      payload: userObj
    }
}

setUser  return về một object với 2 key là type và payload. Dispatch sẽ lấy object này truyền qua reducers, trong reducers sẽ so sánh action type. Cụ thể trong trường hợp này:

case "SET_USER":
   return {
   ...state,
   user: action.payload,
   loggedIn: true
}
import React, {useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux'
import './App.css';
import allActions from './actions'


const App = () => {
  const counter = useSelector(state => state.counter)
  const currentUser = useSelector(state => state.currentUser)

  const dispatch = useDispatch()

  const user = {name: "Rei"}

  useEffect(() => {
    dispatch(allActions.userActions.setUser(user))
  }, [])

  return (
    <div className="App">
      {
        currentUser.loggedIn ? 
        <>
          <h1>Hello, {currentUser.user.name}</h1>
          <button onClick={() => dispatch(allActions.userActions.logOut())}>Logout</button>
        </> 
        : 
        <>
          <h1>Login</h1>
          <button onClick={() => dispatch(allActions.userActions.setUser(user))}>Login as Rei</button>
        </>
        }
      <h1>Counter: {counter}</h1>
      <button onClick={() => dispatch(allActions.counterActions.increment())}>Increase Counter</button>
      <button onClick={() => dispatch(allActions.counterActions.decrement())}>Decrease Counter</button>
    </div>
  );
}

export default App;

Yes, đến đây thì bạn đã done. Các hook của React-Redux useSelector và useDispatch được triển khai trong React App.

So với connect(), thì các hook này dễ dùng và sáng sủa hơn nhiều.

Chúc các bạn thành công!

Tham khảo source git tại đây: https://github.com/reireynoso/react-redux-hooks-practice

Nguồn bài viết: tại đây.