Functional setState trong React - Mới mà không hề mới! - P1

Nếu đã từng làm việc với React, chắc hẳn không ai không động đến hàm setState - một trong những hàm cơ bản nhất của React được giới thiệu ngay từ những bài học đầu tiên ở bất cứ một tutorial nào về React. Vậy thì, bạn có nghĩ là mình đã khai thác hết chức năng từ hàm setState "thần thánh" này?

Để hàm setState() hoạt động, bạn sẽ truyền 1 object vào làm tham số cho hàm này. Khi ấy, object này cần có có key trùng với key của state bạn muốn update, sau đó hàm setState sẽ cập nhật state.

Vậy điều gì mà có lẽ bạn vẫn chưa biết?

Hãy nhớ lại cách hàm setState() hoạt động, nếu thay vì làm như cách truyền thống, chúng ta truyền vào 1 function thì sao? Hàm setState() cũng chấp nhận điều này. Function sẽ có thể truy cập state trước đó cũng như props hiện tại của component. Ví dụ:

this.setState(function (state, props) {
 return {
  score: state.score - 1
 }
});

Nhưng truyền function vào hàm setState để làm gì?

Hãy nghĩ về việc xảy ra khi hàm setState() được gọi tới. React trước tiên sẽ merge object bạn truyền cho hàm setState() vào state hiện tại. Sau đó, nó sẽ tạo ra một React Element tree (1 object thể hiện UI hiện tại của bạn), so sánh sự khác nhau giữa tree mới và tree cũ, và tìm ra những thay đổi dựa trên object bạn truyền vào hàm setState(), cuối cùng là udpate DOM.

Tuy nhiên React không chỉ đơn giản “set-state”.

"React may batch multiple setState() calls into a single update for performance." Điều này nghĩa là thế nào? Trước tiên, hãy xem ví dụ này:

state = {score : 0};
// multiple setState() calls
increaseScoreBy3 () {
 this.setState({score : this.state.score + 1});
 this.setState({score : this.state.score + 1});
 this.setState({score : this.state.score + 1});
}

Khi bắt gặp trường hợp như trên, thay vì "set-state" 3 lần, React sẽ tránh làm việc lượng lớn công việc đã mô tả phía trên và đưa ra 1 cách giải quyết tốt hơn: "Tôi sẽ tránh lặp lại 1 công việc 3 lần, tôi thích tạo ra 1 container và gom nhóm tất cả chúng lại để chỉ phải update 1 lần duy nhất thôi!". Và đó chính là batching. Vậy, giả sử React gặp trường hợp nhiều hàm setState() được gọi, nó sẽ thực hiện batching bằng cách merges tất cả các object được truyền vào hàm setState làm 1 object duy nhất, sau đó dùng 1 object đó cho hàm setState() thôi. Trong JavaScript, việc merge object có thể diễn ra như sau:

const singleObject = Object.assign(
  {}, 
  objectFromSetState1, 
  objectFromSetState2, 
  objectFromSetState3
);

Pattern này còn được biết đến với tên gọi: "Object composition" Trong JavaScript, cách merge objects diễn ra nhu sau: Nếu có 3 object có key giống nhau, thì value của key ở object cuối cùng truyền vào hàm Object.assign() sẽ trở thành value cuối cùng của object được merge, ví dụ:

const me  = {name : "Justice"}, 
      you = {name : "Your name"},
      we  = Object.assign({}, me, you);
we.name === "Your name"; //true
console.log(we); // {name : "Your name"}

you là object cuối cùng được merge vào we, nên value của name trong object we sẽ là "Your name". Do đó, nếu bạn gọi hàm setState() với 1 object nhiều lần - mỗi lần truyền vào 1 object - React sẽ merge chúng. Và nếu có bất kỳ object nào chứa cùng key, thì value của key ở object cuối cùng sẽ được lưu lại. Điều đó có nghĩa là hàm increaseScoreBy3 ở phía trên sẽ cho ra kết quả cuối cùng là 1 thay vì là 3, vì React không trực tiếp cập nhật state theo thứ tự. Nhưng trước tiên, React merge tất cả object lại, kết quả là được object {score : this.state.score + 1}, sau đó chỉ thực hiện "set-state" 1 lần - với object mới (được tạo ra từ việc merge 3 object ban đầu). Tuy nhiên, việc truyền object vào hàm setState() không phải là vấn đề ở đây. Vấn đề thực sự là việc bạn truyền object vào hàm setSate() khi bạn muốn tính tóan previous state. Bởi vậy đừng làm như vậy vì nó không an toàn chút nào!

Bạn có thể tham khảo 2 cách làm (cả bad solution và good solution) trong đoạn code dưới đây để hiểu rõ hơn vấn đề này: https://codepen.io/mrscobbler/pen/JEoEgN

Và đây là lúc để sử dụng Functional setState

Cách làm của good solution trong đoạn code nói trên đã hoàn toàn giải quyết được vấn đề của chúng ta, tại sao vậy? Hãy cùng thảo luận về bài viết sau đây của Dan: https://twitter.com/dan_abramov/status/824309659775467527/photo/1

Dan viết: * "Updates will be queued and later executed in the order they were called".* Như vậy, khi React bắt gặp "multiple functional setState() calls", thay vì merge các objects lại với nhau, React xếp các functions vào hàng đợi theo thứ tự mà chúng được gọi. Sau đó, React thực hiện update state thông qua việc gọi từng functions trong hàng đợi và truyền cho chúng previous state.

TO BE CONTINUED...