React Native 0.86 Năm 2026: Edge-to-Edge Android, DevTools và Câu Hỏi Phỏng Vấn

Làm chủ React Native 0.86 với hỗ trợ edge-to-edge Android 15, cải tiến DevTools và các câu hỏi phỏng vấn thực tế. Hướng dẫn đầy đủ với ví dụ code.

Sơ đồ quy trình phát triển edge-to-edge Android với React Native 0.86

React Native 0.86 mang đến hỗ trợ edge-to-edge toàn diện cho Android 15+, đánh dấu phiên bản thứ hai liên tiếp không có breaking changes. Được phát hành vào tháng 6 năm 2026, phiên bản này giải quyết các vấn đề API layout và measurement đã bị ảnh hưởng âm thầm bởi chính sách edge-to-edge bắt buộc của Android.

Không Có Breaking Changes

React Native 0.86 duy trì khả năng tương thích hoàn toàn với phiên bản 0.85. Quá trình nâng cấp không yêu cầu sửa đổi code cho hầu hết các ứng dụng, khiến đây trở thành một trong những lần chuyển đổi phiên bản major mượt mà nhất trong lịch sử React Native.

Hiểu Về Edge-to-Edge Trên Android 15

Layout edge-to-edge cho phép nội dung mở rộng xuống dưới các thành phần UI hệ thống như thanh trạng thái và thanh điều hướng. Android 15 áp dụng hành vi này theo mặc định, bất kể cấu hình ứng dụng. React Native 0.86 đảm bảo tất cả các API measurement và layout hoạt động chính xác trong điều kiện này.

Hàm measureInWindow hiện trả về tọa độ chính xác khi chế độ edge-to-edge được kích hoạt. Trước đây, các phép đo có thể bị lệch bằng chiều cao của các thành phần UI hệ thống, gây ra lỗi định vị trên overlay, tooltip và floating action button.

SafeAreaHandler.tsxtypescript
import React, { useRef, useCallback } from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

interface TooltipPosition {
  x: number;
  y: number;
}

export function SafeAreaHandler() {
  const buttonRef = useRef<View>(null);
  const insets = useSafeAreaInsets();

  const showTooltip = useCallback(() => {
    // measureInWindow now returns correct coordinates on Android 15+
    buttonRef.current?.measureInWindow((x, y, width, height) => {
      const position: TooltipPosition = {
        x: x + width / 2,
        // Coordinates are accurate even with edge-to-edge enabled
        y: y + height + 8,
      };
      console.log('Tooltip position:', position);
    });
  }, []);

  return (
    <View style={[styles.container, { paddingTop: insets.top }]}>
      <View ref={buttonRef} collapsable={false}>
        <TouchableOpacity onPress={showTooltip} style={styles.button}>
          <Text style={styles.buttonText}>Show Tooltip</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
  button: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8 },
  buttonText: { color: '#FFFFFF', fontWeight: '600' },
});

Prop collapsable={false} đảm bảo View duy trì tham chiếu native để đo lường. Nếu không có điều này, React Native có thể tối ưu hóa View native đó, khiến measureInWindow thất bại.

Sửa Lỗi KeyboardAvoidingView Cho Android 15

Component KeyboardAvoidingView tính toán không gian khả dụng không chính xác khi chế độ edge-to-edge được kích hoạt. Phiên bản 0.86 sửa lỗi này bằng cách tính toán các insets UI hệ thống trong phép tính chiều cao khả dụng.

