Một ví dụ về cách sử dụng React Hook `useState` để Quản lý trạng thái của một component | Tài liệu môn an toàn thông tin Trường đại học sư phạm kỹ thuật TP. Hồ Chí Minh
Ở đây, `useState` là một hàm cho phép bạn thêm trạng thái React vào trong các functional components: - `count` là giá trị ện tại của trạng thái. `setCount` là một hàm mà khi được gọi, sẽ cập nhật trạng thái `count` và re-render component với giá trị mới. Component `Counter` hiển thị số lần mà nút đã được click và có ba nút: một để tăng giá trị của `count`, một để ảm nó, và một để reset giá trị về 0. Tài liệu giúp bạn tham khảo, ôn tập và đạt kết quả cao. Mời bạn đọc đón xem!
Môn: An toàn thông tin (INSE330380)
Trường: Đại học Sư phạm Kỹ thuật Thành phố Hồ Chí Minh
Thông tin:
Tác giả:
Preview text:
[1]
Dưới đây là một ví dụ về cách sử dụng React Hook `useState` để quản lý trạng thái của một
component. Trong ví dụ này, chúng ta sẽ tạo một counter đơn giản mà người dùng có thể tăng và giảm giá trị: ```jsx
import React, { useState } from 'react'; function Counter() {
// Khởi tạo trạng thái count với giá trị ban đầu là 0
const [count, setCount] = useState(0); return (
Bạn đã click {count} lần
setCount(count + 1)}> Click để tăng setCount(count - 1)}> Click để giảm setCount(0)}> Reset ); } export default Counter; ```
Ở đây, `useState` là một hàm cho phép bạn thêm trạng thái React vào trong các functional components:
- `count` là giá trị hiện tại của trạng thái.
- `setCount` là một hàm mà khi được gọi, sẽ cập nhật trạng thái `count` và re-render component với giá trị mới.
Component `Counter` hiển thị số lần mà nút đã được click và có ba nút: một để tăng giá trị của
`count`, một để giảm nó, và một để reset giá trị về 0. Khi nút được click, hàm `setCount` được
gọi và cập nhật giá trị của `count`, điều này kích hoạt React để re-render `Counter` với giá trị mới. 1
Để sử dụng component này trong ứng dụng của bạn, bạn có thể nhập nó vào trong file `App.js`
(hoặc tương đương) và render nó như một thành phần của giao diện: ```jsx import React from 'react';
import Counter from './Counter'; // Đường dẫn đến file Counter.js function App() { return (
Đây là ứng dụng đếm số ); } export default App; ```
Khi bạn chạy ứng dụng này, bạn sẽ thấy ột co m
unter với ba nút để điều khiển giá trị đếm. [2]
Trong React, "one-way data "
binding chỉ quá trình truyền dữ l ệu i từ component cha xuống
component con thông qua props. Trong khi đó, "two-way binding" thường liên quan đến việc
đồng bộ hóa trạng thái giữa component và form inputs, cho phép cập nhật trạng thái khi input
thay đổi và ngược lại.
Dưới đây là ví dụ về `useState` với one-way binding và two-way binding: ### One-Way Binding ```jsx
import React, { useState } from 'react';
function Display({ message }) {
return
{message}
; } function App() {
const [message, setMessage] = useState('Xin chào!'); return ( 2
setMessage('Chào bạn!')}>Thay đổi lời chào ); } export default App; ```
Trong ví dụ trên, chúng ta có một component `App` với trạng thái `message`. Component này
render `Display` component và truyền `message` như một prop. Khi người dùng click vào nút,
`message` sẽ được cập nhật thông qua `setMessage`, và `Display` component sẽ được tự động
cập nhật với giá trị mới của `message`. ### Two-Way Binding
Trong React, two-way binding có thể được thực hiện bằng cách sử dụng `useState` để quản lý
trạng thái của form input và cập nhật nó khi có sự kiện thay đổi trên input. ```jsx
import React, { useState } from 'react'; function Input() {
const [value, setValue] = useState('');
function handleChange(event) {
setValue(event.target.value); } return (
Giá trị nhập: {value}
); } export default Input; ```
Trong ví dụ trên, chúng ta có một component `Input` quản lý giá trị của một input. `value` là
trạng thái đang được quản lý bởi `useState`. `handleChange` là hàm được gọi mỗi khi input
thay đổi giá trị, nó cập nhật trạng thái `value` và giao diện sẽ được cập nhật để phản ánh trạng 3
thái mới. Input element có `value` được set từ trạng thái và `onChange` để xử lý sự kiện thay
đổi, tạo ra two-way binding giữa trạng thái và input.
Kết hợp cả hai ví dụ này vào một ứng dụng React sẽ cho bạn cả hai hình thức binding trong một context thực tế. [3]
`useEffect` là một hook trong React cho phép bạn thực hiện side effects trong functional
components. Side effects có thể bao gồm data fetching, subscriptions, hoặc thay đổi DOM mà
không phải là phản hồi trực tiếp từ việc render.
Dưới đây là một ví dụ cụ thể về cách sử dụng `useEffect` để giao tiếp với API và cập nhật state dựa trên phản hồi: ```jsx
import React, { useState, useEffect } from 'react'; function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true); useEffect(() => {
async function fetchUsers() { try {
let response = await fetch('https://jsonplaceholder.typicode.com/users');
let data = await response.json(); setUsers(data); setLoading(false); } catch (error) {
console.error('Error fetching data: ', error); setLoading(false); } } fetchUsers();
}, []); // Chỉ c ạy một lần sau khi component được moun h t if (loading) { return Loading...; } return (
- 4 {users.map(user => (
- {user.name} ))}
); } export default UserList; ``` Trong ví dụ này:
- `useEffect` được gọi với một hàm callback và một mảng rỗng. Mảng rỗng nghĩa là callback này chỉ sẽ c ạy h
một lần ngay sau khi component được mount (tương tự như
`componentDidMount` trong class components).
- Trong hàm callback, chúng ta gọi một hàm async `fetchUsers` để lấy ữ
d l ệu từ API và sau đó i
cập nhật trạng thái `users` với dữ l ệu nhận được và set `loading` thành `false`. i
- Nếu `loading` là `true`, chúng ta hiển thị một thông báo "Loading...". Khi dữ l ệu i đã sẵn sàng
và `loading` là `false`, chúng ta render ra danh sách các users.
Ví dụ này cho thấy cách `useEffect` được sử dụng để thực hiện các tác vụ không đồng bộ, như
việc fetch dữ l ệu từ một API, và cập nhật state của component dựa trên kết quả i nhận được. [4]
Dưới đây là một ví dụ nâng cao hơn sử dụng ` ` để useEffect
thực hiện việc fetch dữ l ệu, t i heo
dõi thay đổi của state và cleanup khi component bị unmount hoặc dependencies thay đổi: ```jsx
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(''); useEffect(() => {
const controller = new AbortController(); // Tạo một controller để có thể hủy ỏ request b
const { signal } = controller; async function fetchUser() { try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, { signal }); if (!response.ok) { 5
throw new Error('User not found'); }
const userData = await response.json(); setUser(userData); setError(''); } catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted'); } else { setError(err.message); setUser(null); } } } fetchUser(); return () => {
controller.abort(); // Hủy ỏ request khi component unmount hoặc khi userId thay đổ b i };
}, [userId]); // Chỉ c ạy khi userId thay đổi h if (error) { return Error: {error}; } if (!user) { return Loading...; } return ( {user.name}
Email: {user.email}
Phone: {user.phone}
{/* Render thêm thông tin user nếu muốn */} ); } export default UserProfile; ``` 6 Trong ví dụ này:
- Chúng ta sử dụng `AbortController` để hủy bỏ fetch request nếu component bị unmount
hoặc nếu `userId` thay đổi trước khi request hoàn thành. Điều này giúp tránh lỗi và rò rỉ bộ nhớ
khi cập nhật state của một component không còn tồn tại.
- `useEffect` có một hàm cleanup được trả về, được gọi trước khi component unmount hoặc
trước khi `useEffect` chạy lại do dependencies (`userId`) thay đổi. Hàm cleanup này sẽ thực hiện việc hủy request.
- Dependencies array `[userId]` nghĩa là `useEffect` sẽ c ạy h
lại mỗi khi `userId` thay đổi, đảm
bảo rằng thông tin user được cập nhật đúng đắn.
- Trong `catch` block, chúng ta kiểm tra xem lỗi có phải là `AbortError` không, nếu không phải
thì set lỗi vào state để thông báo cho người dùng.
- Component sẽ hiển thị thông tin user nếu có, hoặc một thông báo lỗi, hoặc trạng thái loading
tùy thuộc vào state hiện tại của fetch request. [5]
Hook `useCallback` trong React được sử dụng để bảo toàn cùng một hàm callback qua các
lần render khác nhau của một component, để tránh việc tái tạo không cần thiết khi sử dụng
hàm này làm props trong một component con hoặc trong một dependency array của hooks như `useEffect`.
Dưới đây là một ví dụ minh họa chi tiết về việc sử dụng `useCallback`: ```jsx
import React, { useState, useCallback } from 'react';
function Button({ onClick, children }) {
console.log(`Rendering button: ${children}`); return ( {children} ); }
// React.memo chỉ re-render component nếu props thay đổi.
const MemoizedButton = React.memo(Button); function Counter() {
const [count, setCount] = useState(0);
const [otherCounter, setOtherCounter] = useState(0);
// Sử dụng useCallback để đảm bảo rằng 'increment' không được tái tạo
// mỗi khi component re-render, trừ khi 'count' thay đổi.
const increment = useCallback(() => { 7 setCount(c => c + 1); }, [count]);
// Function 'incrementOtherCounter' không sử dụng useCallback.
// Điều này có nghĩa là nó sẽ được tái tạo mỗi lần Counter re-render.
const incrementOtherCounter = () => {
setOtherCounter(c => c + 1); }; return ( Count: {count} Increment Other Count: {otherCounter}
{/* 'MemoizedButton' sẽ re-render ở đây mỗi lần 'Counter' re-render
bởi vì 'incrementOtherCounter' không được bọc trong useCallback */} Increment Other ); } export default Counter; ``` Trong ví dụ này:
- `MemoizedButton` là một component được bọc trong `React.memo`, nó sẽ c ỉ h re-render nếu props của nó thay đổi.
- `increment` là một hàm được memoized với `useCallback`, nó chỉ được tạo lại khi `count` thay
đổi. Điều này giúp `MemoizedButton` không bị re-render không cần thiết khi `Counter` re-
render vì `increment` không thay đổi.
- `incrementOtherCounter` không sử dụng `useCallback`, nên nó sẽ được tạo mới mỗi lần
`Counter` re-render, điều này sẽ gây ra việc re-render không cần thiết của `MemoizedButton`
với callback này mỗi lần `Counter` re-render.
Sử dụng `useCallback` trong trường hợp này giúp tối ưu hiệu suất, đặc biệt là trong các trường
hợp mà việc tạo lại hàm có chi phí tính toán cao hoặc khi hàm được truyền vào các component
con có khả năng re-render thường xuyên. 8 [5]
Dưới đây là một ví dụ nâng
cao hơn, nó mô tả việc sử dụng ` `
useCallback trong một ứng
dụng todo list nhỏ. Chúng ta sẽ có một form để thêm công việc vào danh sách và một danh
sách công việc mà mỗi công việc có thể được đánh dấu là hoàn thành.
Ví dụ này sẽ sử dụng `useCallback` để tránh tái tạo các hàm xử lý trong mỗi render, giúp tối ưu
hiệu suất khi chúng ta truyền các hàm này như props đến các component con. ```jsx
import React, { useState, useCallback } from 'react';
// Một component item todo, nhận vào một callback và một item.
const TodoItem = React.memo(({ item, onToggle }) => {
console.log(`Rendering item: ${item.text}`); return (
onClick={() => onToggle(item.id)}
style={{ textDecoration: item.completed ? 'line-through' : 'none' }} > {item.text} ); });
// Form để thêm một todo mới.
const TodoForm = ({ onAdd }) => {
const [text, setText] = useState('');
const handleSubmit = (e) => { e.preventDefault(); onAdd(text); setText(''); }; return (
setText(e.target.value)} /> Add Todo ); };
// Component chính, quản lý state của todos. function TodoList() { 9
const [todos, setTodos] = useState([]);
// useCallback đảm bảo rằng hàm này không được tái tạo trừ khi
// danh sách todos thay đổi.
const toggleTodo = useCallback((id) => { setTodos((todos) => todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); }, []);
// Tương tự như trên, hàm này được memoized để không tái tạo mỗi lần render.
const addTodo = useCallback((text) => { setTodos((todos) => [ ...todos,
{ id: Date.now(), text: text, completed: false }, ]); }, []); return (
- {todos.map((item) => ( ))}
); } export default TodoList; ``` Trong ví dụ này:
- Mỗi `TodoItem` chỉ re-render khi nó được đánh dấu là hoàn thành hoặc khi văn bản của nó
thay đổi, nhờ vào `React.memo`.
- `onToggle` được định nghĩa bằng `useCallback` và sẽ không thay đổi giữa các lần render, giúp
tránh làm cho các `TodoItem` con re-render không cần thiết.
- `addTodo` cũng được định nghĩa bằng `useCallback` và sẽ không thay đổi trừ khi `todos` thay
đổi, điều này không bao giờ xảy ra bởi vì mảng `todos` luôn được xử lý bên trong hàm callback. 10
Đây là một ví dụ nâng cao hơn, nó không chỉ sử dụng `useCallback` để tối ưu hiệu suất, mà
còn minh họa việc sử dụng `React.memo` để tránh việc re-render không cần thiết của các component con. [6]
`useReducer` là một hook mà cung cấp một cách khác để quản lý state trong các thành phần
React. Nó thường được sử dụng khi logic quản lý state trở nên phức tạp hoặc khi tiếp theo là
một chuỗi các hành động có thể tái sử dụng.
Dưới đây là một ví dụ sử dụng `useReducer` để quản lý state trong một ứng dụng todo list đơn giản: ```jsx
import React, { useReducer } from 'react';
// Định nghĩa các hành động const actionTypes = { ADD: 'add', TOGGLE: 'toggle', DELETE: 'delete', };
// Reducer function để cập nhật state
function reducer(state, action) { switch (action.type) { case actionTypes.ADD:
return [...state, { id: Date.now(), text: action.text, completed: false }]; case actionTypes.TOGGLE: return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo ); case actionTypes.DELETE:
return state.filter(todo => todo.id !== action.id); default: throw new Error(); } } function TodoApp() {
// Khởi tạo useReducer với reducer function và initial state là một mảng trống
const [todos, dispatch] = useReducer(reducer, []); 11 // Xử lý khi submit form
function handleAddSubmit(event) { event.preventDefault();
const text = event.target.elements.todoText.value;
dispatch({ type: actionTypes.ADD, text });
event.target.elements.todoText.value = ''; } return ( Todos Add Todo
- {todos.map(todo => ( key={todo.id} style={{
textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => dispatch({ type: actionTypes.TOGGLE, id: todo.id })} > {todo.text}
dispatch({ type: actionTypes.DELETE, id: todo.id })}> Delete ))}
); } export default TodoApp; ``` Trong ví dụ trên:
- `reducer` là một hàm nhận vào state hiện tại và một action, sau đó trả về một state mới.
- `dispatch` là một hàm mà bạn có thể gọi để gửi một action đến reducer.
- `actionTypes` là một object định nghĩa các hằng số cho các loại hành động. 12
- `useReducer` được gọi với `reducer` và một state khởi tạo.
- Khi một todo được thêm, toggle hoặc delete, một hành động được dispatch với type tương
ứng và payload cần thiết.
Sử dụng `useReducer` giúp cho việc quản lý các actions và logic cập nhật state trở nên rõ ràng
và dễ quản lý hơn so với `useState`, đặc biệt khi số l ợng hành động tăng lên. ư [7]
Một ví dụ nâng cao về việc sử dụng ` ` có useReducer
thể bao gồm việc quản lý các trạng thái
phức tạp hơn và xử lý các side-effects. Dưới đây là một ví dụ về việc sử dụng `useReducer` kết
hợp với `useContext` để tạo ra một store trạng thái toàn cục cho ứng dụng, tương tự như Redux: ```jsx
import React, { useReducer, useContext, createContext } from 'react'; // Actions const actionTypes = { INCREMENT: 'increment', DECREMENT: 'decrement', RESET: 'reset', SET: 'set', }; // Tạo context
const CounterStateContext = createContext();
const CounterDispatchContext = createContext(); // Reducer function
function counterReducer(state, action) { switch (action.type) { case actionTypes.INCREMENT:
return { count: state.count + 1 }; case actionTypes.DECREMENT:
return { count: state.count - 1 }; case actionTypes.RESET: return { count: 0 }; case actionTypes.SET:
return { count: action.payload }; default:
throw new Error(`Unhandled action type: ${action.type}`); } } 13
// Custom hook để sử dụng Counter state function useCounterState() {
const context = useContext(CounterStateContext); if (context === undefined) {
throw new Error('useCounterState must be used within a CounterProvider'); } return context; }
// Custom hook để sử dụng Counter dispatch
function useCounterDispatch() {
const context = useContext(CounterDispatchContext); if (context === undefined) {
throw new Error('useCounterDispatch must be used within a CounterProvider'); } return context; } // Provider component
function CounterProvider({ children }) {
const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( {children} ); }
// Sử dụng trong một component function Counter() {
const { count } = useCounterState();
const dispatch = useCounterDispatch(); return ( <> Count: {count}
dispatch({ type: actionTypes.INCREMENT })}>+
dispatch({ type: actionTypes.DECREMENT })}>-
dispatch({ type: actionTypes.RESET })}>Reset 14
dispatch({ type: actionTypes.SET, payload: 10 })}> Set to 10 > ); } // Ứng dụng function App() { return ( ); } export default App; ```
Trong ví dụ này, `useReducer` được sử dụng để quản lý trạng thái của bộ đếm. Một context
toàn cục được tạo ra sử dụng `createContext` để có thể chia sẻ trạng thái và hàm dispatch giữa
các components mà không cần phải truyền props. Hai custom hooks, `useCounterState` và
`useCounterDispatch`, được tạo ra để cung cấp một API sạch sẽ và dễ sử dụng cho việc truy cập state và dispatch.
Mô hình này giúp tạo ra một store trạng thái tương tự như Redux và là một cách hiệu quả để
quản lý trạng thái ứng dụng trong các dự án React lớn. 15