Bài tập cơ bản về redux cho react native

Redux là gì? Vì sao người ta thường dùng Redux cùng ReactJS? Cùng 200Lab cập nhật kiến thức Redux cơ bản & ứng dụng hữu ích nhé!

1. Redux là gì?

Redux là một thư viện quản lý trạng thái (state management) tương thích với các ứng dụng web, phổ biến trong việc phát triển ứng dụng front-end sử dụng JavaScript và ReactJS.

Đây là một công cụ hữu ích giúp bạn xây dựng các ứng dụng có tính nhất quán, hoạt động linh hoạt trên nhiều môi trường (client, server và native), và dễ dàng kiểm thử. Sáng tạo từ ngôn ngữ Elm và kiến trúc Flux của Facebook, Redux thường được kết hợp sử dụng cùng với React.

Redux có nhiệm vụ quản lý trạng thái phức tạp trong ứng dụng web, giúp tách biệt logic và giao diện người dùng. Với tiếp cận dễ hiểu và theo dõi, Redux giúp bạn theo dõi và cập nhật trạng thái một cách hiệu quả, đồng thời đảm bảo tính nhất quán cho dữ liệu trong ứng dụng, từ đó dễ dàng debug khi gặp lỗi.

Photo by Clint Patterson / Unsplash

Nếu bạn muốn tìm hiểu thêm về Redux, bạn có thể xem qua các bài viết từ trang Blog Lập Trình & Dữ Liệu của 200Lab nhé.

2. Tại sao lập trình viên nên dùng Redux?

Khi ứng dụng web Frontend của chúng ta ngày càng lớn, có thêm nhiều component hơn, đồng nghĩa với việc sẽ sinh ra nhiều state. Có những state chúng ta sử dụng trong phạm vi component hiện tại, nhưng sẽ có những state ta sẽ sử dụng ở nhiều component khác nhau, và các component mà ta sử dụng nằm rải rác trong source code của chúng ta.

Việc truyền state thông qua props theo cách truyền thống từ cha xuống con sẽ không còn hiệu quả nếu như 2 component cần sử dụng chung 1 state nằm quá xa nhau. Trong thực tế chúng ta sẽ gặp trường hợp sau:

Bài tập cơ bản về redux cho react native
Trường hợp hay gặp trong thực tế

Nếu như chưa biết đến Redux thì giải pháp cho trường hợp trên của các bạn là gì? Làm sao để cả Admin.jsx với News.jsx đều truy cập được state userDetail?

Hướng dẫn truy cập state userDetail trên Redux

1. Call API ở những nơi cần sử dụng

Cách đầu tiên là làm tương tự như ở file Header.jsx, đấy là component nào cần sử dụng userDetail thì ta call api ở component đấy. Đây là cách nhanh và đơn giản nhất nhưng nó lại có vài vấn đề. Đó là nó làm api bị call nhiều lần.

Bạn thử tưởng tượng Header nằm trong trang Admin, khi 2 component này được render, nó sẽ call đến api kia một lần, và mỗi khi 1 trong 2 component này re-render, nó sẽ lại call tiếp.

Việc này ảnh hưởng rất lớn đến performance của website, khiến website của chúng ta chậm đi đáng kể. Nên đây không phải là cách chúng ta nên làm đúng không?

2. Lưu dữ liệu call từ api xuống localStorage

Cách thứ hai có thể nghĩ đến là lưu dữ liệu call từ api xuống localStorage, component nào cần dùng thì gọi ra để sử dụng là xong.

Cách này sẽ giải quyết được vấn đề của cách đầu tiên, là api sẽ không bị gọi đi gọi lại nhiều lần không cần thiết, nhưng nó lại sinh ra 2 vấn đề:

  • Một là làm website của chúng ta chậm lại vì việc format dữ liệu từ localStorage, ta phải sử dụng đến hàm stringify và parse để xử lý dữ liệu khi lưu xuống và lấy ra từ localStorage.
  • Hai là dữ liệu từ localStorage sẽ không tự đồng bộ ở tất cả component khi giá trị của state đó bị thay đổi.

Từ những vấn đề trên mà state management như Redux ra đời, Redux đã khắc phục được toàn bộ nhược điểm bên trên, giúp quản lý state hiệu quả hơn rất nhiều.