ChatInput.tsxtypescript
import React, { useState } from 'react';
import {
  KeyboardAvoidingView,
  TextInput,
  Platform,
  StyleSheet,
  View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export function ChatInput() {
  const [message, setMessage] = useState('');
  const insets = useSafeAreaInsets();

  return (
    <KeyboardAvoidingView
      // behavior="padding" works correctly on Android 15+ edge-to-edge
      behavior={Platform.OS === 'ios' ? 'padding' : 'padding'}
      style={styles.container}
      // keyboardVerticalOffset accounts for headers and safe areas
      keyboardVerticalOffset={Platform.select({ ios: 88, android: 0 })}
    >
      <View style={styles.content}>
        {/* Chat messages would render here */}
      </View>
      <View style={[styles.inputContainer, { paddingBottom: insets.bottom }]}>
        <TextInput
          value={message}
          onChangeText={setMessage}
          placeholder="Type a message..."
          style={styles.input}
          multiline
        />
      </View>
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  content: { flex: 1 },
  inputContainer: { borderTopWidth: 1, borderTopColor: '#E5E5E5', padding: 8 },
  input: { minHeight: 40, padding: 12, backgroundColor: '#F5F5F5', borderRadius: 20 },
});

Trên Android 15, sử dụng behavior="height" thường gây ra hiện tượng nhảy layout. Behavior padding cung cấp animation mượt mà hơn và định vị dễ dự đoán hơn khi edge-to-edge được kích hoạt.

Cập Nhật StatusBar Trong Khi Hiển Thị Modal

Một bug tồn tại lâu đã ngăn cản việc thay đổi style và visibility của StatusBar khi Modal đang mở. React Native 0.86 giải quyết vấn đề này, cho phép tùy chỉnh status bar động trong các luồng modal.

ThemedModal.tsxtypescript
import React, { useState, useEffect } from 'react';
import { Modal, View, StatusBar, Text, Pressable, StyleSheet } from 'react-native';

interface ThemedModalProps {
  visible: boolean;
  onClose: () => void;
  darkMode?: boolean;
}

export function ThemedModal({ visible, onClose, darkMode = false }: ThemedModalProps) {
  // StatusBar changes now apply correctly while modal is visible
  useEffect(() => {
    if (visible) {
      StatusBar.setBarStyle(darkMode ? 'light-content' : 'dark-content', true);
      StatusBar.setBackgroundColor(darkMode ? '#1A1A1A' : '#FFFFFF', true);
    }
    return () => {
      // Restore original status bar when modal closes
      StatusBar.setBarStyle('dark-content', true);
      StatusBar.setBackgroundColor('#FFFFFF', true);
    };
  }, [visible, darkMode]);

  return (
    <Modal visible={visible} animationType="slide" onRequestClose={onClose}>
      <View style={[styles.modalContent, darkMode && styles.darkBackground]}>
        <StatusBar
          barStyle={darkMode ? 'light-content' : 'dark-content'}
          backgroundColor={darkMode ? '#1A1A1A' : '#FFFFFF'}
        />
        <Text style={[styles.title, darkMode && styles.lightText]}>
          Modal Content
        </Text>
        <Pressable onPress={onClose} style={styles.closeButton}>
          <Text style={styles.closeButtonText}>Close</Text>
        </Pressable>
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  modalContent: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#FFFFFF' },
  darkBackground: { backgroundColor: '#1A1A1A' },
  title: { fontSize: 24, fontWeight: 'bold', color: '#000000' },
  lightText: { color: '#FFFFFF' },
  closeButton: { marginTop: 20, padding: 12, backgroundColor: '#007AFF', borderRadius: 8 },
  closeButtonText: { color: '#FFFFFF', fontWeight: '600' },
});

Bản sửa lỗi này đặc biệt quan trọng cho các ứng dụng triển khai toggle dark mode trong luồng modal, chẳng hạn như màn hình cài đặt hoặc trình xem hình ảnh.

Sẵn sàng chinh phục phỏng vấn React Native?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Mô Phỏng Light và Dark Mode Trong DevTools

React Native DevTools giới thiệu hỗ trợ Emulation.setEmulatedMedia, cho phép chuyển đổi theme ngay lập tức mà không cần thay đổi cấu hình thiết bị hoặc emulator. Truy cập tính năng này thông qua Command Palette bằng Cmd+Shift+P (macOS) hoặc Ctrl+Shift+P (Windows/Linux).

ThemeProvider.tsxtypescript
import React, { createContext, useContext, useState, useEffect } from 'react';
import { useColorScheme, Appearance } from 'react-native';

type Theme = 'light' | 'dark';

interface ThemeContextValue {
  theme: Theme;
  toggleTheme: () => void;
  colors: typeof lightColors;
}

const lightColors = {
  background: '#FFFFFF',
  surface: '#F5F5F5',
  text: '#000000',
  textSecondary: '#666666',
  primary: '#007AFF',
  border: '#E5E5E5',
};

const darkColors = {
  background: '#1A1A1A',
  surface: '#2D2D2D',
  text: '#FFFFFF',
  textSecondary: '#A0A0A0',
  primary: '#0A84FF',
  border: '#3D3D3D',
};

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  // DevTools emulation triggers colorScheme changes via setEmulatedMedia
  const systemColorScheme = useColorScheme();
  const [theme, setTheme] = useState<Theme>(systemColorScheme ?? 'light');

  useEffect(() => {
    // Listen for DevTools theme emulation changes
    const subscription = Appearance.addChangeListener(({ colorScheme }) => {
      if (colorScheme) {
        setTheme(colorScheme);
      }
    });
    return () => subscription?.remove();
  }, []);

  const toggleTheme = () => setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
  const colors = theme === 'dark' ? darkColors : lightColors;

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme, colors }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme(): ThemeContextValue {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

Trạng thái media được mô phỏng sẽ được reset khi DevTools ngắt kết nối, đảm bảo các bản build production không bị ảnh hưởng bởi việc test theme trong quá trình development.

Câu Hỏi Phỏng Vấn: React Native 0.86 và Edge-to-Edge

Các cuộc phỏng vấn kỹ thuật năm 2026 ngày càng tập trung vào khả năng tương thích Android 15 và triển khai edge-to-edge. Nắm vững các khái niệm này thể hiện sự hiểu biết về các thực hành phát triển mobile hiện đại. Để chuẩn bị phỏng vấn toàn diện, hãy khám phá module câu hỏi phỏng vấn debugging React Native.

Chế độ edge-to-edge là gì và tại sao Android 15 áp dụng nó?

Chế độ edge-to-edge mở rộng nội dung ứng dụng xuống dưới các thành phần UI hệ thống (thanh trạng thái, thanh điều hướng, chỉ báo gesture). Android 15 áp dụng hành vi này theo mặc định để cung cấp trải nghiệm nhập vai và UI nhất quán trên toàn nền tảng. Các ứng dụng phải xử lý safe area insets để ngăn nội dung bị che khuất bởi UI hệ thống.

Hành vi measureInWindow thay đổi như thế nào khi edge-to-edge được kích hoạt?

Trước React Native 0.86, measureInWindow trả về tọa độ tương đối so với cửa sổ hiển thị, không bao gồm các khu vực UI hệ thống. Với edge-to-edge, cửa sổ mở rộng đến các cạnh màn hình, nhưng các phép đo bị sai do offset. Phiên bản 0.86 đảm bảo tọa độ chính xác bất kể trạng thái edge-to-edge.

Behavior KeyboardAvoidingView được khuyến nghị trên Android 15 là gì?

Sử dụng behavior="padding" trên Android 15 với edge-to-edge được kích hoạt. Behavior height có thể gây ra hiện tượng nhảy layout vì phép tính chiều cao khả dụng thay đổi khi bàn phím xuất hiện. Behavior padding cung cấp chuyển đổi mượt mà hơn và định vị dễ dự đoán hơn.

Safe area insets khác nhau như thế nào giữa iOS và Android edge-to-edge?

Trên iOS, safe area insets đã tồn tại từ iPhone X cho notch và home indicator. Trên Android edge-to-edge, insets bao gồm chiều cao thanh trạng thái, chiều cao thanh điều hướng và các khu vực chỉ báo gesture. Thư viện react-native-safe-area-context cung cấp giá trị insets đa nền tảng thông qua useSafeAreaInsets().

Pressable android_ripple Với PlatformColor

React Native 0.86 thêm hỗ trợ PlatformColor cho prop android_ripple, cho phép màu ripple động tự động theo cài đặt theme hệ thống.

ThemedButton.tsxtypescript
import React from 'react';
import { Pressable, Text, PlatformColor, Platform, StyleSheet } from 'react-native';

interface ThemedButtonProps {
  title: string;
  onPress: () => void;
}

export function ThemedButton({ title, onPress }: ThemedButtonProps) {
  return (
    <Pressable
      onPress={onPress}
      style={({ pressed }) => [
        styles.button,
        pressed && styles.pressed,
      ]}
      android_ripple={{
        // PlatformColor adapts to system theme automatically
        color: Platform.OS === 'android'
          ? PlatformColor('?attr/colorControlHighlight')
          : undefined,
        borderless: false,
        foreground: true,
      }}
    >
      <Text style={styles.text}>{title}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    padding: 16,
    backgroundColor: Platform.select({
      android: PlatformColor('?attr/colorPrimary'),
      default: '#007AFF',
    }),
    borderRadius: 8,
    alignItems: 'center',
  },
  pressed: { opacity: 0.8 },
  text: { color: '#FFFFFF', fontWeight: '600', fontSize: 16 },
});

