Lúc này hay lúc khác, bạn có thể cần sử dụng global state bên trong ứng dụng React của mình. Điều này cho phép bạn có dữ liệu của mình ở một nơi và đảm bảo rằng các thành phần cần thiết có thể truy cập dữ liệu đó.
Để giúp bạn làm điều này, bạn sẽ thường sử dụng một số loại thư viện quản lý trạng thái như Redux, React Context hoặc Recoil.
Nhưng trong bài viết này, chúng ta sẽ tìm hiểu về quản lý trạng thái toàn cầu với sự trợ giúp của các mẫu thiết kế.
Chúng ta sẽ xem xét các mẫu thiết kế là gì và đặc biệt chúng ta sẽ tập trung vào mẫu thiết kế đơn lẻ. Cuối cùng, chúng ta sẽ xem xét một ví dụ về mẫu thiết kế singleton cùng với những ưu điểm và nhược điểm của nó.
Vì vậy, không có bất kỳ rắc rối nào nữa, hãy bắt đầu.
Mục lục
điều kiện tiên quyết
Trước khi xem qua bài viết này, tôi thực sự khuyên bạn nên làm quen với nội dung trong các bài viết sau:
Mẫu thiết kế là gì?

Mẫu thiết kế là một tập hợp các hướng dẫn tổng quát cung cấp giải pháp cho các sự cố thường xảy ra trong thiết kế phần mềm.
Bạn có thể nghĩ về các mẫu thiết kế như một trang web bao gồm nhiều mẫu thiết kế mà bạn có thể sử dụng để xây dựng một trang web dựa trên nhu cầu cụ thể của mình.
Vì vậy, bây giờ câu hỏi là – tại sao điều quan trọng là phải biết các mẫu thiết kế? Chà, sử dụng các mẫu thiết kế có một số lợi ích, chẳng hạn như:
- Các mẫu này đã được chứng minh – nghĩa là, các hướng dẫn này đã được thử và kiểm tra, đồng thời chúng phản ánh kinh nghiệm và hiểu biết sâu sắc của nhiều nhà phát triển.
- Chúng là những mẫu mà bạn có thể sử dụng lại dễ dàng.
- Chúng có tính biểu cảm cao.
Lưu ý rằng các mẫu thiết kế chỉ cung cấp giải pháp khái niệm cho vấn đề định kỳ theo cách tối ưu hóa. Nó không cung cấp một đoạn mã mà bạn có thể sử dụng trong dự án của mình.
Vì vậy, bây giờ chúng ta đã biết các mẫu thiết kế là gì, hãy đi sâu vào mẫu thiết kế đầu tiên của chúng ta.
Mẫu thiết kế Singleton là gì?

