Trong phát triển HarmonyOS Next, LazyForEach cung cấp cơ chế tải dữ liệu lười hiệu quả, phù hợp cho việc hiển thị danh sách với lượng dữ liệu lớn, giúp cải thiện hiệu suất và quản lý bộ nhớ. Dưới đây là tổng hợp chi tiết về cách sử dụng LazyForEach.
- Hạn chế khi sử dụng
- Yêu cầu về thành phần chứa: Phải sử dụng bên trong các thành phần chứa cụ thể (List, Grid, Swiper, WaterFlow) và các thành phần này phải hỗ trợ thuộc tính cachedCount để thực hiện tải theo yêu cầu.
- Giới hạn số lượng: Chỉ được sử dụng một LazyForEach duy nhất bên trong thành phần chứa.
- Quy tắc tạo thành phần con: Mỗi lần lặp chỉ được tạo một thành phần con, và thành phần con tạo ra phải tuân theo yêu cầu của thành phần chứa cha.
- Hỗ trợ điều kiện render: Được phép sử dụng trong câu lệnh điều kiện if/else, cũng như có thể chứa câu lệnh điều kiện if/else bên trong.
- Độc đáo của khóa: Hàm tạo khóa phải tạo ra giá trị độc nhất cho mỗi dữ liệu, nếu không sẽ gây ra vấn đề về render UI.
- Hạn chế cập nhật: Cập nhật phải thông qua đối tượng DataChangeListener, gán lại dataSource sẽ gây lỗi, và thay đổi biến trạng thái sẽ không kích hoạt cập nhật UI. Để render hiệu quả, cần sử dụng phương thức onDataChange để cập nhật UI và tạo khóa mới để kích hoạt cập nhật thành phần.
- Yêu cầu về decorator: Cần sử dụng cùng với decorator @Reusable để kích hoạt tái sử dụng nút, decorator này cần được áp dụng cho thành phần trong danh sách LazyForEach.
- Quy tắc tạo khóa
- Quy tắc mặc định: Nếu không định nghĩa hàm keyGenerator, ArkUI sẽ sử dụng hàm mặc định
(item: Object, index: number) => { return viewId + '-' + index.toString(); }(viewId được tạo trong quá trình chuyển đổi, giống nhau trong cùng một thành phần LazyForEach). - Quy tắc tùy chỉnh: Có thể tự định nghĩa quy tắc tạo khóa bằng cách cung cấp hàm keyGenerator.
- Quy tắc tạo thành phần
3.1 Lần render đầu tiên
- Tạo khóa khác nhau: Tạo khóa duy nhất cho mỗi mục trong nguồn dữ liệu theo quy tắc tạo khóa.
- Ví dụ:
class BasicDataSource implements IDataSource {
// Bỏ qua mã...
}
class MyDataSource extends BasicDataSource {
// Bỏ qua mã...
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
}, (item: string) => item)
}.cachedCount(5)
}
}
- Trong ví dụ trên, quy tắc tạo khóa là
item, tạo khóaHello 0,Hello 1, v.v. cho các mục trong nguồn dữ liệu và tạo thành phần con ListItem tương ứng để render lên giao diện. - Render sai khi khóa trùng lặp: Nếu các mục dữ liệu tạo ra khóa giống nhau, hành vi của framework không thể dự đoán, có thể dẫn đến vấn đề render thành phần con khi cuộn.
3.2 Không phải lần render đầu tiên
- Khi nguồn dữ liệu thay đổi, cần gọi phương thức tương ứng của listener để thông báo cho LazyForEach cập nhật, bao gồm thêm, xóa, hoán đổi dữ liệu và thay đổi dữ liệu đơn lẻ.
- Thêm dữ liệu:
class BasicDataSource implements IDataSource {
// Bỏ qua mã...
}
class MyDataSource extends BasicDataSource {
// Bỏ qua mã...
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// Thêm dữ liệu khi click
this.data.pushData(`Hello ${this.data.totalCount()}`);
})
}, (item: string) => item)
}.cachedCount(5)
}
}
- Khi click vào thành phần con, gọi phương thức
pushDatacủa nguồn dữ liệu để thêm dữ liệu và thông báo cho LazyForEach, LazyForEach sẽ tạo thành phần con mới ở vị trí tương ứng. - Xóa dữ liệu:
class BasicDataSource implements IDataSource {
// Bỏ qua mã...
}
class MyDataSource extends BasicDataSource {
// Bỏ qua mã...
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// Xóa dữ liệu khi click
this.data.deleteData(this.data.dataArray.indexOf(item));
})
}, (item: string) => item)
}.cachedCount(5)
}
}
- Khi click vào thành phần con, gọi phương thức
deleteDatacủa nguồn dữ liệu để xóa dữ liệu và thông báo cho LazyForEach, LazyForEach sẽ xóa thành phần con ở vị trí tương ứng. - Hoán đổi dữ liệu:
class BasicDataSource implements IDataSource {
// Bỏ qua mã...
}
class MyDataSource extends BasicDataSource {
// Bỏ qua mã...
}
@Entry
@Component
struct MyComponent {
private moved: number[] = [];
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
this.moved.push(this.data.dataArray.indexOf(item));
if (this.moved.length === 2) {
// Hoán đổi dữ liệu khi click
this.data.moveData(this.moved[0], this.moved[1]);
this.moved = [];
}
})
}, (item: string) => item)
}.cachedCount(5)
}
}
- Khi click lần đầu, lưu chỉ số, khi click lần thứ hai, gọi phương thức
moveDatacủa nguồn dữ liệu để di chuyển dữ liệu và thông báo cho LazyForEach, LazyForEach sẽ hoán đổi vị trí thành phần con.
- Các vấn đề thường gặp
- Kết quả render không mong đợi: Có thể do quy tắc tạo khóa không hợp lý hoặc thao tác nguồn dữ liệu không khớp với thông báo.
- Ảnh nhấp nháy khi render lại: Có thể do xử lý ảnh không đúng, cần tối ưu hóa thao tác liên quan đến ảnh.
- Thay đổi @ObjectLink không cập nhật UI: Đảm bảo sử dụng decorator @ObjectLink và cơ chế ràng buộc dữ liệu đúng cách.
- Màn hình nhấp nháy khi sử dụng trong List: Kiểm tra cấu hình thành phần List, tần suất cập nhật dữ liệu và hiệu suất thiết bị.
Khi sử dụng LazyForEach, cần tuân thủ các hạn chế, định nghĩa quy tắc tạo khóa hợp lý, xử lý thông báo thay đổi nguồn dữ liệu chính xác, đồng thời chú ý đến các vấn đề thường gặp để tận dụng tối đa lợi ích của cơ chế tải dữ liệu lười, cải thiện hiệu suất và trải nghiệm người dùng.