Sử dụng ?attr/colorControlHighlight đảm bảo màu ripple tuân theo nguyên tắc Material Design và thích ứng với theme sáng/tối được cấu hình ở cấp hệ thống.

Image.getSize Trả Về Kích Thước Chính Xác

Các phương thức Image.getSizeImage.getSizeWithHeaders hiện trả về kích thước hình ảnh gốc thay vì giá trị đã được downsample. Bản sửa lỗi này rất quan trọng để triển khai tính toán aspect ratio chính xác và layout hình ảnh responsive.

ResponsiveImage.tsxtypescript
import React, { useState, useEffect } from 'react';
import { Image, View, useWindowDimensions, StyleSheet } from 'react-native';

interface ResponsiveImageProps {
  uri: string;
  maxHeight?: number;
}

export function ResponsiveImage({ uri, maxHeight = 400 }: ResponsiveImageProps) {
  const { width: screenWidth } = useWindowDimensions();
  const [dimensions, setDimensions] = useState({ width: screenWidth, height: 200 });

  useEffect(() => {
    // getSize now returns true source dimensions, not downsampled values
    Image.getSize(
      uri,
      (width, height) => {
        const aspectRatio = width / height;
        let calculatedHeight = screenWidth / aspectRatio;
        
        // Constrain to maxHeight while maintaining aspect ratio
        if (calculatedHeight > maxHeight) {
          calculatedHeight = maxHeight;
        }
        
        setDimensions({
          width: screenWidth,
          height: calculatedHeight,
        });
      },
      (error) => {
        console.error('Failed to get image size:', error);
      }
    );
  }, [uri, screenWidth, maxHeight]);

  return (
    <View style={styles.container}>
      <Image
        source={{ uri }}
        style={{ width: dimensions.width, height: dimensions.height }}
        resizeMode="cover"
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { width: '100%' },
});

