ภาพเส้นทาง import หลายสายจากโฟลเดอร์ภายในถูกรวมผ่านประตู public API เดียว ก่อนเข้าสู่เครื่องตรวจ TypeScript สีเขียว

React Native 0.87 เปิด Strict TypeScript API เป็นค่าเริ่มต้น: อัปเกรดอย่างไรไม่ให้ type พังทั้งแอป

พี่โชว์Engineering

เวลา import ของจาก react-native/Libraries/... แล้วมันใช้งานได้ ความรู้สึกจะเหมือนเราเจอประตูลัดหลังร้าน เดินเร็วและหยิบของได้ทันที ปัญหาคือหลังร้านไม่ใช่พื้นที่ที่ร้านสัญญาว่าจะจัดวางเหมือนเดิมตลอด วันหนึ่งชั้นย้าย ประตูลัดก็พาเราไปชนกำแพง

React Native 0.87 ประกาศเมื่อ 11 สิงหาคม 2026 และทำให้ Strict TypeScript API เป็นค่าเริ่มต้น API สาธารณะฝั่ง JavaScript/TypeScript จึงถูกจำกัดให้อยู่ที่ root export ของ react-native ขณะที่ type ถูกสร้างจาก source โดยตรง แทน definition ที่ดูแลแยกจาก implementation

ผลลัพธ์ระยะสั้นคืออาจเจอ type error ตอนอัปเกรด แต่ผลระยะยาวคือเส้นแบ่ง “ของที่ทีม framework รับประกัน” ชัดขึ้น และ type มีโอกาสคลาดจาก runtime น้อยลงครับ

WHY: Type ที่เข้มขึ้นกำลังซ่อมสัญญา ไม่ได้จับผิดเรา

ก่อนหน้านี้ internal module หลายตัวมองเห็นได้จาก package path นักพัฒนาจึง import ได้แม้ไม่อยู่ใน public API เมื่อทีม React Native จัดโครงสร้างภายในใหม่ แอปภายนอกก็พังทั้งที่พฤติกรรมสาธารณะไม่ได้เปลี่ยน

Strict API เปลี่ยนคำถามจาก “ไฟล์นี้ยังอยู่ไหม” เป็น “framework export สิ่งนี้เป็นสัญญาสาธารณะหรือไม่” เหมือนการรับพัสดุผ่านเคาน์เตอร์ที่มีรายการชัดเจน แทนการเดินเข้าโกดังไปหยิบเอง

เอกสาร migration ยืนยันด้วยว่าการเปลี่ยนนี้กระทบการวิเคราะห์ของ TypeScript ตาม tsconfig.json ของแต่ละโปรเจกต์ ไม่ได้เปลี่ยน JavaScript bundle โดยตัวมันเอง แอปและ library จึงทยอยย้ายได้อิสระเป็นส่วนใหญ่

จุดที่มักแตกหลังอัปเกรด

กลุ่มแรกคือ deep import เช่น path ใต้ Libraries/ ที่กลายเป็น type error ถ้ามี root export ให้ย้ายมาใช้ root ทันที ถ้าไม่มี อย่ารีบทำ alias หลบทุกจุด เพราะนั่นอาจบอกว่าเราพึ่ง internal contract อยู่จริงและควรหาทางออกระดับ design

กลุ่มที่สองคือ ref ของ built-in components เดิมเราอาจเขียน useRef<View> ได้ แต่ Strict API แยก type ของ instance ชัดขึ้นเป็น ViewInstance, TextInputInstance และชื่อแบบเดียวกันสำหรับ component อื่น ๆ

ตัวอย่างนี้เป็น React Native snippet ที่ตรวจ syntax แล้ว และตรวจชื่อ import/API เทียบกับเอกสาร 0.87 โดยไม่ได้อ้างว่ารันบน simulator หรืออุปกรณ์จริง

import { useRef } from "react";
import {
  TextInput,
  type TextInputInstance,
  View,
  type ViewInstance,
} from "react-native";

export function SearchPanel() {
  const panelRef = useRef<ViewInstance>(null);
  const inputRef = useRef<TextInputInstance>(null);

  return (
    <View ref={panelRef}>
      <TextInput
        ref={inputRef}
        accessibilityLabel="ค้นหา"
        onSubmitEditing={() => inputRef.current?.blur()}
      />
    </View>
  );
}

