0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

Quản lý trạng thái trong React: Từ useState đến useReducer

Đăng vào 7 tháng trước

• 10 phút đọc

Chủ đề:

KungFuTech

Giới thiệu

Quản lý trạng thái trong React có thể trở nên phức tạp nhanh chóng khi ứng dụng của bạn phát triển. Trong khi useStateuseEffect rất tốt cho các tình huống đơn giản, các ứng dụng React hiện đại thường yêu cầu các mẫu quản lý trạng thái phức tạp hơn. Hướng dẫn toàn diện này sẽ khám phá các kỹ thuật quản lý trạng thái nâng cao giúp ứng dụng React của bạn trở nên dễ bảo trì và hiệu suất hơn.

Sự phát triển của quản lý trạng thái trong React

React đã trải qua một chặng đường dài kể từ những ngày đầu. Sự xuất hiện của hooks trong React 16.8 đã cách mạng hóa cách chúng ta xử lý trạng thái, chuyển từ các thành phần class sang các thành phần function với hooks. Tuy nhiên, khi ứng dụng mở rộng, các nhà phát triển thường gặp khó khăn với việc truyền props, đồng bộ hóa trạng thái và tối ưu hóa hiệu suất.

useReducer: Cánh cửa tới logic trạng thái phức tạp

Khi useState hoàn hảo cho các cập nhật trạng thái đơn giản, useReducer tỏa sáng khi xử lý logic trạng thái phức tạp liên quan đến nhiều giá trị con hoặc khi trạng thái tiếp theo phụ thuộc vào trạng thái trước đó.

javascript Copy
import React, { useReducer } from 'react';

const initialState = {
  user: null,
  loading: false,
  error: null,
  notifications: []
};

function appReducer(state, action) {
  switch (action.type) {
    case 'LOGIN_START':
      return { ...state, loading: true, error: null };
    case 'LOGIN_SUCCESS':
      return { ...state, loading: false, user: action.payload };
    case 'LOGIN_ERROR':
      return { ...state, loading: false, error: action.payload };
    case 'ADD_NOTIFICATION':
      return { 
        ...state, 
        notifications: [...state.notifications, action.payload] 
      };
    case 'REMOVE_NOTIFICATION':
      return {
        ...state,
        notifications: state.notifications.filter(n => n.id !== action.payload)
      };
    default:
      return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(appReducer, initialState);

  const login = async (credentials) => {
    dispatch({ type: 'LOGIN_START' });
    try {
      const user = await authService.login(credentials);
      dispatch({ type: 'LOGIN_SUCCESS', payload: user });
    } catch (error) {
      dispatch({ type: 'LOGIN_ERROR', payload: error.message });
    }
  };

  return (
    // Your JSX here
  );
}

Context API: Loại bỏ Prop Drilling

Context API cung cấp cách để chia sẻ dữ liệu giữa các thành phần mà không cần phải truyền props qua từng cấp độ của cây thành phần. Dưới đây là cách tạo hệ thống quản lý trạng thái dựa trên context mạnh mẽ:

javascript Copy
import React, { createContext, useContext, useReducer } from 'react';

const AppContext = createContext();

export function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);

  const value = {
    state,
    dispatch,
    // Action creators
    login: (credentials) => {
      dispatch({ type: 'LOGIN_START' });
      // Login logic here
    },
    logout: () => dispatch({ type: 'LOGOUT' }),
    addNotification: (notification) => 
      dispatch({ type: 'ADD_NOTIFICATION', payload: notification })
  };

  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

export function useApp() {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useApp must be used within an AppProvider');
  }
  return context;
}

Custom Hooks: Logic Trạng Thái Có Thể Tái Sử Dụng

Custom hooks cho phép bạn trích xuất và tái sử dụng logic trạng thái giữa nhiều thành phần:

javascript Copy
import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error('Error reading from localStorage:', error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error('Error writing to localStorage:', error);
    }
  };

  return [storedValue, setValue];
}

function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();

        if (!cancelled) {
          setData(result);
          setError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };

    fetchData();

    return () => {
      cancelled = true;
    };
  }, [url]);

  return { data, loading, error };
}

Máy Trạng Thái với useReducer

Đối với các trạng thái UI phức tạp, hãy xem xét việc triển khai máy trạng thái:

javascript Copy
const STATES = {
  IDLE: 'idle',
  LOADING: 'loading',
  SUCCESS: 'success',
  ERROR: 'error'
};

const ACTIONS = {
  FETCH: 'fetch',
  SUCCESS: 'success',
  ERROR: 'error',
  RESET: 'reset'
};