Bản sửa lỗi này loại bỏ nhu cầu về các workaround lấy kích thước hình ảnh thông qua module native hoặc xử lý hình ảnh phía server.

Xử Lý Response HTTP Lớn Không Gây OutOfMemoryError

NetworkingModule trước đây ném OutOfMemoryError khi xử lý các response HTTP lớn. Phiên bản 0.86 triển khai xử lý theo chunk cho các response vượt quá ngưỡng bộ nhớ.

LargeFileDownload.tsxtypescript
import { useState, useCallback } from 'react';

interface DownloadProgress {
  loaded: number;
  total: number;
  percentage: number;
}

export function useLargeFileDownload() {
  const [progress, setProgress] = useState<DownloadProgress | null>(null);
  const [error, setError] = useState<string | null>(null);

  const downloadFile = useCallback(async (url: string): Promise<Blob | null> => {
    try {
      setError(null);
      setProgress({ loaded: 0, total: 0, percentage: 0 });

      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const contentLength = response.headers.get('Content-Length');
      const total = contentLength ? parseInt(contentLength, 10) : 0;

      // NetworkingModule now handles large responses without OutOfMemoryError
      const reader = response.body?.getReader();
      if (!reader) {
        throw new Error('Response body is not readable');
      }

      const chunks: Uint8Array[] = [];
      let loaded = 0;

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        chunks.push(value);
        loaded += value.length;
        
        setProgress({
          loaded,
          total,
          percentage: total > 0 ? Math.round((loaded / total) * 100) : 0,
        });
      }

      const blob = new Blob(chunks);
      setProgress({ loaded, total, percentage: 100 });
      return blob;
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Download failed';
      setError(message);
      return null;
    }
  }, []);

  return { downloadFile, progress, error };
}

