Giới thiệu: Expo SDK 55 và React Native 0.83 - Bước nhảy vọt của hệ sinh thái di động năm 2026
Đầu năm 2026 đánh dấu một cột mốc quan trọng trong hệ sinh thái React Native khi Expo chính thức phát hành SDK 55, được xây dựng trên nền tảng React Native 0.83.1 và React 19.2. Nói thật là lần này không chỉ là một bản cập nhật thông thường mà là một bước chuyển mình mang tính lịch sử: Legacy Architecture bị loại bỏ hoàn toàn, Hermes v1 xuất hiện với hiệu năng vượt trội, các API hiệu suất web được tích hợp sẵn, và hàng loạt công cụ phát triển mới giúp trải nghiệm lập trình mượt mà hơn bao giờ hết.
Nếu bạn đang phát triển ứng dụng React Native bằng Expo, SDK 55 mang đến những thay đổi bạn cần nắm vững ngay lập tức. Mình thấy rằng từ hệ thống cập nhật OTA nhỏ gọn hơn 75%, đến các component UI được thiết kế lại theo chuẩn SwiftUI và Jetpack Compose, mọi thứ đều hướng tới mục tiêu: hiệu suất cao hơn, mã nguồn sạch hơn, và trải nghiệm người dùng tự nhiên hơn.
Trong bài viết này, chúng ta sẽ đi sâu vào từng tính năng mới, đi kèm ví dụ mã thực tế và hướng dẫn nâng cấp chi tiết từ SDK 54 lên SDK 55. Hãy cùng bắt đầu nhé.
Kiến Trúc Mới là tiêu chuẩn duy nhất
SDK 54 là phiên bản cuối cùng hỗ trợ Legacy Architecture. Kể từ SDK 55, New Architecture (Kiến Trúc Mới) trở thành tiêu chuẩn duy nhất và bắt buộc - nghĩa là bạn không còn lựa chọn nào khác ngoài việc sử dụng Fabric, TurboModules và chế độ Bridgeless.
Cụ thể, cờ newArchEnabled đã bị loại bỏ hoàn toàn khỏi app.json. Trước đây, bạn cần khai báo như sau để bật Kiến Trúc Mới:
// app.json - SDK 54 (phiên bản cũ, KHÔNG còn áp dụng)
{
"expo": {
"newArchEnabled": true,
"plugins": [
["expo-build-properties", {
"android": { "newArchEnabled": true },
"ios": { "newArchEnabled": true }
}]
]
}
}
Từ SDK 55 trở đi, bạn không cần và không thể khai báo cờ này nữa. Kiến Trúc Mới luôn được bật mặc định:
// app.json - SDK 55 (không còn newArchEnabled)
{
"expo": {
"name": "my-app",
"slug": "my-app",
"scheme": "myapp",
"plugins": ["expo-router"]
}
}
Sự thay đổi này mang lại nhiều lợi ích quan trọng mà bạn sẽ thấy ngay:
- Fabric Renderer: Hệ thống render mới thay thế hoàn toàn Paper, mang lại khả năng render đồng thời (concurrent rendering) và hiệu suất cuộn mượt mà hơn hẳn.
- TurboModules: Tải module gốc theo kiểu lazy, giảm thời gian khởi động ứng dụng đáng kể so với Bridge cũ.
- Bridgeless Mode: Loại bỏ hoàn toàn cầu nối JavaScript-Native, giảm độ trễ giao tiếp giữa hai tầng (thú thật là mình rất thích cải tiến này).
- Codegen tự động: Tự động tạo mã giao tiếp giữa JavaScript và Native từ các đặc tả TypeScript/Flow.
Nếu dự án của bạn vẫn đang dùng các thư viện phụ thuộc vào Legacy Architecture, đây là lúc bạn cần cập nhật hoặc tìm thư viện thay thế. Tin vui là hầu hết các thư viện phổ biến như react-native-reanimated, react-native-gesture-handler, và react-native-screens đều đã hỗ trợ đầy đủ Kiến Trúc Mới rồi.
React 19.2 và các API mới
SDK 55 đi kèm React 19.2, mang đến hai API đáng chú ý nhất: component <Activity> và hook useEffectEvent.
Component Activity: Quản lý trạng thái ẩn/hiện thông minh
Component <Activity> cho phép bạn kiểm soát trạng thái hiển thị của các phần giao diện mà không mất đi state. Nó có hai chế độ: visible (hiển thị) và hidden (ẩn đi).
Khi chuyển sang chế độ hidden, Activity sẽ ẩn các phần tử con, gỡ bỏ các effect, và trì hoãn mọi cập nhật cho đến khi React không còn công việc nào khác cần xử lý. Điểm thú vị là khi quay lại visible, mọi thứ được khôi phục nguyên trạng - bao gồm cả state và vị trí cuộn.
import { Activity, useState } from 'react';
import { View, Text, Button, FlatList } from 'react-native';
function TabContainer() {
const [activeTab, setActiveTab] = useState('home');
return (
<View style={{ flex: 1 }}>
{/* Tab Home - luôn render nhưng ẩn/hiện theo trạng thái */}
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
<HomeScreen />
</Activity>
{/* Tab Profile - giữ nguyên state khi chuyển tab */}
<Activity mode={activeTab === 'profile' ? 'visible' : 'hidden'}>
<ProfileScreen />
</Activity>
{/* Tab Settings - pre-render sẵn để chuyển tab nhanh */}
<Activity mode={activeTab === 'settings' ? 'visible' : 'hidden'}>
<SettingsScreen />
</Activity>
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
<Button title="Trang chủ" onPress={() => setActiveTab('home')} />
<Button title="Hồ sơ" onPress={() => setActiveTab('profile')} />
<Button title="Cài đặt" onPress={() => setActiveTab('settings')} />
</View>
</View>
);
}
Ứng dụng thực tế của Activity rất rộng: bạn có thể pre-render các màn hình mà người dùng có khả năng điều hướng tới tiếp theo, hoặc giữ nguyên trạng thái của các màn hình khi người dùng chuyển đi rồi quay lại. Đặc biệt hữu ích cho các ứng dụng có nhiều tab hoặc luồng điều hướng phức tạp.
Hook useEffectEvent: Tách biệt logic sự kiện khỏi Effect
Hook useEffectEvent giải quyết một vấn đề kinh điển mà nhiều dev React Native hay gặp phải: khi bạn cần truy cập giá trị mới nhất của props hoặc state bên trong một Effect mà không muốn thêm chúng vào mảng phụ thuộc.
import { useEffect, useState } from 'react';
import { useEffectEvent } from 'react';
import { AppState } from 'react-native';
function AnalyticsTracker({ screenName, userId }) {
const [visitCount, setVisitCount] = useState(0);
// useEffectEvent luôn "nhìn thấy" giá trị mới nhất
// của screenName và userId mà không cần khai báo
// trong mảng phụ thuộc của useEffect
const onAppStateChange = useEffectEvent((nextAppState) => {
if (nextAppState === 'active') {
setVisitCount(c => c + 1);
// screenName và userId luôn là giá trị mới nhất
analytics.track('screen_visit', {
screen: screenName,
user: userId,
visits: visitCount + 1,
});
}
});
useEffect(() => {
// Không cần thêm screenName, userId vào deps
const subscription = AppState.addEventListener(
'change',
onAppStateChange
);
return () => subscription.remove();
}, []); // Mảng phụ thuộc rỗng - effect chỉ chạy một lần
return null;
}
Điểm mấu chốt ở đây là useEffectEvent tạo ra một hàm luôn truy cập được giá trị mới nhất của props và state (tương tự sự kiện DOM), nhưng bản thân hàm đó không được khai báo trong mảng phụ thuộc của Effect. Điều này giúp loại bỏ các vòng lặp Effect vô hạn và làm code rõ ràng hơn rất nhiều.
Hermes v1 - Engine JavaScript thế hệ mới
Hermes v1 là bản nâng cấp lớn của engine JavaScript được Meta phát triển riêng cho React Native. Trong SDK 55, Hermes v1 được cung cấp dưới dạng opt-in (tùy chọn bật) thông qua plugin expo-build-properties.
Cải thiện hiệu năng đáng kể
Các bài benchmark cho thấy Hermes v1 mang lại cải thiện đáng kể trên thiết bị Android cấp thấp (và mình nghĩ đây là điều rất quan trọng cho thị trường Việt Nam):
- Tải bundle nhanh hơn đến 9%: Thời gian nạp mã JavaScript giảm rõ rệt, đặc biệt với các bundle lớn.
- Time to Interactive (TTI) nhanh hơn 7.6%: Người dùng có thể tương tác với ứng dụng sớm hơn sau khi mở app.
- Hỗ trợ JavaScript hiện đại tốt hơn: ES6 classes,
const/let,async/awaitđược tối ưu hóa ở tầng engine thay vì phải chuyển đổi qua Babel.
Cách bật Hermes v1
Để kích hoạt Hermes v1, bạn cần cập nhật cấu hình expo-build-properties trong app.json:
// app.json - Bật Hermes v1 trong SDK 55
{
"expo": {
"plugins": [
["expo-build-properties", {
"android": {
"useHermesV1": true,
"buildReactNativeFromSource": true
},
"ios": {
"useHermesV1": true,
"buildReactNativeFromSource": true
}
}]
]
}
}
Lưu ý quan trọng: Việc sử dụng Hermes v1 hiện yêu cầu build React Native từ mã nguồn (buildReactNativeFromSource: true). Điều này sẽ làm tăng đáng kể thời gian build native - có thể thêm từ 5-15 phút tùy cấu hình máy. Trong các phiên bản React Native tương lai, khi Hermes v1 trở thành mặc định, hạn chế này sẽ được gỡ bỏ.
Khuyến nghị của mình: Nếu ứng dụng của bạn nhắm đến thị trường có nhiều thiết bị cấp thấp và hiệu năng khởi động là ưu tiên hàng đầu, Hermes v1 là lựa chọn đáng để đầu tư. Với các dự án khác, bạn có thể chờ đến khi nó trở thành mặc định trong SDK tương lai.
Hermes Bytecode Diffing cho OTA Updates
Một trong những tính năng hấp dẫn nhất của SDK 55 là Hermes Bytecode Diffing - tối ưu hóa kích thước cập nhật OTA (Over-The-Air) cho các bundle Hermes.
Cách hoạt động
Thông thường, khi bạn phát hành bản cập nhật OTA qua expo-updates hoặc EAS Update, toàn bộ file bytecode Hermes mới sẽ được tải xuống thiết bị người dùng. Nói thật là khá tốn băng thông. Nhưng với Bytecode Diffing, thay vì tải toàn bộ file, hệ thống chỉ tải về bản vá nhị phân (binary diff/patch) chứa phần khác biệt giữa phiên bản cũ và mới.
Kết quả rất ấn tượng: kích thước tải xuống giảm khoảng 75% cho cả Android và iOS. Cập nhật được tải nhanh hơn, tiêu tốn ít băng thông hơn, và tỷ lệ người dùng nhận bản cập nhật mới tăng đáng kể.
Bật tính năng Bytecode Diffing
Tính năng này là opt-in trong SDK 55 và sẽ được bật mặc định từ SDK 56. Để kích hoạt, thêm cấu hình sau:
// app.json - Bật Hermes Bytecode Diffing
{
"expo": {
"updates": {
"url": "https://u.expo.dev/your-project-id"
},
"plugins": [
["expo-updates", {
"enableBsdiffPatchSupport": true
}]
]
}
}
Sau khi bật, quy trình phát hành cập nhật không thay đổi gì cả:
# Phát hành bản cập nhật OTA như bình thường
eas update --branch production --message "Sửa lỗi giao diện trang chủ"
# Hệ thống sẽ tự động tạo và phục vụ bản vá nhị phân
# thay vì toàn bộ bytecode bundle
Bạn có thể xác nhận rằng các bản vá nhị phân đang được phục vụ bằng cách kiểm tra trang Update Details trên dashboard của EAS. Tại đây sẽ hiển thị kích thước bản vá so với kích thước bundle đầy đủ.
Công cụ DevTools mới trong React Native 0.83
React Native 0.83 nâng cấp bộ công cụ phát triển (DevTools) lên một tầm cao mới với ba cải tiến lớn: Network Inspection, Performance Tracing, và ứng dụng desktop tích hợp.
Network Inspection - Kiểm tra yêu cầu mạng
Giờ đây, bạn có thể xem và phân tích tất cả các yêu cầu mạng mà ứng dụng thực hiện trực tiếp trong DevTools - khá tiện luôn. Các yêu cầu được ghi lại bao gồm thông tin chi tiết như thời gian phản hồi, headers gửi/nhận, và bản xem trước nội dung response.
Đặc biệt, lần đầu tiên bạn có thể sử dụng tab Initiator để xem chính xác dòng code nào trong ứng dụng đã phát sinh yêu cầu mạng đó. Hiện tại hỗ trợ các yêu cầu qua fetch(), XMLHttpRequest, và <Image>.
Performance Tracing - Ghi và phân tích hiệu suất
Performance Tracing cho phép bạn ghi lại một phiên hiệu suất trong ứng dụng để hiểu cách mã JavaScript đang chạy và những thao tác nào tốn nhiều thời gian nhất. Công cụ hiển thị trong một timeline thống nhất bao gồm: JavaScript execution, React Performance tracks, Network events, và custom User Timings.
Ứng dụng desktop tích hợp - Không cần trình duyệt
Trong phiên bản 0.83, React Native giới thiệu ứng dụng desktop tích hợp mới với những ưu điểm đáng chú ý:
- Không cần trình duyệt: DevTools chạy trong ứng dụng desktop nhẹ, được notarize sẵn, không phụ thuộc vào Chrome hay bất kỳ trình duyệt nào.
- Khởi động nhanh hơn: Nhờ là ứng dụng nhị phân gốc, thời gian mở DevTools giảm đáng kể.
- Ổn định hơn: Tránh được các vấn đề do các extension Chrome đã cài sẵn gây ra, vì DevTools chạy tách biệt hoàn toàn (mình thấy điểm này rất hay).
- Cải thiện windowing trên macOS: Hỗ trợ multitasking, tự động raise cửa sổ khi gặp breakpoint, và ghi nhớ vị trí cửa sổ.
Web Performance APIs - Đo lường hiệu suất chuẩn Web
React Native 0.83 đưa các Web Performance APIs từ trạng thái thử nghiệm sang ổn định (stable), mang đến cho lập trình viên những công cụ đo lường hiệu suất mạnh mẽ theo chuẩn web ngay trong ứng dụng di động.
Các API ổn định
Danh sách các API hiệu suất đã ổn định bao gồm:
- High Resolution Time:
performance.now()vàperformance.timeOrigin- đo thời gian chính xác đến micro giây. - Performance Timeline:
PerformanceObservercùng các phương thứcgetEntries(),getEntriesByName(),getEntriesByType(). - User Timing:
performance.mark()vàperformance.measure()- đánh dấu và đo lường các đoạn code tùy chỉnh. - Event Timing: Theo dõi thời gian xử lý sự kiện qua
PerformanceObserver. - Long Tasks API: Phát hiện các tác vụ JavaScript chạy lâu làm chậm giao diện.
Đặc biệt, PerformanceObserver hoạt động trong cả bản production build, cho phép bạn thu thập dữ liệu hiệu suất thực tế từ người dùng và tích hợp với các hệ thống phân tích.
import { PerformanceObserver } from 'react-native';
// Theo dõi các tác vụ chạy lâu (Long Tasks)
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn(
`Tác vụ dài phát hiện: ${entry.duration}ms`,
`Loại: ${entry.entryType}`
);
// Gửi dữ liệu lên hệ thống giám sát
analytics.track('long_task', {
duration: entry.duration,
startTime: entry.startTime,
});
}
});
longTaskObserver.observe({ type: 'longtask', buffered: true });
// Đo lường thời gian tải dữ liệu
performance.mark('fetch-start');
const data = await fetchUserData();
performance.mark('fetch-end');
performance.measure('fetch-duration', 'fetch-start', 'fetch-end');
const measures = performance.getEntriesByName('fetch-duration');
console.log(`Thời gian tải dữ liệu: ${measures[0].duration}ms`);
IntersectionObserver (Canary) - Theo dõi phần tử hiển thị
React Native 0.83 cũng giới thiệu IntersectionObserver ở trạng thái canary (thử nghiệm). API này cho phép bạn theo dõi khi một phần tử xuất hiện hoặc biến mất khỏi vùng nhìn thấy - vô cùng hữu ích cho lazy loading hình ảnh, cuộn vô hạn (infinite scroll), và theo dõi khả năng hiển thị của component.
import { useRef, useEffect } from 'react';
import { View, Image, Text } from 'react-native';
// Canary API - chỉ dùng cho mục đích thử nghiệm
function LazyImage({ src, placeholder }) {
const viewRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
if (!viewRef.current) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 } // Kích hoạt khi 10% phần tử hiển thị
);
observer.observe(viewRef.current);
return () => observer.disconnect();
}, []);
return (
<View ref={viewRef}>
{isVisible ? (
<Image source={{ uri: src }} style={{ width: 300, height: 200 }} />
) : (
<View style={{ width: 300, height: 200, backgroundColor: '#e0e0e0' }} />
)}
</View>
);
}
Lưu ý: IntersectionObserver đang ở trạng thái canary và chưa nên được sử dụng trong ứng dụng production. API có thể thay đổi trong các phiên bản tương lai.
Thay đổi UI Components - Hướng tới chuẩn nền tảng gốc
Expo UI tiếp tục hoàn thiện trong SDK 55 với việc đổi tên và hợp nhất các component để phù hợp hơn với quy ước của SwiftUI (iOS) và Jetpack Compose (Android). Ba thay đổi chính bạn cần lưu ý (không quá phức tạp đâu):
DateTimePicker chuyển thành DatePicker
Component DateTimePicker được đổi tên thành DatePicker và bổ sung tính năng chọn khoảng thời gian (range selection):
// SDK 54 - Cách cũ
import { DateTimePicker } from '@expo/ui';
function OldDatePicker() {
return (
<DateTimePicker
mode="date"
value={new Date()}
onChange={(event, date) => console.log(date)}
/>
);
}
// SDK 55 - Cách mới với DatePicker và hỗ trợ range
import { DatePicker } from '@expo/ui';
function NewDatePicker() {
const [dateRange, setDateRange] = useState({
start: new Date(2026, 0, 1),
end: new Date(2026, 0, 31),
});
return (
<DatePicker
mode="range"
startDate={dateRange.start}
endDate={dateRange.end}
onRangeChange={({ startDate, endDate }) => {
setDateRange({ start: startDate, end: endDate });
}}
title="Chọn khoảng thời gian"
/>
);
}
Switch chuyển thành Toggle
Component Switch được đổi tên thành Toggle để phù hợp với thuật ngữ của SwiftUI:
// SDK 54 - Cách cũ
import { Switch } from '@expo/ui';
// SDK 55 - Cách mới
import { Toggle } from '@expo/ui';
function SettingsScreen() {
const [darkMode, setDarkMode] = useState(false);
const [notifications, setNotifications] = useState(true);
return (
<View style={{ padding: 16 }}>
<Toggle
value={darkMode}
onValueChange={setDarkMode}
label="Chế độ tối"
/>
<Toggle
value={notifications}
onValueChange={setNotifications}
label="Thông báo đẩy"
/>
</View>
);
}
CircularProgress và LinearProgress hợp nhất thành ProgressView
Hai component CircularProgress và LinearProgress được gộp thành ProgressView duy nhất, sử dụng prop progressViewStyle để xác định kiểu hiển thị:
// SDK 54 - Hai component riêng biệt
import { CircularProgress, LinearProgress } from '@expo/ui';
// SDK 55 - Một component thống nhất
import { ProgressView } from '@expo/ui';
function DownloadProgress({ progress }) {
return (
<View>
{/* Thanh tiến trình dạng tuyến tính */}
<ProgressView
progress={progress}
progressViewStyle="linear"
color="#4CAF50"
/>
{/* Thanh tiến trình dạng vòng tròn */}
<ProgressView
progress={progress}
progressViewStyle="circular"
color="#2196F3"
/>
</View>
);
}
Ngoài ra, component Section cũng được cập nhật: thay vì cấu hình qua constructor, giờ sử dụng prop title trực tiếp (đơn giản hơn nhiều).
Apple Zoom Transition - Chuyển cảnh kiểu iOS gốc
SDK 55 giới thiệu Apple Zoom Transition, cho phép tạo hiệu ứng chuyển cảnh phóng to/thu nhỏ tương tác trên iOS, tận dụng API zoom transition gốc của iOS 18+. Đây là hiệu ứng mà bạn thấy trong các ứng dụng Apple như Photos khi nhấn vào ảnh thu nhỏ để xem ảnh toàn màn hình (khá mượt mà và chuyên nghiệp).
Tính năng này tạo ra cảm giác không gian giữa các route, giúp người dùng hiểu được mối quan hệ phân cấp giữa các màn hình. Hiệu ứng hỗ trợ tương tác - người dùng có thể vuốt để quay lại và phần tử sẽ thu nhỏ về vị trí ban đầu.
import { Link } from 'expo-router';
import { View, Text, Image, FlatList } from 'react-native';
// Màn hình danh sách - nguồn của transition
function PhotoGrid() {
const photos = usePhotos();
return (
<FlatList
data={photos}
numColumns={3}
renderItem={({ item }) => (
// Link.AppleZoom đánh dấu phần tử nguồn
<Link.AppleZoom href={`/photo/${item.id}`}>
<Image
source={{ uri: item.thumbnail }}
style={{ width: 120, height: 120 }}
/>
</Link.AppleZoom>
)}
/>
);
}
// Màn hình chi tiết - đích của transition
function PhotoDetail() {
const { id } = useLocalSearchParams();
const photo = usePhoto(id);
return (
<View style={{ flex: 1 }}>
{/* Link.AppleZoomTarget chỉ định phần tử đích */}
<Link.AppleZoomTarget>
<Image
source={{ uri: photo.fullSize }}
style={{ width: '100%', height: 400 }}
resizeMode="contain"
/>
</Link.AppleZoomTarget>
<Text style={{ padding: 16 }}>{photo.caption}</Text>
</View>
);
}
Yêu cầu: Apple Zoom Transition chỉ hoạt động trên iOS 18 trở lên. Trên các phiên bản iOS cũ hơn hoặc Android, ứng dụng sẽ tự động fallback về hiệu ứng chuyển cảnh mặc định. Tính năng này hiện ở trạng thái alpha và chỉ khả dụng trên iOS.
Stack.Toolbar API - Xây dựng thanh công cụ trên iOS
Stack.Toolbar là API alpha mới trong SDK 55, cho phép bạn thêm các nút thanh công cụ gốc iOS (UIToolbar) vào các màn hình Stack. API này đặc biệt hữu ích để xây dựng các menu hành động và thanh công cụ phía dưới màn hình.
import { Stack } from 'expo-router';
function ArticleScreen() {
return (
<>
<Stack.Screen
options={{
title: 'Chi tiết bài viết',
}}
/>
{/* Thanh công cụ phía dưới với các nút hành động */}
<Stack.Toolbar>
<Stack.Toolbar.Item
icon="square.and.arrow.up" {/* SF Symbol */}
title="Chia sẻ"
onPress={() => shareArticle()}
/>
<Stack.Toolbar.Item
icon="bookmark"
title="Lưu"
onPress={() => bookmarkArticle()}
/>
<Stack.Toolbar.Spacer />
<Stack.Toolbar.Item
icon="trash"
title="Xóa"
tint="red"
onPress={() => deleteArticle()}
/>
</Stack.Toolbar>
{/* Nội dung màn hình */}
<ArticleContent />
</>
);
}
Cách dễ nhất để thêm biểu tượng là sử dụng SF Symbols - thư viện biểu tượng tích hợp sẵn của Apple - bằng cách truyền tên symbol trực tiếp vào prop icon. Hiện tại, Stack.Toolbar chỉ khả dụng trên iOS, với kế hoạch hỗ trợ các API tương tự trên Android trong tương lai.
expo-brownfield Package - Tích hợp Expo vào ứng dụng Native hiện có
SDK 55 giới thiệu gói expo-brownfield hoàn toàn mới, được thiết kế để tích hợp Expo vào các ứng dụng native hiện có (brownfield) một cách biệt lập và chuyên nghiệp. Cá nhân mình rất thích cách tiếp cận này.
Mô hình tích hợp biệt lập
Thay vì yêu cầu nhóm phát triển native phải cài đặt Node.js và thiết lập môi trường React Native, expo-brownfield cho phép bạn phát triển phần React Native riêng biệt và đóng gói thành thư viện native:
- AAR (Android Archive) cho Android
- XCFramework cho iOS
Các nhà phát triển native chỉ cần thêm thư viện này vào dự án của họ như bất kỳ thư viện native nào khác, không cần biết gì về React Native hay Node.js (rất tiện cho các team lớn).
CLI và API
Gói cung cấp CLI để build các artifact và API để giao tiếp hai chiều giữa ứng dụng native và phần Expo nhúng:
# Cài đặt expo-brownfield
npx expo install expo-brownfield
# Build artifact cho Android (AAR)
npx expo-brownfield build --platform android
# Build artifact cho iOS (XCFramework)
npx expo-brownfield build --platform ios
API giao tiếp hai chiều cho phép gửi tin nhắn qua lại giữa mã native và React Native:
// Trong code React Native - nhận và gửi tin nhắn
import { BrownfieldBridge } from 'expo-brownfield';
// Lắng nghe tin nhắn từ phía native
BrownfieldBridge.addMessageListener('userAuthenticated', (payload) => {
console.log('Người dùng đã xác thực:', payload.userId);
// Cập nhật trạng thái trong React Native
setUser(payload);
});
// Gửi tin nhắn sang phía native
BrownfieldBridge.sendMessage('navigateToNative', {
screen: 'PaymentScreen',
orderId: '12345',
});
// Gửi tin nhắn với callback phản hồi
const result = await BrownfieldBridge.sendMessageAsync(
'getDeviceInfo',
{ includeLocation: true }
);
console.log('Thông tin thiết bị:', result);
Mô hình này lý tưởng cho các doanh nghiệp lớn muốn dần dần đưa React Native vào ứng dụng native hiện có, hoặc cho các nhóm phát triển muốn chia sẻ tính năng React Native giữa nhiều ứng dụng native khác nhau.
Template mặc định mới - Cấu trúc dự án cải tiến
SDK 55 mang đến template mặc định hoàn toàn mới khi tạo dự án, tập trung vào quy ước nền tảng gốc và cấu trúc thư mục rõ ràng hơn.
Native Tabs thay cho JavaScript Tabs
Template mới sử dụng Native Tabs API thay cho JavaScript tabs. Điều này mang lại trải nghiệm tab hoàn toàn gốc trên iOS (UITabBarController) và Android (Material Bottom Navigation), đồng thời cung cấp layout web responsive cho trình duyệt.
Cấu trúc thư mục /src/app
Thay đổi lớn nhất trong cấu trúc dự án: mã ứng dụng giờ nằm trong /src/app thay vì /app. Điều này giúp phân tách rõ ràng giữa mã ứng dụng và các file cấu hình ở thư mục gốc (mình nghĩ đây là một cải tiến hợp lý):
# Cấu trúc dự án mới SDK 55
my-app/
├── app.json # Cấu hình Expo
├── package.json # Dependencies
├── tsconfig.json # Cấu hình TypeScript
├── src/
│ ├── app/ # Thư mục route (Expo Router)
│ │ ├── _layout.tsx # Layout gốc với Native Tabs
│ │ ├── index.tsx # Trang chủ (tab đầu tiên)
│ │ ├── explore.tsx # Tab khám phá
│ │ └── settings.tsx # Tab cài đặt
│ ├── components/ # Component tái sử dụng
│ │ ├── ThemedText.tsx
│ │ └── ThemedView.tsx
│ └── utils/ # Hàm tiện ích
│ └── api.ts
└── assets/ # Tài nguyên tĩnh
├── images/
└── fonts/
Để tạo dự án mới với template này:
# Tạo dự án mới với template SDK 55
npx create-expo-app@latest my-app --template default@next
# Hoặc khi SDK 55 chính thức ra mắt
npx create-expo-app@latest my-app
Template mới cũng tích hợp sẵn xử lý safe area cho native-tabs layout trên cả iOS và Android, giúp bạn không cần lo lắng về notch hay thanh trạng thái.
Hướng dẫn nâng cấp từ SDK 54 lên SDK 55
Dưới đây là hướng dẫn từng bước chi tiết để nâng cấp dự án từ SDK 54 lên SDK 55. Hãy thực hiện cẩn thận và kiểm tra kỹ từng bước nhé.
Bước 1: Cập nhật Expo SDK và các dependencies
# Cập nhật Expo SDK lên phiên bản 55
npx expo install expo@^55.0.0 --fix
# Lệnh --fix sẽ tự động cập nhật các dependencies
# tương thích với SDK 55
Bước 2: Kiểm tra tương thích với expo-doctor
# Chạy kiểm tra tương thích
npx expo-doctor
# Lệnh này sẽ phát hiện:
# - Các package chưa tương thích
# - Phiên bản sai lệch
# - Vấn đề cấu hình
Bước 3: Loại bỏ cấu hình Legacy Architecture
Xóa mọi tham chiếu đến newArchEnabled trong app.json vì Kiến Trúc Mới giờ là mặc định duy nhất:
// app.json - Trước khi nâng cấp (SDK 54)
{
"expo": {
"newArchEnabled": true, // XÓA dòng này
"plugins": [
["expo-build-properties", {
"android": {
"newArchEnabled": true // XÓA dòng này
},
"ios": {
"newArchEnabled": true // XÓA dòng này
}
}]
]
}
}
// app.json - Sau khi nâng cấp (SDK 55)
{
"expo": {
"plugins": [
["expo-build-properties", {
"android": {},
"ios": {}
}]
]
}
}
Bước 4: Cập nhật các thư viện bị ảnh hưởng
Một số thay đổi quan trọng cần xử lý:
- expo-av: Đã bị loại khỏi Expo Go. Chuyển sang sử dụng
expo-videovàexpo-audiothay thế. - expo-video-thumbnails: Đã bị deprecated. Sử dụng
generateThumbnailsAsynctừexpo-video. - Blur API: Giờ yêu cầu wrapper
BlurTargetViewcho nội dung nền. - Push Notifications trên Expo Go (Android): Giờ ném lỗi thay vì cảnh báo.
# Cập nhật từ expo-av sang expo-video và expo-audio
npx expo install expo-video expo-audio
# Gỡ cài đặt expo-av nếu không còn cần
npm uninstall expo-av
Bước 5: Cập nhật UI Components (nếu sử dụng @expo/ui)
// Cập nhật import cho các component đổi tên
// Trước (SDK 54)
import { DateTimePicker, Switch, CircularProgress, LinearProgress } from '@expo/ui';
// Sau (SDK 55)
import { DatePicker, Toggle, ProgressView } from '@expo/ui';
Bước 6: Xóa và tái tạo thư mục native
Nếu bạn sử dụng Continuous Native Generation (CNG), hãy xóa các thư mục native cũ để chúng được tạo lại cho SDK 55:
# Xóa thư mục native cũ
rm -rf android ios
# Thư mục sẽ được tái tạo tự động khi build
# hoặc chạy prebuild thủ công
npx expo prebuild
Nếu bạn không sử dụng CNG mà quản lý thư mục native thủ công, hãy chạy npx pod-install cho iOS.
Bước 7: Tùy chọn - Di chuyển sang cấu trúc /src/app
Nếu muốn áp dụng cấu trúc thư mục mới, bạn có thể di chuyển thư mục app vào src:
# Tạo thư mục src nếu chưa có
mkdir -p src
# Di chuyển thư mục app vào src
mv app src/app
# Di chuyển các thư mục khác (tùy chọn)
mv components src/components
mv utils src/utils
Sau đó cập nhật đường dẫn trong tsconfig.json nếu cần. Expo Router sẽ tự động nhận diện thư mục src/app nếu nó tồn tại.
Bước 8: Kiểm tra và xây dựng lại
# Xóa cache và khởi động lại
npx expo start --clear
# Build bản development cho kiểm thử
eas build --profile development --platform all
# Chạy kiểm tra cuối cùng
npx expo-doctor
Kết luận và khuyến nghị
Expo SDK 55 cùng React Native 0.83 đánh dấu một bước tiến lớn cho hệ sinh thái phát triển ứng dụng di động đa nền tảng. Dưới đây là tóm tắt những điểm quan trọng nhất và khuyến nghị cho các nhà phát triển:
Những thay đổi bắt buộc cần thực hiện ngay
- Loại bỏ Legacy Architecture: Đảm bảo tất cả thư viện trong dự án của bạn đã tương thích với New Architecture. Không còn đường quay lại nữa.
- Cập nhật UI Components: Đổi tên các import từ
DateTimePicker,Switch,CircularProgress/LinearProgresssangDatePicker,Toggle,ProgressView. - Thay thế expo-av: Chuyển sang
expo-videovàexpo-audiovìexpo-avkhông còn được hỗ trợ trong Expo Go.
Những tính năng nên áp dụng sớm
- Hermes Bytecode Diffing: Bật ngay nếu bạn sử dụng OTA updates - giảm 75% kích thước tải xuống mà không cần thay đổi mã nguồn (khá ấn tượng đúng không).
- Web Performance APIs: Tích hợp
PerformanceObserverđể theo dõi hiệu suất thực tế từ người dùng cuối trong bản production. - React 19.2 Activity: Áp dụng cho các màn hình tab hoặc luồng điều hướng phức tạp để cải thiện tốc độ chuyển cảnh.
- useEffectEvent: Thay thế các workaround với ref cho việc truy cập giá trị mới nhất trong Effect.
Những tính năng cần theo dõi
- Hermes v1: Hiệu năng tốt nhưng yêu cầu build từ mã nguồn. Chờ đến khi trở thành mặc định nếu thời gian build là vấn đề (theo mình thì nên chờ).
- IntersectionObserver: Đang ở trạng thái canary, chưa nên dùng trong production nhưng rất đáng để thử nghiệm.
- Apple Zoom Transition và Stack.Toolbar: Cả hai đều ở trạng thái alpha và chỉ trên iOS, nhưng mang lại trải nghiệm người dùng tuyệt vời.
- expo-brownfield: Lý tưởng cho doanh nghiệp muốn tích hợp dần React Native vào ứng dụng native hiện có.
Lộ trình khuyến nghị
Đối với dự án mới bắt đầu năm 2026, hãy sử dụng template mặc định của SDK 55 với cấu trúc /src/app và Native Tabs ngay từ đầu. Đối với dự án hiện có, hãy lên kế hoạch nâng cấp trong vòng 2-4 tuần kể từ khi SDK 55 chính thức ra mắt (sau giai đoạn beta), đảm bảo tất cả thư viện phụ thuộc đã tương thích.
Hệ sinh thái React Native đang trưởng thành nhanh chóng. Với Kiến Trúc Mới trở thành tiêu chuẩn duy nhất, Hermes v1 mang lại hiệu năng đáng kể, và các công cụ DevTools ngày càng mạnh mẽ, năm 2026 thực sự là năm mà React Native chứng minh mình xứng đáng là lựa chọn hàng đầu cho phát triển ứng dụng di động đa nền tảng. Expo SDK 55 chính là minh chứng rõ ràng nhất cho điều đó.