Chương 1: derivedStateOf – Vũ Khí Chống Recomposition Thừa Trong Jetpack Compose
Giới thiệu
Một trong những điểm khác biệt lớn nhất giữa Jetpack Compose và View System truyền thống là cơ chế Recomposition.
Compose rất thông minh trong việc chỉ cập nhật những phần UI cần thiết. Tuy nhiên, nếu không quản lý state đúng cách, ứng dụng vẫn có thể xảy ra:
- Recomposition quá nhiều
- Tăng CPU usage
- Giảm FPS khi scroll
- Lag trên các màn hình phức tạp
Đó là lý do derivedStateOf ra đời.
API này cho phép chúng ta tạo ra một state dẫn xuất (derived state), giúp Compose chỉ thực hiện recomposition khi giá trị cuối cùng thực sự thay đổi.
Vấn đề thường gặp
Giả sử chúng ta có một danh sách dài:
@Composable
fun UserList() {
val listState = rememberLazyListState()
val showButton =
listState.firstVisibleItemIndex > 0
Box {
LazyColumn(
state = listState
) {
items(1000) {
Text("Item $it")
}
}
if (showButton) {
ScrollToTopButton()
}
}
}
Đoạn code trên hoạt động chính xác.
Tuy nhiên:
listState.firstVisibleItemIndex
thay đổi liên tục trong quá trình scroll.
Ví dụ:
0
1
2
3
4
5
6
7
...
Mỗi lần giá trị thay đổi, Compose sẽ đánh giá lại phần UI phụ thuộc vào state đó.
Trên các màn hình lớn, điều này có thể tạo ra lượng recomposition không cần thiết.
derivedStateOf là gì?
derivedStateOf cho phép tạo một state mới từ các state khác.
val showButton by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 0
}
}
Compose sẽ theo dõi giá trị trả về.
Chỉ khi kết quả thực sự thay đổi:
false -> true
hoặc
true -> false
thì UI mới được thông báo cập nhật.
Điều gì xảy ra bên trong?
Không sử dụng derivedStateOf:
0
1
2
3
4
5
6
Compose liên tục nhận state mới.
Sử dụng derivedStateOf:
false
true
true
true
true
true
Compose chỉ quan tâm đến việc giá trị cuối cùng có thay đổi hay không.
Kết quả là số lần recomposition giảm đáng kể.
Ví dụ thực tế: Scroll To Top Button
@Composable
fun HomeScreen() {
val listState =
rememberLazyListState()
val showScrollTop by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 5
}
}
Box {
LazyColumn(
state = listState
) {
items(500) {
Text("Item $it")
}
}
AnimatedVisibility(
visible = showScrollTop
) {
FloatingActionButton(
onClick = {}
) {
Icon(
Icons.Default.KeyboardArrowUp,
contentDescription = null
)
}
}
}
}
Lúc này nút chỉ xuất hiện khi người dùng scroll quá item thứ 5.
Ví dụ thực tế: Form Validation
Một màn hình đăng nhập đơn giản:
var email by remember {
mutableStateOf("")
}
var password by remember {
mutableStateOf("")
}
var termsAccepted by remember {
mutableStateOf(false)
}
Nút Login chỉ được bật khi:
email.isNotBlank() &&
password.length >= 8 &&
termsAccepted
Sử dụng:
val isValid by remember(
email,
password,
termsAccepted
) {
derivedStateOf {
email.isNotBlank() &&
password.length >= 8 &&
termsAccepted
}
}
Sau đó:
Button(
enabled = isValid,
onClick = {}
) {
Text("Login")
}
Code rõ ràng hơn và dễ bảo trì hơn.
Ví dụ thực tế: AdMob
Trong nhiều ứng dụng miễn phí:
val shouldShowAds by remember {
derivedStateOf {
!userState.isPremium
}
}
Hiển thị banner:
if (shouldShowAds) {
BannerAd()
}
Compose sẽ không phải đánh giá lại logic quảng cáo mỗi khi các state không liên quan thay đổi.
Những sai lầm phổ biến
Sai lầm #1: Dùng cho phép tính đơn giản
val fullName by derivedStateOf {
"$firstName $lastName"
}
Không cần thiết.
Chi phí tính toán thấp hơn nhiều so với chi phí quản lý derived state.
Sai lầm #2: Chỉ bọc lại state
val count by derivedStateOf {
uiState.count
}
Không mang lại lợi ích nào.
Sai lầm #3: Lạm dụng mọi nơi
derivedStateOf { ... }
derivedStateOf { ... }
derivedStateOf { ... }
derivedStateOf { ... }
derivedStateOf không miễn phí.
Chỉ nên dùng khi thực sự cần tối ưu.
Khi nào nên sử dụng?
Scroll State
LazyListState
PagerState
ScrollState
Animation State
Animatable
Transition
Tính toán tốn kém
filter()
groupBy()
map()
sortedBy()
Chỉ quan tâm đến kết quả cuối
Ví dụ:
isValid
showButton
shouldShowAds
Khi nào không nên sử dụng?
Không nên dùng cho:
count + 1
"$firstName $lastName"
Text("Hello")
Các phép tính cực kỳ nhẹ.
Quy tắc Senior Compose
Một nguyên tắc đơn giản:
Nếu state thay đổi hàng chục lần mỗi giây nhưng UI chỉ cần phản ứng khi kết quả cuối thay đổi, hãy cân nhắc sử dụng
derivedStateOf.
Điều này đặc biệt hữu ích với:
- LazyColumn
- HorizontalPager
- Animation
- Gesture
- Ad Visibility
- Form Validation
Kết luận
derivedStateOf là một trong những API quan trọng nhất để tối ưu hiệu năng Compose.
Lợi ích chính:
- Giảm số lần recomposition
- Giảm CPU usage
- Tăng FPS khi scroll
- Tối ưu màn hình lớn
- Giúp code rõ ràng hơn
Trong các dự án Compose production, đây là một kỹ thuật xuất hiện rất thường xuyên ở những màn hình có lượng state thay đổi liên tục.
Top comments (0)