Đối với việc tải file vượt quá 50MB, hãy cân nhắc sử dụng react-native-blob-util hoặc các thư viện xử lý file native tương tự để quản lý bộ nhớ tốt hơn.

Header Cookie được truyền qua constructor WebSocket trước đây bị loại bỏ trước khi đến server. Điều này ngăn cản xác thực dựa trên session cho các kết nối WebSocket.

AuthenticatedWebSocket.tstypescript
type MessageHandler = (data: string) => void;
type ErrorHandler = (error: Event) => void;

interface WebSocketOptions {
  url: string;
  sessionCookie: string;
  onMessage: MessageHandler;
  onError?: ErrorHandler;
}

export function createAuthenticatedWebSocket({
  url,
  sessionCookie,
  onMessage,
  onError,
}: WebSocketOptions): WebSocket {
  // Cookie header now reaches the server correctly in 0.86
  const ws = new WebSocket(url, undefined, {
    headers: {
      Cookie: sessionCookie,
      // Additional headers as needed
      'X-Client-Version': '1.0.0',
    },
  });

  ws.onopen = () => {
    console.log('WebSocket connected with session cookie');
  };

  ws.onmessage = (event) => {
    if (typeof event.data === 'string') {
      onMessage(event.data);
    }
  };

  ws.onerror = (error) => {
    console.error('WebSocket error:', error);
    onError?.(error);
  };

  ws.onclose = (event) => {
    console.log(`WebSocket closed: ${event.code} ${event.reason}`);
  };

  return ws;
}

Bản sửa lỗi này rất quan trọng cho các ứng dụng sử dụng quản lý session dựa trên cookie cùng với các tính năng real-time. Module câu hỏi phỏng vấn networking React Native đề cập sâu về các pattern triển khai WebSocket.

Kết Luận

  • React Native 0.86 cung cấp các bản sửa lỗi edge-to-edge bắt buộc cho Android 15 mà không có breaking changes
  • measureInWindow, KeyboardAvoidingViewDimensions hoạt động chính xác với edge-to-edge được kích hoạt
  • Cập nhật StatusBar hiện áp dụng khi component Modal đang hiển thị
  • Mô phỏng theme DevTools tăng tốc phát triển và kiểm thử dark mode
  • Hỗ trợ PlatformColor trong android_ripple cho phép phản hồi chạm theo theme
  • Image.getSize trả về kích thước gốc chính xác cho layout responsive
  • Response HTTP lớn không còn gây ra OutOfMemoryError
  • Header Cookie WebSocket đến server như mong đợi

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thẻ

#react-native
#android
#mobile-development
#edge-to-edge
#devtools

Chia sẻ

Bài viết liên quan