3. Redux hoạt động như thế nào?

Vậy để quản lý các state hiệu quả thì Redux đã làm gì?

Redux sẽ đưa tất cả các state vào một kho lưu trữ chung gọi là Store, khi component nào đó muốn sử dụng state có trong Store thì sẽ gọi vào Store thông qua Reducer. Và để thực hiện hành động đối với các state này thì chúng ta sẽ tạo ra các Action.

Vậy là sẽ có 3 thành phần chính của Redux mà chúng ta cần quan tâm đó là: Store, Reducer và Action.

3.1. Store trên Redux là gì?

Store là nơi Redux dùng để lưu toàn bộ state của ứng dụng. Nó giống như một cái nhà kho vậy, khi một component có nhu cầu sử dụng state nào đấy chỉ cần vào trong store để lấy ra.

3.2. Reducer trên Redux là gì?

Là các hàm xử lý các hành động cụ thể và thay đổi state của ứng dụng. Reducer nhận vào giá trị của state hiện tại và hành động bạn muốn thực hiện, rồi trả ra state mới dựa trên hành động đó.

3.3. Action trên Redux là gì?

Là hành động mà bạn muốn thực hiện với state đó. Ví dụ đơn giản nhất là việc thay đổi giá trị của state.

Redux còn có thêm 1 thành phần phụ nữa là Middleware.

3.4. Tìm hiểu về Middleware trên Redux

Là các lớp trung gian giúp xử lý các hành động trước khi chúng đến Reducer. Middleware thường được sử dụng để thực hiện các tác vụ như gọi API, xử lý bất đồng bộ,...

Dưới đây là hình ảnh miêu tả cách hoạt động của Redux:

Bài tập cơ bản về redux cho react native
Cách hoạt động của Redux. Nguồn: Internet

Luồng xử lý của Redux như sau:

  • Trường hợp không sử dụng Middleware: State được khởi tạo bên trong Store -> State được đưa vào Reducer -> Reducer khởi tạo giá trị state ban đầu (initialState) -> Thực hiện action ở component (dispatch event) -> Thay đổi giá trị của state bên trong Reducer thành state mới -> Đẩy state mới ra ngoài View (component)
  • Trường hợp sử dụng Middleware: State được khởi tạo bên trong Store -> State được đưa vào Reducer -> Reducer khởi tạo giá trị state ban đầu (initialState) -> Thực hiện action ở component (dispatch event) -> Gọi API ở Middleware -> Đưa dữ liệu vừa gọi vào Reducer -> Thay đổi giá trị của state bên trong Reducer thành state mới -> Đẩy state mới ra ngoài View (component).

Các bạn có thể xem cách setup một ứng dụng To do list sử dụng Redux ở ví dụ dưới đây:

Ứng dụng Todo list sử dụng Redux

Vậy luồng xử lý của Redux trong ứng dụng TodoList này hoạt động như thế nào?

  • Khai báo constants: Để code của chúng ta trong gọn và dễ maintain hơn, việc đầu tiên chúng ta cần làm là khai báo các constant cần sử dụng với Redux. Các constants được khai báo ở file src/redux/actionTypes.js
  • Khởi tạo state: giá trị state ban đầu của redux sẽ được khai báo ở src/redux/reducer.js
  • Khởi tạo các reducer: Khởi tạo reducer với tên các hành động lấy từ src/redux/actionTypes.js.
  • Khởi tạo các actions: Các action được khởi tạo ở src/redux/actions.js

import { ADD_TODO, TOGGLE_STATUS, DELETE_TODO, EDIT_TODO } from "./actionTypes";
const initState = {
  todo: []
};
export const reducer = (state = initState, { type, payload }) => {
  switch (type) {
    case ADD_TODO:
      return {
        todo: [...state.todo, payload]
      };
    case TOGGLE_STATUS:
      return {
        ...state,
        todo: payload
      };
    case DELETE_TODO:
      return {
        ...state,
        todo: payload
      };
    default:
      return state;
  }
};
export default reducer;

reducer.js

