0
0
Lập trình
Thaycacac
Thaycacac thaycacac

Quản lý trạng thái trong Vanilla JavaScript: Hướng dẫn chi tiết

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

• 9 phút đọc

Quản lý trạng thái trong Vanilla JavaScript: Hướng dẫn chi tiết

Giới thiệu

Trong thế giới phát triển web năng động, việc quản lý trạng thái ứng dụng một cách hiệu quả là rất quan trọng để xây dựng những ứng dụng tương tác và dễ bảo trì. Mặc dù các framework phổ biến như React, Angular và Vue.js cung cấp các giải pháp quản lý trạng thái mạnh mẽ, nhưng điều quan trọng là hiểu cách tiếp cận quản lý trạng thái trong Vanilla JavaScript. Vanilla JavaScript, tức là JavaScript thuần túy không có bất kỳ framework hay thư viện nào, cung cấp kiến thức nền tảng về các nguyên lý cơ bản, cho phép bạn xây dựng các giải pháp tùy chỉnh phù hợp với nhu cầu cụ thể của bạn và thậm chí hiểu rõ hơn về cách mà các giải pháp quản lý trạng thái dựa trên framework hoạt động.

Bài viết này sẽ đi sâu vào các khía cạnh phức tạp của việc quản lý trạng thái trong Vanilla JavaScript, khám phá các kỹ thuật khác nhau, lợi ích, bất lợi và các ví dụ mã thực tiễn. Chúng tôi sẽ đề cập đến mọi thứ từ các kỹ thuật cơ bản như biến toàn cục đến các mẫu nâng cao sử dụng mô hình Observer và thậm chí khám phá cách bắt chước các nguyên tắc của Redux trong bối cảnh Vanilla JS.

Điều kiện tiên quyết

Trước khi bắt đầu, bạn cần có hiểu biết vững chắc về những kiến thức cơ bản sau:

  • Biến và kiểu dữ liệu: Làm quen với let, const, var và các kiểu dữ liệu nguyên thủy như chuỗi, số, boolean và đối tượng.
  • Hàm: Hiểu biết về khai báo hàm, biểu thức hàm, hàm mũi tên và phạm vi hàm.
  • Đối tượng và mảng: Thành thạo trong việc làm việc với các đối tượng JavaScript, bao gồm các đối tượng literal, thuộc tính, phương thức và mảng với các phương thức tích hợp của chúng (ví dụ: map, filter, reduce).
  • Thao tác DOM: Kiến thức về cách tương tác với Document Object Model (DOM) bằng các phương thức như document.getElementById, document.querySelector, và các listener sự kiện (addEventListener).
  • Xử lý sự kiện: Hiểu biết cách xử lý các sự kiện người dùng như nhấp chuột, gửi biểu mẫu và nhập liệu từ bàn phím.
  • Closure: Hiểu cách hoạt động của closure là rất quan trọng để xây dựng các giải pháp quản lý trạng thái nâng cao hơn.

Tại sao quản lý trạng thái trong Vanilla JavaScript?

Ngay cả khi bạn dự định sử dụng các framework trong các dự án của mình, việc tìm hiểu về quản lý trạng thái trong Vanilla JavaScript mang lại nhiều lợi ích:

  • Hiểu biết sâu sắc hơn: Nó cho phép bạn nắm bắt các khái niệm cơ bản về quản lý trạng thái, cung cấp nền tảng vững chắc để hiểu cách mà các framework và thư viện xử lý trạng thái bên trong.
  • Giải pháp tùy chỉnh: Đối với các dự án nhỏ hơn hoặc các trường hợp cụ thể, bạn có thể không cần đến độ phức tạp của một framework đầy đủ. Quản lý trạng thái bằng Vanilla JavaScript cho phép bạn xây dựng các giải pháp nhẹ, tùy chỉnh.
  • Hiệu suất: Bằng cách tránh chi phí của các thư viện lớn, bạn có thể tối ưu hóa hiệu suất cho các ứng dụng đơn giản hơn.
  • Giảm phụ thuộc: Dựa vào ít phụ thuộc bên ngoài hơn giúp dự án của bạn trở nên mạnh mẽ hơn và ít bị ảnh hưởng bởi những thay đổi không mong muốn trong các thư viện bên ngoài.
  • Gỡ lỗi và xử lý sự cố: Hiểu các nguyên tắc cốt lõi sẽ giúp bạn rất nhiều trong việc gỡ lỗi các vấn đề ngay cả trong các ứng dụng phức tạp.

Kỹ thuật quản lý trạng thái cơ bản

1. Biến toàn cục