กลุ่มที่สามคือ type ชื่อเก่า เช่น ViewProperties ต้องย้ายเป็น ViewProps และ type สำหรับ Codegen ย้ายมาอยู่ใต้ CodegenTypes namespace นอกจากนี้ entry point สำหรับ setup environment เปลี่ยนจาก deep path เดิมเป็น react-native/setup-env

Migration checklist ที่ช่วยให้ error ไม่ถล่มพร้อมกัน

เริ่มจากอัปเดต dependency ให้เป็นรุ่นที่รองรับก่อน เพราะบาง library แพ็ก raw TypeScript หรือ deep import ภายในของตัวเอง ถ้า error ชี้เข้า dependency ให้หา fixed release แทนการ patch declaration แบบถาวรในแอป

จากนั้นค้น react-native/Libraries/ และ react-native/src/private/ ทั้ง repository แล้วจัดกลุ่มเป็นสามแบบ: มี root export ทดแทน, มี API ใหม่เฉพาะทาง, หรือไม่มี public replacement การแยกกลุ่มทำให้ทีมเห็นขนาด migration จริงก่อนลงมือ

ต่อมาค่อยแก้ ref types, *Properties, *Static, Codegen imports และ test setup ทีละกลุ่ม รัน TypeScript check หลังแต่ละกลุ่มเพื่อให้ diff เล็กและรู้ว่า error ชุดใหม่มาจากอะไร อย่ารวมกับ redesign หน้าจอหรือเปลี่ยน state library ใน PR เดียวกัน

เอกสารแนะนำให้ใช้ skipLibCheck ตามค่าเริ่มต้นของ @react-native/typescript-config ระหว่าง migration เพื่อไม่ให้ declaration ภายใน third-party package กลบ error ของโค้ดเราเอง นี่ไม่ใช่การปิด type check ทั้งโปรเจกต์ แต่เป็นการกำหนดขอบเขตให้แก้สิ่งที่ทีมควบคุมได้ก่อน

ทางถอยมีไว้ซื้อเวลา ไม่ใช่ที่อยู่ถาวร

ถ้าย้ายไม่ทัน 0.87 ยังมี custom condition react-native-legacy-deep-imports เพื่อกลับไปใช้ type เดิมชั่วคราว แต่ทีม React Native ระบุว่าทางเลือกนี้อยู่ถึง 0.88 และตั้งใจนำออกในรุ่นถัดไป

ดังนั้นถ้าจำเป็นต้อง opt out ให้เปิด issue พร้อมเจ้าของและเส้นตาย ระบุ deep import ที่ยังติดและ library ที่รอแก้ การเขียน flag แล้วลืมเหมือนเอาเทปปิดไฟเตือนรถ เสียงเงียบลง แต่ระยะทางถึงปัญหาใหญ่สั้นลงทุกวัน

อย่าลืมของใหม่ด้าน toolchain

0.87 ไม่ได้มีแค่ Strict API รุ่นนี้ต้องการ Node.js อย่างน้อย 22.13.0, Kotlin 2.0 ขึ้นไป, Android compile SDK 37 และเพิ่มการรองรับ Android Gradle Plugin 9 ส่วน Swift Package Manager ยังเป็น experimental และ CocoaPods ยังเป็นทางหลักที่รองรับ

เวลาวางแผนอัปเกรดจึงควรแยก “type migration” ออกจาก “native toolchain migration” แม้อยู่ใน release เดียวกัน ทำ matrix ของ iOS, Android, CI และ library compatibility ให้ครบ จะได้ไม่สรุปผิดว่า type ผ่านแล้วแปลว่าแอป build และเปิดได้จริง

Recap

Strict TypeScript API อาจทำให้ upgrade PR มีเส้นแดงเพิ่ม แต่เส้นแดงเหล่านั้นกำลังเปิดเผยหนี้ที่เคยซ่อนอยู่หลัง deep import และ type ที่แยกดูแลจาก source

จำสามขั้น: จำกัด import ไว้ที่ public root, เปลี่ยน ref เป็น *Instance, และใช้ opt-out เฉพาะเป็นสะพานชั่วคราว เมื่อแยก TypeScript check ออกจาก native build และทดสอบทีละ platform เราจะได้ API contract ที่นิ่งขึ้นโดยไม่บังคับให้ทีมเสี่ยงแบบ big bang ครับ