Giải thích các Reducer:

  • ADD_TODO: Khi user gửi action tên là ADD_TODO, reducer sẽ trả về một state mới với danh sách todo được cập nhật bằng cách thêm payload (todo mới) vào cuối danh sách hiện có.
  • `

    import { nanoid } from "nanoid"; import { useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { addTodo, toggleStatus, deleteTodo } from "../redux/actions"; export default function Todo() { const [text, setText] = useState(""); const { todo } = useSelector((store) => store); const dispatch = useDispatch(); const handelChange = (e) => {

    setText(e.target.value);  
    
    }; const handleClick = (e) => {
    var todoData = {  
      id: nanoid(),  
      title: text,  
      status: false,  
      edit: false  
    };  
    dispatch(addTodo(todoData));  
    
    }; const handleToggle = (id) => {
    const todoData = todo.map((item) => {  
      if (item.id === id) {  
        return {  
          ...item,  
          status: !item.status  
        };  
      }  
      return item;  
    });  
    dispatch(toggleStatus(todoData));  
    
    }; const handleDelete = (id) => {
    const todoData = todo.filter((item) => {  
      return item.id !== id;  
    });  
    dispatch(deleteTodo(todoData));  
    
    }; return (
    <div>  
      <div>  
        <input value={text} onChange={handelChange} type="text" />  
        <button onClick={handleClick}>Add</button>  
      </div>  
      <div>  
        {todo.map((item) => (  
          <div key={item.id}>  
            <p>{item.title}</p>  
            <p>{item.status ? "Completed" : "Not Completed"}</p>  
            <button  
              onClick={() => {  
                handleToggle(item.id);  
              }}  
    Toggle </button> <button onClick={() => { handleDelete(item.id); }}
          Delete  
        </button>  
        {/ <button onClick={handleEdit}>Edit</button> /}  
      </div>  
    ))}  
  </div>  
</div>  
); }

1 :Khi user gửi action tên là

import { nanoid } from "nanoid"; import { useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { addTodo, toggleStatus, deleteTodo } from "../redux/actions"; export default function Todo() { const [text, setText] = useState(""); const { todo } = useSelector((store) => store); const dispatch = useDispatch(); const handelChange = (e) => {

setText(e.target.value);  
}; const handleClick = (e) => {
var todoData = {  
  id: nanoid(),  
  title: text,  
  status: false,  
  edit: false  
};  
dispatch(addTodo(todoData));  
}; const handleToggle = (id) => {
const todoData = todo.map((item) => {  
  if (item.id === id) {  
    return {  
      ...item,  
      status: !item.status  
    };  
  }  
  return item;  
});  
dispatch(toggleStatus(todoData));  
}; const handleDelete = (id) => {
const todoData = todo.filter((item) => {  
  return item.id !== id;  
});  
dispatch(deleteTodo(todoData));  
}; return (
<div>  
  <div>  
    <input value={text} onChange={handelChange} type="text" />  
    <button onClick={handleClick}>Add</button>  
  </div>  
  <div>  
    {todo.map((item) => (  
      <div key={item.id}>  
        <p>{item.title}</p>  
        <p>{item.status ? "Completed" : "Not Completed"}</p>  
        <button  
          onClick={() => {  
            handleToggle(item.id);  
          }}  
          Toggle  
        </button>  
        <button  
          onClick={() => {  
            handleDelete(item.id);  
          }}  
          Delete  
        </button>  
        {/ <button onClick={handleEdit}>Edit</button> /}  
      </div>  
    ))}  
  </div>  
</div>  
); }

1, reducer chỉ cập nhật lại mảng todo bằng payload (mảng mới) để thay đổi trạng thái của todo (đã hoàn thành hay chưa).

  • import { nanoid } from "nanoid"; import { useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { addTodo, toggleStatus, deleteTodo } from "../redux/actions"; export default function Todo() { const [text, setText] = useState(""); const { todo } = useSelector((store) => store); const dispatch = useDispatch(); const handelChange = (e) => {

    setText(e.target.value);  
    
    }; const handleClick = (e) => {
    var todoData = {  
      id: nanoid(),  
      title: text,  
      status: false,  
      edit: false  
    };  
    dispatch(addTodo(todoData));  
    
    }; const handleToggle = (id) => {
    const todoData = todo.map((item) => {  
      if (item.id === id) {  
        return {  
          ...item,  
          status: !item.status  
        };  
      }  
      return item;  
    });  
    dispatch(toggleStatus(todoData));  
    
    }; const handleDelete = (id) => {
    const todoData = todo.filter((item) => {  
      return item.id !== id;  
    });  
    dispatch(deleteTodo(todoData));  
    
    }; return (
    <div>  
      <div>  
        <input value={text} onChange={handelChange} type="text" />  
        <button onClick={handleClick}>Add</button>  
      </div>  
      <div>  
        {todo.map((item) => (  
          <div key={item.id}>  
            <p>{item.title}</p>  
            <p>{item.status ? "Completed" : "Not Completed"}</p>  
            <button  
              onClick={() => {  
                handleToggle(item.id);  
              }}  
          Toggle  
        </button>  
        <button  
          onClick={() => {  
            handleDelete(item.id);  
          }}  
          Delete  
        </button>  
        {/ <button onClick={handleEdit}>Edit</button> /}  
      </div>  
    ))}  
  </div>  
</div>  
); }

3: Khi user gửi action tên là

import { nanoid } from "nanoid"; import { useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { addTodo, toggleStatus, deleteTodo } from "../redux/actions"; export default function Todo() { const [text, setText] = useState(""); const { todo } = useSelector((store) => store); const dispatch = useDispatch(); const handelChange = (e) => {

setText(e.target.value);  
}; const handleClick = (e) => {
var todoData = {  
  id: nanoid(),  
  title: text,  
  status: false,  
  edit: false  
};  
dispatch(addTodo(todoData));  
}; const handleToggle = (id) => {
const todoData = todo.map((item) => {  
  if (item.id === id) {  
    return {  
      ...item,  
      status: !item.status  
    };  
  }  
  return item;  
});  
dispatch(toggleStatus(todoData));  
}; const handleDelete = (id) => {
const todoData = todo.filter((item) => {  
  return item.id !== id;  
});  
dispatch(deleteTodo(todoData));  
}; return (
<div>  
  <div>  
    <input value={text} onChange={handelChange} type="text" />  
    <button onClick={handleClick}>Add</button>  
  </div>  
  <div>  
    {todo.map((item) => (  
      <div key={item.id}>  
        <p>{item.title}</p>  
        <p>{item.status ? "Completed" : "Not Completed"}</p>  
        <button  
          onClick={() => {  
            handleToggle(item.id);  
          }}  
          Toggle  
        </button>  
        <button  
          onClick={() => {  
            handleDelete(item.id);  
          }}  
          Delete  
        </button>  
        {/ <button onClick={handleEdit}>Edit</button> /}  
      </div>  
    ))}  
  </div>  
</div>  
); }

` 3, reducer cũng chỉ cập nhật lại mảng todo bằng payload mới để xoá công việc khỏi danh sách.