Cách tiếp cận đơn giản nhất là sử dụng biến toàn cục để lưu trữ trạng thái ứng dụng. Điều này thường không được khuyến khích cho các dự án lớn do khả năng xảy ra xung đột tên và khó khăn trong việc quản lý các phụ thuộc. Tuy nhiên, nó có thể đủ cho các ứng dụng nhỏ, đơn giản.

javascript Copy
// Biến trạng thái toàn cục
let counter = 0;

function incrementCounter() {
  counter++;
  updateDisplay();
}

function updateDisplay() {
  document.getElementById('counter').textContent = counter;
}

// Gán hàm cho một nút
 document.getElementById('incrementButton').addEventListener('click', incrementCounter);

// Khởi tạo hiển thị
updateDisplay();

Ưu điểm:

  • Đơn giản và dễ thực hiện.

Nhược điểm:

  • Phạm vi toàn cục có thể dẫn đến xung đột tên và làm cho mã trở nên khó hiểu.
  • Khó theo dõi sự thay đổi trạng thái và gỡ lỗi các vấn đề trong các ứng dụng lớn hơn.
  • Khả năng bảo trì và mở rộng kém.

2. Phương pháp Hướng đối tượng

Bạn có thể bao bọc trạng thái trong một đối tượng, cung cấp sự tổ chức tốt hơn so với biến toàn cục.

javascript Copy
const appState = {
  counter: 0,
  userName: 'Guest',

  incrementCounter() {
    this.counter++;
    this.updateDisplay();
  },

  updateUserName(name) {
    this.userName = name;
    this.updateDisplay();
  },

  updateDisplay() {
    document.getElementById('counter').textContent = this.counter;
    document.getElementById('userName').textContent = this.userName;
  }
};

document.getElementById('incrementButton').addEventListener('click', () => appState.incrementCounter());
document.getElementById('nameForm').addEventListener('submit', (event) => {
  event.preventDefault();
  const name = document.getElementById('nameInput').value;
  appState.updateUserName(name);
});

appState.updateDisplay(); // Hiển thị ban đầu

Ưu điểm:

  • Tổ chức tốt hơn so với biến toàn cục.
  • Bao bọc trạng thái và logic liên quan.

Nhược điểm:

  • Vẫn phụ thuộc vào phạm vi toàn cục cho chính đối tượng appState.
  • Có thể trở nên khó quản lý khi ứng dụng phát triển.
  • Việc thao tác trực tiếp lên đối tượng trạng thái có thể dẫn đến các tác dụng phụ không mong muốn.

Kỹ thuật quản lý trạng thái nâng cao

3. Mô hình Observer

Mô hình Observer (còn gọi là Publish-Subscribe) cung cấp một cách để thông báo cho nhiều thành phần hoặc hàm (observer) khi trạng thái ứng dụng thay đổi. Điều này thúc đẩy sự kết nối lỏng lẻo và cải thiện khả năng bảo trì.

javascript Copy
class StateManager {
  constructor(initialState = {}) {
    this.state = initialState;
    this.observers = [];
  }

  subscribe(observer) {
    this.observers.push(observer);
    return () => { // Trả về một hàm hủy đăng ký
      this.observers = this.observers.filter(obs => obs !== observer);
    };
  }

  setState(newState) {
    this.state = { ...this.state, ...newState }; // Gộp trạng thái mới
    this.notifyObservers();
  }

  getState() {
    return { ...this.state }; // Trả về bản sao để ngăn chặn thao tác trực tiếp
  }

  notifyObservers() {
    this.observers.forEach(observer => observer(this.getState()));
  }
}

// Ví dụ Sử dụng:
const stateManager = new StateManager({ counter: 0, message: 'Hello' });

const counterDisplay = (state) => {
  document.getElementById('counter').textContent = state.counter;
};

const messageDisplay = (state) => {
  document.getElementById('message').textContent = state.message;
};

const unsubscribeCounter = stateManager.subscribe(counterDisplay); // Đăng ký hiển thị counter
const unsubscribeMessage = stateManager.subscribe(messageDisplay);   // Đăng ký hiển thị message

document.getElementById('incrementButton').addEventListener('click', () => {
  stateManager.setState({ counter: stateManager.getState().counter + 1 });
});

document.getElementById('updateMessageButton').addEventListener('click', () => {
  const newMessage = document.getElementById('messageInput').value;
  stateManager.setState({ message: newMessage });
});

// Khởi tạo hiển thị
counterDisplay(stateManager.getState());
messageDisplay(stateManager.getState());

Ưu điểm:

  • Kết nối lỏng lẻo giữa trạng thái và các thành phần.
  • Tách biệt rõ ràng giữa các mối quan tâm.
  • Dễ dàng thêm hoặc xóa các thành phần phụ thuộc vào trạng thái.

Nhược điểm:

  • Có thể phức tạp hơn để triển khai so với các biến toàn cục đơn giản.
  • Cần quản lý cẩn thận các đăng ký để tránh rò rỉ bộ nhớ.

4. Bắt chước các nguyên tắc của Redux

Mặc dù không phải là một triển khai đầy đủ của Redux, bạn có thể bắt chước các khái niệm chính như nguồn duy nhất của sự thật, các hành động, bộ giảm và một cửa hàng để quản lý trạng thái theo cách có thể dự đoán được.

javascript Copy
// Các hành động
const INCREMENT = 'INCREMENT';
const UPDATE_MESSAGE = 'UPDATE_MESSAGE';

// Tạo hành động
const incrementAction = () => ({ type: INCREMENT });
const updateMessageAction = (message) => ({ type: UPDATE_MESSAGE, payload: message });

// Bộ giảm
const reducer = (state = { counter: 0, message: 'Tin nhắn ban đầu' }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, counter: state.counter + 1 };
    case UPDATE_MESSAGE:
      return { ...state, message: action.payload };
    default:
      return state;
  }
};

// Cửa hàng
class Store {
  constructor(reducer, initialState) {
    this.reducer = reducer;
    this.state = initialState;
    this.listeners = [];
  }

  getState() {
    return this.state;
  }

  dispatch(action) {
    this.state = this.reducer(this.state, action);
    this.listeners.forEach(listener => listener());
  }

  subscribe(listener) {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }
}

const store = new Store(reducer, { counter: 0, message: 'Hello' });

// Logic Thành phần (Ví dụ)
const counterDisplay = () => {
  document.getElementById('counter').textContent = store.getState().counter;
};

const messageDisplay = () => {
  document.getElementById('message').textContent = store.getState().message;
};

const unsubscribeCounter = store.subscribe(counterDisplay);
const unsubscribeMessage = store.subscribe(messageDisplay);

document.getElementById('incrementButton').addEventListener('click', () => {
  store.dispatch(incrementAction());
});

document.getElementById('updateMessageButton').addEventListener('click', () => {
  const newMessage = document.getElementById('messageInput').value;
  store.dispatch(updateMessageAction(newMessage));
});

// Hiển thị ban đầu
counterDisplay();
messageDisplay();

Ưu điểm:

  • Quản lý trạng thái có thể dự đoán được với nguồn duy nhất của sự thật.
  • Tách biệt rõ ràng giữa các hành động và bộ giảm.
  • Dễ dàng gỡ lỗi các thay đổi trạng thái.

Nhược điểm:

  • Phức tạp hơn để triển khai so với các phương pháp khác.
  • Cần hiểu sâu về các nguyên tắc của Redux.
  • Có thể phức tạp cho các ứng dụng nhỏ.

Lựa chọn cách tiếp cận phù hợp

Kỹ thuật quản lý trạng thái tốt nhất phụ thuộc vào độ phức tạp và quy mô của ứng dụng của bạn:

  • Ứng dụng nhỏ, đơn giản: Biến toàn cục hoặc phương pháp hướng đối tượng có thể đủ.
  • Ứng dụng vừa: Mô hình Observer cung cấp sự cân bằng tốt giữa độ phức tạp và khả năng bảo trì.
  • Ứng dụng lớn, phức tạp: Bắt chước các nguyên tắc của Redux hoặc sử dụng một thư viện quản lý trạng thái nhẹ có thể cung cấp cấu trúc và độ tin cậy cần thiết.

Kết luận

Quản lý trạng thái là một khía cạnh quan trọng của phát triển web, đảm bảo rằng các ứng dụng của bạn tương tác, dự đoán và dễ bảo trì. Mặc dù các framework cung cấp các giải pháp tích hợp, nhưng việc hiểu cách quản lý trạng thái trong Vanilla JavaScript mang lại những hiểu biết quý giá và cho phép bạn tùy chỉnh các giải pháp phù hợp với nhu cầu cụ thể của bạn. Bằng cách thành thạo các kỹ thuật được nêu trong bài viết này, bạn có thể xây dựng các ứng dụng mạnh mẽ và có thể mở rộng, bất kể framework nào bạn chọn. Hãy nhớ chọn cách tiếp cận phù hợp nhất với độ phức tạp và quy mô của dự án của bạn, và ưu tiên mã nguồn rõ ràng, dễ bảo trì.

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