Giới Thiệu
Khi xây dựng giao diện người dùng phức tạp với các bảng điều khiển có thể dock, hộp thoại modal hoặc giao diện kéo và thả, bạn thường cần di chuyển các thành phần giữa các cha DOM khác nhau. Trong Mithril.js, điều này tạo ra một vấn đề: khi di chuyển một thành phần sang cha khác, thể hiện của thành phần đó sẽ bị phá hủy và mất tất cả trạng thái cục bộ, ngay cả khi sử dụng cùng một key.
Vấn Đề
javascript
// Trước: thành phần trong sidebar
m('div.sidebar', [
m(MyComponent, { key: 'editor-1' }) // Có trạng thái cục bộ
]);
// Sau: thành phần được chuyển sang cửa sổ nổi
m('div.floating-window', [
m(MyComponent, { key: 'editor-1' }) // THỂ HIỆN MỚI, mất trạng thái!
]);
Ngay cả khi các khóa giống nhau, Mithril coi đây là việc loại bỏ thành phần và tạo mới vì cha đã thay đổi. Thuộc tính key chỉ giúp sắp xếp lại trong cùng một cha.
Giải Pháp: Cửa Hàng Trạng Thái Đếm Tham Chiếu
Ý tưởng chính là trong quá trình di chuyển cha, cả hai thể hiện của thành phần cũ và mới với cùng một khóa sẽ tồn tại đồng thời trong một khoảng thời gian ngắn. Chúng ta có thể sử dụng sự chồng chéo này để phát hiện việc di chuyển cha so với việc loại bỏ thực sự.
Cách Hoạt Động
Trong quá trình di chuyển cha (cùng khóa, cha khác):
- Thể hiện thành phần cũ tồn tại →
refCount = 1 - Thể hiện thành phần mới được tạo với cùng khóa →
refCount = 2 - Thể hiện thành phần cũ bị loại bỏ →
refCount = 1, trạng thái được bảo tồn - Thể hiện thành phần mới tiếp tục với trạng thái được phục hồi
Trong việc loại bỏ thực sự:
- Thể hiện thành phần tồn tại →
refCount = 1 - Thể hiện thành phần bị loại bỏ, không có sự thay thế →
refCount = 0, trạng thái được dọn dẹp
Ví Dụ: Bộ Đếm Liên Tục
javascript
class Counter {
static stateStore = new Map();
oninit(vnode) {
const key = vnode.key;
// Lấy hoặc tạo mục trạng thái
let entry = Counter.stateStore.get(key);
if (!entry) {
entry = {
refCount: 0,
state: { count: 0 }
};
Counter.stateStore.set(key, entry);
}
entry.refCount++;
this.persistentState = entry.state;
}
onbeforeremove(vnode) {
const key = vnode.key;
const entry = Counter.stateStore.get(key);
if (entry) {
entry.refCount--;
if (entry.refCount === 0) {
Counter.stateStore.delete(key);
}
}
}
view() {
return m('div', [
m('p', `Đếm: ${this.persistentState.count}`),
m('button', {
onclick: () => this.persistentState.count++
}, 'Tăng')
]);
}
}
// Cách sử dụng - giá trị bộ đếm sống sót sau khi di chuyển giữa các container khác nhau
m(Counter, { key: 'my-counter' });
Lợi Ích
- Tự Động: Hoạt động với các thuộc tính
keyhiện có - An Toàn Bộ Nhớ: Đếm tham chiếu ngăn ngừa rò rỉ
- Bản Địa Khung: Chỉ sử dụng các hook vòng đời của Mithril
- Đơn Giản: Chỉ cần thêm mẫu này vào bất kỳ thành phần nào cần thiết
Kết Luận
Mẫu này lấp đầy một khoảng trống trong vòng đời thành phần của Mithril, cho phép các hành vi UI tinh vi như các bảng điều khiển không thể dock và hộp thoại modal liên tục. Cách tiếp cận đếm tham chiếu là thanh lịch vì nó tận dụng thời gian của chính Mithril - khoảnh khắc ngắn ngủi khi cả hai vnode cũ và mới tồn tại trong quá trình di chuyển cha trở thành tín hiệu của chúng ta để bảo tồn trạng thái.
Thực Hành Tốt Nhất
- Sử Dụng Khóa Đúng Cách: Đảm bảo sử dụng khóa duy nhất cho từng thể hiện để tránh nhầm lẫn.
- Quản Lý Trạng Thái Một Cách Kỹ Lưỡng: Đảm bảo rằng trạng thái được dọn dẹp đúng cách khi không còn cần thiết.
Cạm Bẫy Thường Gặp
- Không Quản Lý Đếm Tham Chiếu: Bỏ qua việc giảm đếm tham chiếu có thể dẫn đến rò rỉ bộ nhớ.
- Sử Dụng Khóa Không Chính Xác: Sử dụng khóa không chính xác có thể gây ra mất dữ liệu trạng thái.
Mẹo Hiệu Suất
- Tối Ưu Hóa Điểm Nhấn: Sử dụng các điểm nhấn nhỏ để đảm bảo rằng việc cập nhật trạng thái không làm chậm giao diện người dùng.
Câu Hỏi Thường Gặp
1. Làm thế nào để di chuyển một thành phần mà không mất trạng thái?
- Sử dụng phương pháp đếm tham chiếu như đã mô tả ở trên.
2. Điều gì sẽ xảy ra nếu tôi không sử dụng khóa?
- Nếu không sử dụng khóa, Mithril sẽ không biết cách giữ lại trạng thái, dẫn đến việc mất dữ liệu.
3. Có cách nào khác để quản lý trạng thái không?
- Có thể sử dụng các giải pháp quản lý trạng thái ngoài Mithril như Redux hoặc MobX, nhưng phương pháp này đơn giản và hiệu quả trong nhiều trường hợp.