import { nanoid } from "nanoid";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addTodo, toggleStatus, deleteTodo } from "../redux/actions";
export default function Todo() {
  const [text, setText] = useState("");
  const { todo } = useSelector((store) => store);
  const dispatch = useDispatch();
  const handelChange = (e) => {
    setText(e.target.value);
  };
  const handleClick = (e) => {
    var todoData = {
      id: nanoid(),
      title: text,
      status: false,
      edit: false
    };
    dispatch(addTodo(todoData));
  };
  const handleToggle = (id) => {
    const todoData = todo.map((item) => {
      if (item.id === id) {
        return {
          ...item,
          status: !item.status
        };
      }
      return item;
    });
    dispatch(toggleStatus(todoData));
  };
  const handleDelete = (id) => {
    const todoData = todo.filter((item) => {
      return item.id !== id;
    });
    dispatch(deleteTodo(todoData));
  };
  return (
    <div>
      <div>
        <input value={text} onChange={handelChange} type="text" />
        <button onClick={handleClick}>Add</button>
      </div>
      <div>
        {todo.map((item) => (
          <div key={item.id}>
            <p>{item.title}</p>
            <p>{item.status ? "Completed" : "Not Completed"}</p>
            <button
              onClick={() => {
                handleToggle(item.id);
              }}
Toggle </button> <button onClick={() => { handleDelete(item.id); }} Delete </button> {/ <button onClick={handleEdit}>Edit</button> /} </div> ))} </div> </div> ); }

Todo.jsx

Các hàm handleClick(), handleToggle(), handleDelete() đều xử lý dữ liệu todo rồi trả về mảng todo mới, sau đó cập nhật vào Redux thông qua việc dispatch một action.

Photo by Fotis Fotopoulos / Unsplash

Redux Toolkit là thư viện được phát triển bởi ReduxJS, giúp viết mã Redux nhanh chóng, toàn diện và tuân thủ chuẩn mực. Nó giải quyết vấn đề phức tạp của Redux và cung cấp API tiện ích để viết mã ngắn gọn, dễ đọc hơn và tuân theo các mẫu thiết kế tốt nhất.

Một số lập trình viên cảm thấy Redux hơi dài dòng và khó sử dụng. Tạo một store hoàn chỉnh đòi hỏi nhiều bước và tạo nhiều tệp lặp đi lặp lại. Redux Toolkit được ra đời, nhằm tối giản hoá cách setup và sử dụng, giúp lập trình viên có thể tập trung hơn vào việc xử lý logic thay vì mất quá nhiều thời gian ban đầu để set-up.

Hiện tại Redux Toolkit đang được sử dụng rất phổ biến, các bạn có thể xem ví dụ về một ứng dụng Todo list sử dụng Redux Toolkit dưới đây:

Ứng dụng To do list sử dụng Redux Toolkit

Trông đơn giản hơn rất nhiều đúng không nè?

5. Lưu ý khi sử dụng Redux

  • Giá trị lưu trong Redux về bản chất vẫn là state nên nó thừa hưởng mọi tính chất của state, chỉ khác ở chỗ nó là global state (state toàn cục) nên khi giá trị lưu trong Redux thay đổi thì tất cả component đang sử dụng giá trị đó cũng sẽ thay đổi theo.
  • Chuẩn hoá dữ liệu (Normalized State) trước khi lưu chúng vào Redux để đảm bảo hiệu suất của ứng dụng đạt ở mức cao.
  • Nên thực hiện việc xử lý logic ở các hàm bên ngoài, hạn chế xử lý logic phức tạp bên trong Redux, Redux chỉ nên sử dụng để lưu dữ liệu cuối cùng sau khi đã qua xử lý.

6. Nên sử dụng Redux trong trường hợp nào?

Photo by Danial Igdery / Unsplash

Redux không phải lúc nào cũng cần thiết, việc xác định được ứng dụng web của bạn có cần sử dụng Redux hay không là rất quan trọng. Vì việc sử dụng Redux không đúng cách có thể làm ứng dụng của bạn chậm đi đáng kể, ảnh hưởng đến performance. Với những ứng dụng web nhỏ bạn có thể sử dụng state nội bộ hoặc các state management gọn nhẹ hơn như ContextAPI, Recoil,...

Vậy thì những trường hợp nào chúng ta nên sử dụng Redux?

  • Quản lý nhiều state phức tạp: Khi ứng dụng web của bạn cần quản lý nhiều state phức tạp và chúng cần tương tác với nhau, việc sử dụng Redux sẽ giúp bạn quản lý các state dễ dàng hơn
  • State cần chia sẻ global: Khi nhiều component cần sử dụng chung 1 state, việc sử dụng Redux sẽ giúp các component truy cập dễ dàng hơn so với việc sử dụng props
  • Lưu lại lịch sử của state và actions: Redux cho phép bạn theo dõi lịch sử của các state và actions, điều này rất hữu ích cho việc debug về sau. Redux còn có extension trên Chrome tên là Redux DevTools, giúp chúng ta có thể dễ dàng theo dõi lịch sử thay đổi của state và actions ngay trên trình duyệt.

7. Kết luận về Redux

Việc sử dụng Redux ngày nay đã trở nên rất phổ biến ở các ứng dụng web hiện đại, nó giúp hiệu suất ứng dụng web của chúng ta được cải thiện hơn rất nhiều cũng như giúp các lập trình viên có thể làm việc với nhau hiệu quả hơn, tốn ít công sức hơn.

(*) Bài viết này có tham khảo tài liệu tại đây.

Giờ thì bạn đã hiểu được khái niệm "Redux là gì?" cũng như cách hoạt động của Redux. Hãy theo dõi 200Lab để đọc thêm các bài viết hay nhé. Chúc bạn khám phá Redux thật vui vẻ.