function dataFetchReducer(state, action) {
  switch (state.status) {
    case STATES.IDLE:
      switch (action.type) {
        case ACTIONS.FETCH:
          return { status: STATES.LOADING, data: null, error: null };
        default:
          return state;
      }
    case STATES.LOADING:
      switch (action.type) {
        case ACTIONS.SUCCESS:
          return { status: STATES.SUCCESS, data: action.payload, error: null };
        case ACTIONS.ERROR:
          return { status: STATES.ERROR, data: null, error: action.payload };
        default:
          return state;
      }
    case STATES.SUCCESS:
    case STATES.ERROR:
      switch (action.type) {
        case ACTIONS.FETCH:
          return { status: STATES.LOADING, data: null, error: null };
        case ACTIONS.RESET:
          return { status: STATES.IDLE, data: null, error: null };
        default:
          return state;
      }
    default:
      return state;
  }
}

Chiến Lược Tối Ưu Hóa Hiệu Suất

Memoization với useMemo và useCallback

javascript Copy
import React, { useMemo, useCallback, memo } from 'react';

const ExpensiveComponent = memo(({ items, onItemClick }) => {
  const expensiveValue = useMemo(() => {
    return items.reduce((acc, item) => acc + item.value, 0);
  }, [items]);

  return (
    <div>
      <p>Total: {expensiveValue}</p>
      {items.map(item => (
        <Item 
          key={item.id} 
          item={item} 
          onClick={onItemClick} 
        />
      ))}
    </div>
  );
});

function ParentComponent() {
  const [items, setItems] = useState([]);

  const handleItemClick = useCallback((item) => {
    // Handle item click
    console.log('Item clicked:', item);
  }, []);

  return (
    <ExpensiveComponent 
      items={items} 
      onItemClick={handleItemClick} 
    />
  );
}

Phân Tách Trạng Thái để Tăng Hiệu Suất

Thay vì giữ tất cả trạng thái trong một đối tượng lớn, hãy chia nhỏ nó thành các phần tập trung hơn:

javascript Copy
// Thay vì điều này:
const [appState, setAppState] = useState({
  user: null,
  posts: [],
  comments: [],
  ui: { loading: false, error: null }
});

// Làm điều này:
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [ui, setUi] = useState({ loading: false, error: null });

Thực Tiễn Tốt Nhất và Hướng Dẫn

  1. Bắt đầu đơn giản: Bắt đầu với useStateuseEffect. Chỉ nên giới thiệu phức tạp khi cần thiết.
  2. Đặt Trạng Thái Cạnh Bên: Giữ trạng thái gần nơi nó được sử dụng nhất có thể.
  3. Sử dụng Custom Hooks: Trích xuất logic trạng thái có thể tái sử dụng thành custom hooks.
  4. Cân nhắc Máy Trạng Thái: Đối với các trạng thái UI phức tạp, máy trạng thái mang lại sự rõ ràng và ngăn chặn các trạng thái không thể xảy ra.
  5. Tối Ưu Hóa Cẩn Thận: Đo lường trước khi tối ưu hóa. Không phải thành phần nào cũng cần memo hoặc useMemo.
  6. Kiểm Tra Logic Trạng Thái: Logic quản lý trạng thái nên được kiểm tra kỹ lưỡng, đặc biệt là reducers và custom hooks.

Kết luận

Các ứng dụng React hiện đại yêu cầu các chiến lược quản lý trạng thái có suy nghĩ. Bằng cách hiểu và áp dụng các mẫu này - từ useReducer và Context API đến custom hooks và các kỹ thuật tối ưu hóa hiệu suất - bạn có thể xây dựng các ứng dụng React có khả năng mở rộng và dễ bảo trì.

Điều quan trọng là chọn công cụ phù hợp cho công việc. Trạng thái cục bộ đơn giản có thể chỉ cần useState, trong khi trạng thái ứng dụng phức tạp có thể hưởng lợi từ sự kết hợp của useReducer, Context API và custom hooks. Hãy nhớ rằng, giải pháp quản lý trạng thái tốt nhất là giải pháp giúp mã của bạn trở nên dễ đọc và dễ bảo trì cho trường hợp sử dụng cụ thể của bạn.

Câu hỏi thường gặp (FAQ)

1. Khi nào nên sử dụng useReducer thay vì useState?
Khi trạng thái của bạn phức tạp và có nhiều giá trị con, useReducer sẽ giúp bạn quản lý dễ dàng hơn.

2. Làm thế nào để tối ưu hóa hiệu suất trong ứng dụng React?
Sử dụng useMemouseCallback để tối ưu hóa các phép toán tốn kém và tránh render lại không cần thiết.

3. Context API có ảnh hưởng đến hiệu suất không?
Có, việc sử dụng Context API có thể dẫn đến render lại không cần thiết nếu không được quản lý tốt. Hãy đảm bảo chỉ sử dụng nó cho các dữ liệu thực sự cần chia sẻ giữa các thành phần.

4. Custom hooks là gì?
Custom hooks là một cách để trích xuất logic trạng thái có thể tái sử dụng giữa nhiều thành phần trong React.

5. Tại sao nên kiểm tra logic quản lý trạng thái?
Đảm bảo rằng logic quản lý trạng thái hoạt động đúng và không có lỗi là rất quan trọng để duy trì tính ổn định của ứng dụng.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào