Khi "this" Không Phải Là Thứ Bạn Nghĩ: Cuộc Phiêu Lưu Của Một React Developer

Khi "this" Không Phải Là Thứ Bạn Nghĩ: Cuộc Phiêu Lưu Của Một React Developer

Câu chuyện bắt đầu từ một bug kỳ lạ...

Hãy tưởng tượng: 3 giờ sáng, anh em vẫn ngồi trước màn hình. Component React của anh em hoạt động hoàn hảo... cho đến khi anh em truyền một method vào props. Đột nhiên, this.setState ném ra lỗi "Cannot read property 'setState' of undefined".

Nghe có quen không 😆

Nếu anh em đã từng trải qua cảm giác này, chúc mừng - anh em vừa va phải một trong những bí ẩn thú vị nhất của JavaScript: con trỏ this.

Câu chuyện về một cửa hàng cà phê JavaScript

Hãy quên React đi một chút. Tôi muốn kể cho bạn nghe về một quán cà phê đặc biệt trong thế giới JavaScript:

const coffeeMachine = {
  brand: "EspressoMaster 3000",
  temperature: 95,
  brew() {
    console.log(`${this.brand} đang pha cà phê ở ${this.temperature}°C`);
  }
};

coffeeMachine.brew(); // "EspressoMaster 3000 đang pha cà phê ở 95°C" ✅

Màn hình in ra "EspressoMaster 3000 đang pha cà phê ở 95°C" ✅

Mọi thứ vẫn hoàn hảo. Máy pha cà phê hoạt động, cuộc sống tươi đẹp.

Nhưng khoan đã, nếu như làm như sau:

const justBrew = coffeeMachine.brew;
justBrew(); // "undefined đang pha cà phê ở undefined°C" 🤯

Plot twist! Máy pha cà phê đã... mất trí nhớ? Chuyện gì đã xảy ra?

Sự thật: Functions là những người làm thuê

Đây là điều mà nhiều developer không nhận ra: Trong JavaScript, methods chỉ là functions được lưu trong objects. Chúng không "thuộc về" object đó theo cách bạn tưởng.

Hãy tưởng tượng functions như những người làm thuê. Khi anh em viết:

const coffeeMachine = {
  brand: "EspressoMaster 3000",
  brew() {
    console.log(`${this.brand} đang pha cà phê`);
  }
};

JavaScript thực sự lưu trữ nó như thế này:

// brew thực chất chỉ là một người làm thuê "tự do"
function brew() {
  console.log(`${this.brand} đang pha cà phê`);
}

// Được "thuê" bởi coffeeMachine
coffeeMachine.brew = brew;

"this" - Chiếc áo đồng phục của cửa hàng

Bí mật nằm ở đây: this giống như chiếc áo đồng phục mà người làm thuê mặc - nó thay đổi tùy theo quán cà phê họ đang làm việc.

Khi bạn gọi coffeeMachine.brew(), JavaScript âm thầm làm điều này:

// Đằng sau hậu trường
function callMethod(shop, methodName) {
  const barista = shop[methodName];  // Tìm người làm thuê (function)
  barista.call(shop);                 // Mặc áo đồng phục của shop này
}

// coffeeMachine.brew() tương đương với:
callMethod(coffeeMachine, "brew");

Nhưng khi bạn làm:

const justBrew = coffeeMachine.brew;
justBrew(); // Người làm thuê không biết mình đang làm ở đâu!

Không có quán cà phê nào → Người làm thuê không có áo đồng phục → this = undefined (hoặc window).

Một Người làm tại nhiều quán cà phê?

Lúc đầu, điều này có vẻ như một lỗi thiết kế. Nhưng không! Đây là sức mạnh thực sự:

const italianCafe = {
  brand: "Italiano Express",
  temperature: 92
};

const vietnameseCafe = {
  brand: "Sài Gòn Drip",
  temperature: 85
};

function brew() {
  console.log(`${this.brand} pha cà phê ở ${this.temperature}°C`);
}

// Một người làm thuê, làm việc ở nhiều quán!
brew.call(italianCafe);     // "Italiano Express pha cà phê ở 92°C"
brew.call(vietnameseCafe);   // "Sài Gòn Drip pha cà phê ở 85°C"

Người làm thuê có thể làm việc ở bất kỳ quán nào, mặc áo đồng phục của quán đó. Điều này cho phép JavaScript có những patterns cực kỳ linh hoạt như mixins, decorators, và method borrowing.

Quay lại với React: Bây giờ mọi thứ đã sáng tỏ

Anh em nhớ bug React lúc đầu không? Giờ chắc đã hiểu tại sao:

class CoffeeShopApp extends React.Component {
  constructor() {
    super();
    this.state = { cupsServed: 0 };
  }

  serveCoffee() {
    // 'this' ở đây là ai?
    this.setState({ cupsServed: this.state.cupsServed + 1 });
  }

  render() {
    return (
      // Khi truyền như prop, serveCoffee mất "áo đồng phục"!
      <button onClick={this.serveCoffee}>
        Serve Coffee
      </button>
    );
  }
}

Một số solutions cho vấn đề đó như sau:

Solution 1: Arrow function (tự động giữ áo đồng phục)


serveCoffee = () => {
  this.setState({ cupsServed: this.state.cupsServed + 1 });
}

Solution 2: Bind - "khâu" áo đồng phục vào nhân viên


<button onClick={this.serveCoffee.bind(this)}>

Solution 3: Wrapper - gọi người làm thuê qua quản lý

<button onClick={() => this.serveCoffee()}>

Bài học từ quán cà phê JavaScript

this trong JavaScript không phải là bug - nó là một tính năng cực kỳ hay ho. Một khi bạn hiểu rằng:

  1. Functions là người làm thuê - Họ không gắn chặt với một "công ty" nào
  2. Context là chiếc áo đồng phục - Được mặc khi làm việc, không phải khi ký hợp đồng
  3. Bạn là ông chủ - Quyết định ai mặc áo gì với .call(), .apply(), .bind()

...thì this từ cơn ác mộng trở thành cơ hội để hiểu sâu bản chất

Thử thách cuối cho bạn đọc

Anh em đoán xem đoạn code này in ra gì:

const starbucks = {
  brand: "Starbucks",
  specialRecipe: function() {
    const secretIngredient = () => {
      console.log(`${this.brand} thêm nguyên liệu bí mật!`);
    };
    secretIngredient();
  }
};

const highlands = {
  brand: "Highlands"
};

starbucks.specialRecipe.call(highlands); // ???

Read more