Singleton là một mẫu thiết kế cho chúng ta biết rằng chúng ta chỉ có thể tạo một thể hiện của một lớp và thể hiện đó có thể được truy cập trên toàn cầu.
Đây là một trong những loại cơ bản của mẫu thiết kế. Nó đảm bảo rằng lớp hoạt động như một nguồn đầu vào duy nhất cho tất cả các thành phần người tiêu dùng muốn truy cập trạng thái này. Nói cách khác, nó cung cấp một điểm vào chung để sử dụng trạng thái toàn cục.
Vì vậy, một lớp đơn nên là một trong đó:
- Đảm bảo rằng nó chỉ tạo một thể hiện của lớp
- Cung cấp một điểm truy cập toàn cầu cho nhà nước.
- Đảm bảo rằng phiên bản chỉ được tạo lần đầu tiên.
Ví dụ về Mẫu thiết kế Singleton
Để hiểu khái niệm này một cách tốt hơn, hãy xem xét một ví dụ. Ví dụ này là một ứng dụng React đơn giản thể hiện cách sử dụng giá trị trạng thái chung trên các thành phần, cách nó được thay đổi và cách cùng một giá trị được cập nhật trong tất cả các thành phần. Bắt đầu nào.
Trước khi bắt đầu triển khai thực tế, chúng ta hãy xem cấu trúc thư mục:
.
├── index.html
├── package.json
└── src
├── componentA.js
├── componentB.js
├── globalStyles.js
├── index.js
├── styles.css
└── utilities.js
Dưới đây là chi tiết của từng tệp:
componentA.js
là một thành phần người tiêu dùng sử dụng lớp singleton để truy cập đối tượng trạng thái toàn cầu và thao tác với nó.componentB.js
tương tự như thành phần trên, vì nó phải truy cập đối tượng trạng thái toàn cầu và có thể thao tác với nó.globalStyles.js
là một mô-đun bao gồm lớp singleton và xuất thể hiện của lớp này.index.js
quản lý các hoạt động JS toàn cầu, đó là các thay đổi JavaScript cần thiết cho các phần tử DOM khác.styles.css
quản lý phong cách của ứng dụng. Bao gồm CSS cơ bản.utilities.js
là một mô-đun xuất một số chức năng tiện ích.index.html
bao gồm mã HTML cho các thành phần được yêu cầu trong dự án.package.json
là một cấu hình soạn sẵn được phát ra bởinpm init
chỉ huy.
Bây giờ chúng ta đã biết chức năng của từng tệp, chúng ta có thể bắt đầu bằng cách triển khai từng tệp một.
Nhưng trước khi đi sâu vào ví dụ này, chúng ta cần hiểu dòng mã. Mục đích của ví dụ của chúng ta là xây dựng một ứng dụng JavaScript thể hiện phong cách chung color
được tiêu thụ bởi từng thành phần và cách mỗi thành phần thay đổi nó.
Mỗi thành phần bao gồm một color-picker
. Khi bạn thay đổi phong cách toàn cầu color
thông qua bộ chọn màu hiện diện bên trong mỗi thành phần, nó sẽ tự động xuất hiện trong các thành phần khác và ở trạng thái chung.
Đầu tiên, hãy tạo một tệp: index.html
. Sau đó dán đoạn mã dưới đây vào tập tin này:
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="https://www.freecodecamp.org/news/singleton-design-pattern-with-javascript/./src/styles.css" />
</head>
<body>
<div class="global-state">
<h3>Global State</h3>
<h4>Color</h4>
<span id="selected-color"></span>
</div>
<div class="contents">
<div class="component-a">
<strong>Component A</strong>
<div>Pick color</div>
<span id="selected-color">black</span>
<input type="color" id="color-picker-a" />
</div>
<div class="component-b">
<strong>Component B</strong>
<div>Pick color</div>
<span id="selected-color">black</span>
<input type="color" id="color-picker-b" />
</div>
</div>
<script src="src/index.js"></script>
<script src="src/componentA.js"></script>
<script src="src/componentB.js"></script>
</body>
</html>
Ở trên cùng, chúng tôi tải CSS của mình qua <link rel="stylesheet" href="https://www.freecodecamp.org/news/singleton-design-pattern-with-javascript/./src/styles.css" />
.
Sau đó, chúng tôi đã chia ứng dụng của mình thành hai phần thông qua hai lớp:
.global-state
: Điều này sẽ đại diện cho mã HTML để hiển thị trạng thái chung hiện tại của ứng dụng..contents
: Điều này sẽ đại diện cho mã HTML đại diện cho hai thành phần.
Mỗi thành phần (component-a
và component-b
) có phần tử đầu vào chọn màu.
Cả hai thành phần này đều có một span
với lớp selected-color
phần tử sẽ giúp hiển thị giá trị hiện tại của biến trạng thái toàn cục color
.
Như bạn có thể thấy khi thay đổi bộ chọn màu bên trong componentA
các giá trị sau cũng đang thay đổi:
- Giá trị bên trong
.selected-color
phần tử span bên trongcomponentB
và trạng thái Toàn cầu. - Giá trị của bộ chọn màu của
componentA
vàcomponentB
.
Sau này chúng ta sẽ thấy tất cả những giá trị này đang thay đổi như thế nào. Nhưng hiện tại, điều quan trọng là chúng ta phải hiểu rằng nếu chúng ta thay đổi giá trị trạng thái chung từ một thành phần, thì các lớp đơn lẻ đảm bảo rằng giá trị đối tượng được cập nhật và tất cả các thành phần đang sử dụng đối tượng này sẽ nhận được cùng một giá trị vì chúng là đề cập đến cùng một trường hợp.
Tiếp theo, chúng tôi tạo một tệp có tên globalStyles.js
. Sao chép-dán mã bên dưới vào đó:
let instance;
let globalState = {
color: ""
};
class StateUtility {
constructor() {
if (instance) {
throw new Error("New instance cannot be created!!");
}
instance = this;
}
getPropertyByName(propertyName) {
return globalState[propertyName];
}
setPropertyValue(propertyName, propertyValue) {
globalState[propertyName] = propertyValue;
}
}
let stateUtilityInstance = Object.freeze(new StateUtility());
export default stateUtilityInstance;
Đoạn mã trên là một mô-đun có một lớp đơn StateUtility
và mặc định xuất phiên bản của cùng một lớp.
Hãy tìm hiểu sâu hơn về lớp học StateUtility
để hiểu làm thế nào nó giải quyết để trở thành một lớp đơn lẻ:
- Nó bao gồm
constructor
và hai phương thức lớp được gọi làgetPropertyByName
vàsetPropertyValue
. Cả hai phương thức lớp này đều khá dễ hiểu: một phương thức nhận giá trị của thuộc tính và phương thức kia đặt giá trị của nó. - Tiếp theo, chúng ta có
constructor
chức năng. Nó là một hàm được gọi bất cứ khi nào chúng ta tạo một đối tượng mới của lớp này. - Nhưng đây là một nhược điểm: để một lớp là một lớp đơn, chúng ta cần đảm bảo rằng nó chỉ tạo một thể hiện và chỉ có vậy thôi.
- Để đảm bảo rằng điều này xảy ra, chúng ta chỉ cần tạo một biến toàn cục có tên là
instance
. Chúng tôi xác định nó ở trên cùng của mô-đun. Biến này hoạt động như một công cụ kiểm tra. Chúng tôi thêm một điều kiện trongconstructor
chức năng sao cho nếuinstance
biến có bất kỳ giá trị nào (nghĩa là đối tượng của biếnStateUtility
class) sau đó đưa ra một lỗi hoặc nếu không thì chỉ địnhinstance
đến thể hiện của lớp hiện tại (cácthis
vật). - Trong ví dụ này, chúng tôi đã triển khai lớp
StateUtility
để nó có thể phơi bày và thay đổiglobalState
Biến đổi. - Chúng tôi đảm bảo rằng chúng tôi không phơi bày
globalState
. Chúng tôi phơi bày chúng bằng cách sử dụng các phương thức lớp củaStateUtility
. Bằng cách này, chúng tôi bảo vệ trạng thái toàn cầu khỏi bị thay đổi trực tiếp. - Cuối cùng, chúng ta tạo thể hiện của lớp như sau:
let stateUtilityInstance = Object.freeze(new StateUtility());
. - Chúng tôi vừa dùng
Object.freeze
để không có lớp/thành phần/mô-đun nào khác có thể sửa đổistateUtilityInstance
.
Sau đó, hãy tạo một tệp có tên componentA.js
bên trong src
thư mục. Sao chép-dán mã dưới đây vào tệp này:
import {
setAllSelectedColor
} from "./utilities";
import globalStyle from "./globalStyles";
// Get respective dom elements
const selectedColor = document.querySelectorAll("#selected-color");
const colorPickerA = document.getElementById("color-picker-a");
const colorPickerB = document.getElementById("color-picker-b");
// Event handler whenever a change event occurs
colorPickerA.onchange = (event) => {
// set the color property of the global state with current color picker's value;
globalStyle.setPropertyValue("color", event.target.value);
const color = globalStyle.getPropertyByName("color");
// A function thats sets the value of all the #selection-color dom elements;
setValueOfSimilarElements(selectedColor, color);
// make sure to set the component B's color picker value is set to color picker A;
// this is done to make sure that both of the color picker have same value on change;
colorPickerB.value = color;
};
Đây là sự cố của đoạn mã trên:
- Mục đích của mã này là để đảm bảo rằng chúng tôi đính kèm
onChange
trình xử lý cho bộ chọn màu hiện diện bên trongcomponent-a
. Trong trường hợp này, bộ chọn màu của componentA được xác định bởi id:#color-picker-a
.
- Chúng ta cần đảm bảo rằng trình xử lý này:
- Đặt giá trị cho màu thuộc tính của globalState.
- Tìm nạp lại cùng một thuộc tính.
- Áp dụng cùng một giá trị cho các khu vực khác nhau của DOM.
- Đồng thời đảm bảo rằng chúng tôi đặt giá trị của bộ chọn màu khác thành trạng thái chung.
Bây giờ, chúng ta hãy xem từng bước một:
- Trước tiên, hãy tìm nạp tất cả các phần tử DOM cần thiết.
- Những gì chúng tôi đang lên kế hoạch ở đây là cập nhật tất cả các bộ chọn màu và các phần tử khoảng cách với id
#selected-color
với giá trị của màu thuộc tính globalState hiện tại bất cứ khi nào sự kiện thay đổi xảy ra. - Trong trường hợp
componentA
một khi chúng ta thay đổi màu thông qua bộ chọn màu, chúng ta cần cập nhật cùng một giá trị trong 2 phần tử span (#selected-color
) – nghĩa là, một phần tử span củacomponentB
và một phần tử span có trong.global-state
vùng chứa div. - Chúng tôi làm điều này bởi vì chúng tôi muốn giữ cho tất cả các thành phần được đồng bộ hóa và chứng minh rằng giá trị của trạng thái chung vẫn giữ nguyên trên tất cả các thành phần.
- Sau đó chúng tôi tiếp tục và cập nhật
color
tài sản của trạng thái toàn cầu bằng cách sử dụngStateUtility
phương thức lớp củasetPropertyValue
. Chúng tôi chuyển sang nóevent.target.value
vì điều này chứa giá trị hiện tại có bên trong#color-picker-a
đầu vào bộ chọn màu. - Khi giá trị được đặt, chúng tôi tìm nạp lại cùng một thuộc tính bằng cách sử dụng
getPropertyByName
. Chúng tôi làm điều này để chứng minh rằng tài sảncolor
của trạng thái toàn cầu đã được cập nhật và sẵn sàng để sử dụng. - Sau đó, chúng tôi sử dụng
setValueOfSimilarElements
chức năng tiện ích để cập nhật tất cả các phần tử có cùng tên lớp/id với một số giá trị. Trong trường hợp này, chúng tôi cập nhật tất cả các#selected-color
các phần tử có giá trịcolor
. - Cuối cùng, chúng tôi cập nhật giá trị của bộ chọn màu ngược lại, đó là bộ chọn màu của thành phần B
#color-picker-b
.
Chúng tôi làm điều tương tự cho componentB
. Chúng tôi tạo một tệp có tên componentB.js
và cập nhật nó với đoạn mã sau:
import {
setValueOfSimilarElements
} from "./utilities";
import globalStyle from "./globalStyles";
// Get respective dom elements
const selectedColor = document.querySelectorAll("#selected-color");
const colorPickerA = document.getElementById("color-picker-a");
const colorPickerB = document.getElementById("color-picker-b");
/**
* Event handler whenever a change event occurs
*/
colorPickerB.onchange = (event) => {
// set the color property of the global state with current color picker's value;
globalStyle.setPropertyValue("color", event.target.value);
const color = globalStyle.getPropertyByName("color");
// A function thats sets the value of all the #selection-color dom elements
setValueOfSimilarElements(selectedColor, color);
// make sure to set the component A's color picker value is set to color picker B;
// this is done to make sure that both of the color picker have same value on change;
colorPickerA.value = color;
};
Chúng tôi làm điều tương tự như những gì chúng tôi đã làm bên trong componentA
tệp, nhưng trong trường hợp này, chúng tôi cập nhật giá trị của bộ chọn màu có bên trong componentA
(nghĩa là chúng ta cập nhật giá trị của phần tử #color-picker-a
).
Đây là cách ứng dụng của chúng tôi sẽ trông như thế nào:
Đây là liên kết đến mã:
Ưu và nhược điểm của Mẫu thiết kế Singleton
Dưới đây là một số ưu điểm của việc sử dụng mẫu thiết kế Singleton:
- Nó đảm bảo rằng chỉ một thể hiện duy nhất của lớp được tạo.
- Chúng tôi có một điểm truy cập duy nhất cho phiên bản có thể được truy cập trên toàn cầu.
Dưới đây là một số nhược điểm của mẫu thiết kế Singleton:
- Nó vi phạm nguyên tắc trách nhiệm duy nhất. Đó là, nó cố gắng giải quyết hai vấn đề cùng một lúc. Nó cố gắng giải quyết các vấn đề sau: Đảm bảo rằng một lớp sẽ chỉ có một thể hiệnvà chỉ định một điểm truy cập toàn cầu cho thể hiện của lớp singleton.
- Rất khó để viết các trường hợp kiểm thử đơn vị cho các lớp đơn lẻ. Điều này là do thứ tự thực hiện có thể thay đổi giá trị hiện tại trong trạng thái toàn cầu, vì vậy thứ tự thực hiện là quan trọng.
- Trong khi viết bài kiểm tra đơn vị, có nguy cơ một thành phần khác hoặc một mô-đun có thể đang thay đổi giá trị/thể hiện trạng thái chung. Trong những tình huống như vậy, việc gỡ lỗi trở nên khó khăn.
Tóm lược
Mẫu thiết kế singleton có thể hữu ích trong việc tạo trạng thái toàn cầu mà bất kỳ thành phần nào cũng có thể truy cập.
Vì vậy, để nói ngắn gọn về mô hình singleton:
- Đó là một mẫu hạn chế lớp chỉ tạo một thể hiện.
- Singleton pattern có thể coi là cơ bản của các thư viện quản lý state toàn cục như Redux hay React Context.
- Chúng có thể được truy cập trên toàn cầu và hoạt động như một điểm truy cập duy nhất để truy cập trạng thái toàn cầu.
Đó là tất cả.
Cảm ơn bạn đã đọc!
theo tôi trên TwitterGitHub và LinkedIn.