JSI (JavaScript Interface) คือชั้นซอฟต์แวร์ใน React Native ที่ให้การเข้าถึงแบบซิงโครนัสโดยตรงจาก JavaScript ไปยังอ็อบเจกต์และฟังก์ชันของ C++ แทนที่ Bridge ซึ่งเป็นสะพาน JSON แบบอะซิงโครนัส ต่างจากรุ่นก่อนหน้า JSI อนุญาตให้เรียกเมธอดเนทีฟโดยไม่ต้องซีเรียไลซ์ข้อความและส่งผ่านการอ้างอิงไปยังอ็อบเจกต์ C++ ไปยังสภาพแวดล้อม JS โดยตรง จากข้อมูลของ React Native Team (2025), JSI ให้การเร่งความเร็วสูงสุด 10 เท่าสำหรับการโต้ตอบระหว่าง JS และโค้ดเนทีฟในสถานการณ์ที่มีการแลกเปลี่ยนข้อมูลอย่างหนาแน่น
ประเด็นสำคัญ
JSI (JavaScript Interface) คือชั้น C++ ที่ให้เอนจิน JavaScript (Hermes, JavaScriptCore, V8) เข้าถึงอ็อบเจกต์ ฟังก์ชัน และหน่วยความจำของ C++ ได้โดยตรง ต่างจาก Bridge ที่ซีเรียไลซ์การเรียกเป็น JSON และส่งผ่านคิวแบบอะซิงโครนัส JSI อนุญาตให้โค้ด JS เรียกเมธอด C++ แบบซิงโครนัสและรับผลลัพธ์ทันที
JSI ถูกนำเสนอใน React Native 0.64 ซึ่งเป็นส่วนหนึ่งของสถาปัตยกรรมใหม่ (New Architecture) เป้าหมายหลักคือการขจัดคอขวดที่เป็น Bridge: ทุกการโต้ตอบระหว่าง JS และโค้ดเนทีฟต้องใช้เวลาในการซีเรียไลซ์ ดีซีเรียไลซ์ และส่งผ่านคิวข้อความ JSI แก้ปัญหานี้โดยให้เอนจิน JS เข้าถึงอ็อบเจกต์ C++ ได้โดยตรงผ่านตัวห่อหุ้มที่ใช้อินเทอร์เฟซ jsi::Value, jsi::Object และ jsi::Function
JSI ไม่ใช่การแทนที่ Bridge แบบ "หนึ่งต่อหนึ่ง" — นี่คือแนวทางการบูรณาการที่แตกต่างโดยพื้นฐาน Bridge ทำงานเหมือนตู้ไปรษณีย์: JS ส่งข้อความ มันผ่านคิว ฝั่งเนทีฟประมวลผลและส่งคำตอบกลับ JSI ทำงานเหมือนพอยน์เตอร์: JS รับการอ้างอิงไปยังอ็อบเจกต์ C++ และสามารถเรียกเมธอดของมันแบบซิงโครนัสเหมือนฟังก์ชัน JS ทั่วไป นี่คือความแตกต่างอย่างสิ้นเชิงในสถาปัตยกรรมการโต้ตอบของสองสภาพแวดล้อม
ความจำเป็นของ JSI เกิดจากข้อจำกัดของ Bridge ดั้งเดิมที่วางไว้ใน React Native ปี 2015 เมื่อความนิยมของเฟรมเวิร์กเพิ่มขึ้นและแอปพลิเคชันซับซ้อนขึ้น ปัญหาด้านประสิทธิภาพก็ชัดเจน: ทุกการเรียกโมดูลเนทีฟต้องใช้เวลาอย่างน้อย 3–5 มิลลิวินาทีในการซีเรียไลซ์ สำหรับการดำเนินการง่ายๆ เช่นการอ่านค่าจากเซนเซอร์หรือการรับขนาดหน้าจอ นี่ถือว่ารับได้ แต่สำหรับแอนิเมชัน การทำงานกับกราฟิก และการประมวลผลข้อมูลแบบสตรีม — ถือว่าวิกฤต ทีม React Native เริ่มทำงานกับสถาปัตยกรรมใหม่ในปี 2019 และ JSI กลายเป็นรากฐานของมัน
JSI ถูกออกแบบให้เป็นชั้นนามธรรมเหนือเอนจิน JavaScript โดยมี API C++ เดียวที่ถูกนำไปใช้กับเอนจินเฉพาะแต่ละตัว: Hermes, JavaScriptCore (iOS), V8 (Android) ซึ่งหมายความว่านักพัฒนาไม่ต้องกังวลเกี่ยวกับความแตกต่างระหว่างเอนจิน — Fabric และ TurboModules ทำงานเหมือนกันไม่ว่าเอนจิน JS ตัวใดจะถูกใช้อยู่เบื้องหลัง
แกนกลางของ JSI คือแนวคิดของHost Objects — อ็อบเจกต์ C++ ที่ถูกส่งออกไปยังสภาพแวดล้อม JS ในรูปแบบอ็อบเจกต์ JS เนทีฟ เมื่อโค้ด JS เข้าถึงคุณสมบัติหรือเมธอดของอ็อบเจกต์ดังกล่าว JSI จะดักจับการเรียกและมอบหมายให้กับเมธอด C++ ที่เกี่ยวข้อง สิ่งนี้เกิดขึ้นแบบซิงโครนัส ในเธรดเดียวกัน โดยไม่ต้องสลับบริบทและไม่ต้องจัดสรรหน่วยความจำสำหรับสตริง JSON
Host Object แต่ละตัวใช้อินเทอร์เฟซ jsi::HostObject พร้อมเมธอด get, set และ getPropertyNames เอนจิน JS จะเรียกเมธอดเหล่านี้ทุกครั้งที่เข้าถึงคุณสมบัติของอ็อบเจกต์ ตัวอย่างเช่น เมื่อเรียก NativeModule.someMethod() ใน JS JSI จะแปลงการเรียกนี้เป็นการเรียก C++ ของเมธอด Host Object ที่เกี่ยวข้อง ค่าที่ส่งกลับจะถูกส่งกลับไปยัง JS ในรูปแบบ jsi::Value — ชนิดทั่วไปที่สามารถแทนตัวเลข สตริง ค่าบูลีน อ็อบเจกต์ หรือ undefined
ลักษณะสำคัญของ JSI คือการไม่มีคิวข้อความ Bridge ใช้คิวแบบอะซิงโครนัส: JS ส่งคำขอ สลับไปทำงานอื่น ฝั่งเนทีฟประมวลผลคำขอ และผลลัพธ์จะถูกส่งกลับผ่าน callback JSI ทำงานแบบซิงโครนัส: ถ้า JS เรียกเมธอดของโมดูลเนทีฟผ่าน JSI การทำงานของโค้ด JS จะหยุดชั่วคราวจนกว่าจะได้รับผลลัพธ์ ซึ่งทำให้ตรรกะง่ายขึ้น (ไม่ต้องรอ callbacks) และขจัดrace conditions แต่ต้องระมัดระวัง — การเรียกแบบซิงโครนัสที่ใช้เวลานานจะบล็อกเธรด JS
ค่าที่สร้างผ่าน JSI อาศัยอยู่ใน runtime ของเอนจิน JS และถูกจัดการโดยตัวเก็บขยะ (garbage collector) เมื่อโค้ด C++ สร้าง jsi::String หรือ jsi::Object และส่งกลับไปยัง JS สภาพแวดล้อมจะจัดการหน่วยความจำโดยอัตโนมัติ หากโค้ด C++ ต้องการเก็บการอ้างอิงไปยังค่า JS ระหว่างการเรียก จะใช้ jsi::Value::getWeak() หรือjsi::Object::setProperty แบบโกลบอลพร้อมเก็บการอ้างอิงไว้ที่อ็อบเจกต์รากของ runtime ซึ่งป้องกันไม่ให้ตัวเก็บขยะลบออกก่อนเวลาอันควร
JSI ไม่ปลอดภัยต่อเธรดโดยค่าเริ่มต้น การเรียกเมธอด JSI ทั้งหมดต้องเกิดขึ้นจากเธรดที่รัน JS (โดยปกติคือเธรด JS ของ React Native) หากโมดูลเนทีฟรันงานเบื้องหลังในเธรดแยก ผลลัพธ์จะต้องถูกส่งกลับผ่านเธรด JS โดยใช้การเรียก runOnJS จาก TurboModules ข้อจำกัดนี้คือราคาของการซิงโครนัสและการไม่ซีเรียไลซ์
ความแตกต่างระหว่าง JSI และ Bridge มีลักษณะเป็นพื้นฐานและส่งผลต่อทุกด้านของการโต้ตอบระหว่าง JS และโค้ดเนทีฟ Bridge เป็นแบบอะซิงโครนัส ซีเรียไลซ์ข้อมูลเป็น JSON และใช้คิวข้อความ ส่วน JSI เป็นแบบซิงโครนัส ทำงานกับการอ้างอิงเนทีฟ และไม่ต้องซีเรียไลซ์
| พารามิเตอร์ | Bridge | JSI |
|---|---|---|
| รูปแบบการเรียก | คิวแบบอะซิงโครนัส | การเรียกตรงแบบซิงโครนัส |
| การซีเรียไลซ์ | JSON (ซีเรียไลซ์ + ดีซีเรียไลซ์) | ไม่มี (การอ้างอิงตรงไปยังอ็อบเจกต์ C++) |
| ความหน่วง | 3–10 มิลลิวินาทีต่อการเรียก | 0.1–0.5 มิลลิวินาทีต่อการเรียก |
| การกำหนดชนิดข้อมูล | แบบไดนามิก (ผ่าน JSON) | แบบสแตติก (ผ่าน Codegen) |
| การบูรณาการ C++ | ผ่านโมดูลเนทีฟเท่านั้น (Java/ObjC) | โดยตรง ไม่ผ่านตัวกลาง |
| เธรด | เธรดเนทีฟแยก | เธรด JS (แบบซิงโครนัส) |
จากข้อมูลของReact Native Team การย้ายจาก Bridge ไป JSI ในแอปพลิเคชัน Facebook Marketplace ลดเวลาการเริ่มต้นลง 35% และลดการใช้หน่วยความจำลง 20% จากการขจัดการทำข้อมูลซ้ำซ้อนระหว่างฝั่ง JS และเนทีฟ
Bridge ไม่ใช่ "ความผิดพลาด" — มันคือการตัดสินใจทางสถาปัตยกรรมที่สมเหตุสมผลในขณะที่สร้าง React Native ในปี 2015 การพัฒนาเนทีฟสำหรับสองแพลตฟอร์มด้วยภาษาที่แตกต่างกันจำเป็นต้องมีรูปแบบการแลกเปลี่ยนที่เป็นสากล JSON ในฐานะรูปแบบซีเรียไลซ์มีอยู่บนทุกแพลตฟอร์มและช่วยให้การโต้ตอบเป็นหนึ่งเดียว ปัญหาเกิดขึ้นในภายหลัง เมื่อ React Native เริ่มถูกใช้สำหรับแอปพลิเคชันที่ซับซ้อนที่มีการเรียกโมดูลเนทีฟหลายพันครั้งต่อวินาที
React Native รักษาความเข้ากันได้ย้อนหลัง: โมดูลเนทีฟที่เขียนสำหรับ Bridge ยังคงทำงานในสถาปัตยกรรมใหม่ผ่านชั้นความเข้ากันได้ อย่างไรก็ตาม สำหรับโมดูลใหม่ แนะนำให้ใช้ JSI ผ่านTurboModules ทันที การย้ายโมดูลที่มีอยู่คือการเปลี่ยนโปรโตคอลการโต้ตอบโดยไม่เปลี่ยนตรรกะทางธุรกิจของโมดูลเอง
JSI เป็นชั้นพื้นฐานที่ใช้สร้างทุกคอมโพเนนต์ของสถาปัตยกรรมใหม่ของ React Native หากไม่มี JSI ทั้ง Fabric (เรนเดอเรอร์ใหม่) และ TurboModules (โมดูลเนทีฟที่ปรับให้เหมาะสม) ก็ไม่สามารถทำงานได้ JSI ให้วิธีแบบเดียวสำหรับการโต้ตอบระหว่าง JS และ C++ ในทุกระดับ
Fabric — เรนเดอเรอร์ใหม่ของ React Native ที่ใช้ JSI สำหรับการเข้าถึงมุมมอง UI ของ C++ แบบซิงโครนัส ในสถาปัตยกรรมเก่าการเรนเดอร์ผ่าน Bridge: JS สร้าง React elements ซีเรียไลซ์เป็น JSON ส่งผ่าน Bridge ฝั่งเนทีฟดีซีเรียไลซ์และสร้าง UI Fabric ผ่าน JSI สร้างอ็อบเจกต์ C++ Shadow Tree โดยตรงจาก JS คำนวณLayout แบบซิงโครนัสผ่าน Yoga และส่งเฟรมที่พร้อมให้เรนเดอเรอร์เนทีฟ — โดยไม่มีการซีเรียไลซ์แม้แต่ครั้งเดียว
TurboModules — วิวัฒนาการของโมดูลเนทีฟ React Native แทนที่จะลงทะเบียนโมดูลใน Bridge และเรียกเมธอดผ่าน JSON TurboModules ใช้ JSI สำหรับการโหลดแบบล่าช้าและการเรียกโดยตรง เมื่อโค้ด JS เข้าถึงโมดูลเป็นครั้งแรก JSI จะสร้าง Host Object — ซึ่งโหลดโมดูลเนทีฟและให้เมธอดเป็นฟังก์ชัน C++ การโหลดแบบล่าช้า หมายความว่าโมดูลไม่ใช้หน่วยความจำจนกว่าจะมีการเข้าถึงครั้งแรก — นี่สำคัญเป็นพิเศษสำหรับแอปพลิเคชันที่มีโมดูลเนทีฟหลายสิบตัว ซึ่งหลายตัวใช้เฉพาะในบางหน้าจอ
สำหรับการทำงานกับ JSI ในสถาปัตยกรรมใหม่ จะใช้ Codegen — เครื่องมือที่สร้างตัวห่อหุ้ม C++ จากสเปค JavaScript นักพัฒนาอธิบายอินเทอร์เฟซของโมดูลเนทีฟใน TypeScript หรือ Flow และ Codegen สร้างโค้ด C++ ที่ใช้Host Object ที่เข้ากันได้กับ JSI สิ่งนี้ทำให้งานที่ซ้ำซากเป็นอัตโนมัติและรับประกันว่าชนิดข้อมูลฝั่ง JS และ C++ ถูกซิงโครไนซ์
มาดูกันว่าการทำงานกับ JSI ในทางปฏิบัติเป็นอย่างไร ในตัวอย่างนี้จะสร้างคลาส C++ อย่างง่ายที่ถูกส่งออกไปยัง JS ผ่าน JSI และเรียกเมธอดของมันจากโค้ด JavaScript ของ React Native
// Calculator.h — ส่วนหัวคลาส C++ ที่เข้าถึงได้จาก JS
class Calculator {
public:
double add(double a, double b) { return a + b; }
double multiply(double a, double b) { return a * b; }
};
คลาส Calculator มีเมธอดทางคณิตศาสตร์สองเมธอด เราต้องทำให้มันเข้าถึงได้จาก JS สำหรับสิ่งนี้ จะสร้าง Host Object ที่ห่อหุ้มCalculator และให้เมธอดของมันผ่าน JSI
// CalculatorHostObject.cpp — การใช้งานตัวห่อหุ้ม JSI
class CalculatorHostObject : public jsi::HostObject {
private:
Calculator calc;
public:
jsi::Value get(jsi::Runtime& runtime,
const jsi::PropNameID& name) override {
auto propName = name.utf8(runtime);
if (propName == "add") {
return jsi::Function::createFromHostFunction(
runtime, name, 2,
[this](jsi::Runtime& runtime,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) -> jsi::Value {
return jsi::Value(calc.add(
args[0].asNumber(),
args[1].asNumber()));
});
}
return jsi::Value::undefined();
}
};
ในโค้ดนี้ เมธอด get จะถูกเรียกทุกครั้งที่ JS เข้าถึงคุณสมบัติของอ็อบเจกต์ หากชื่อคุณสมบัติคือ "add" จะส่งคืนฟังก์ชัน C++ ที่รับอาร์กิวเมนต์สองตัวจาก JS และเรียก calc.add() ค่าจะถูกส่งกลับเป็น jsi::Value — JSI จะแปลง double เป็นตัวเลข JS โดยอัตโนมัติ
หลังจากลงทะเบียน Host Object ในสภาพแวดล้อม JS การเรียกจะดูเหมือนฟังก์ชัน JS ทั่วไป ชนิดข้อมูลทั้งหมดจะถูกตรวจสอบในขั้นตอนการสร้างโค้ด ซึ่งช่วยขจัดข้อผิดพลาดความไม่ตรงกันของชนิดข้อมูลระหว่างการทำงาน
// JavaScript — การเรียกเครื่องคิดเลข C++ ผ่าน JSI
import { Calculator } from 'react-native-calculator'
const result = Calculator.add(5, 3)
console.log(result) // 8 — ซิงโครนัส ไม่มีความหน่วง
const product = Calculator.multiply(4, 2.5)
console.log(product) // 10 — ผลลัพธ์ทันที
โปรดสังเกต: ผลลัพธ์จะถูกส่งกลับทันที โดยไม่มี Promise ไม่มี await ไม่มี callbacks นี่คือการเรียกแบบซิงโครนัส ซึ่งเป็นไปไม่ได้ในสถาปัตยกรรม Bridge สำหรับการทำงานที่ใช้เวลานาน (การอ่านไฟล์ การร้องขอเครือข่าย) ควรใช้รูปแบบอะซิงโครนัสของ Java — JSI ไม่ได้ยกเลิกความจำเป็นของเธรดพื้นหลังสำหรับงานหนัก
ในทางปฏิบัติ นักพัฒนาส่วนใหญ่ไม่ได้เขียน JSI Host Objects ด้วยตนเอง — งานนี้ทำโดย Codegen ซึ่งสร้างตัวห่อหุ้ม C++ ตามสเปค TypeScript อย่างไรก็ตาม การเข้าใจว่า JSI ทำงานภายใต้ฝาครอบอย่างไรนั้นจำเป็นสำหรับการดีบักประสิทธิภาพอย่างมีประสิทธิภาพและการสร้างโมดูลเนทีฟที่ซับซ้อนซึ่งต้องการการเข้าถึงไลบรารี C++ โดยตรง (Skia, FFmpeg, OpenCV)
คำถามที่พบบ่อย
Bridge ทำงานแบบอะซิงโครนัสผ่านการซีเรียไลซ์ JSON และคิวข้อความ — ทุกการเรียกต้องใช้ 3–10 มิลลิวินาทีในการแปลงข้อมูล JSI ให้การเข้าถึงตรงแบบซิงโครนัสไปยังอ็อบเจกต์ C++ โดยไม่ซีเรียไลซ์ ลดความหน่วงลงเหลือ 0.1–0.5 มิลลิวินาที JSI ยังรองรับการส่งการอ้างอิงไปยังอ็อบเจกต์แทนที่จะส่งสำเนา
ใช่ JSI ให้ API C++ เดียวที่ถูกนำไปใช้กับ Hermes (ค่าเริ่มต้นของ React Native), JavaScriptCore (iOS) และ V8 (Android) นักพัฒนาไม่ต้องเขียนโค้ดที่แตกต่างสำหรับเอนจินต่างๆ — Fabric และ TurboModules ทำงานเหมือนกันบนทุกเอนจินที่รองรับ
ได้ React Native มีชั้นความเข้ากันได้ย้อนหลัง โมดูลเนทีฟที่เขียนสำหรับ Bridge ยังคงทำงานในสถาปัตยกรรมใหม่ อย่างไรก็ตาม แนะนำให้ย้ายไปใช้TurboModules เพื่อรับข้อดีของ JSI — การโหลดแบบล่าช้าและการเรียกแบบซิงโครนัส
สำหรับการพัฒนาในชีวิตประจำวัน — ไม่ สเปค TypeScript ของโมดูลเนทีฟจะถูกคอมไพล์เป็นตัวห่อหุ้ม C++ โดยอัตโนมัติผ่าน Codegen ความรู้ C++ จำเป็นเฉพาะเมื่อสร้างไลบรารี C++ ของตัวเองหรือเมื่อดีบักประสิทธิภาพของJSI ในระดับ runtime
JSI แก้ปัญหาหลักสามประการของ Bridge: ความหน่วงสูงจากการซีเรียไลซ์ JSON การไม่มีเรียกแบบซิงโครนัส และการไม่สามารถส่งอ็อบเจกต์ที่ซับซ้อนโดยการอ้างอิง นอกจากนี้ JSI ยังอนุญาตให้บูรณาการไลบรารี C++ โดยตรง โดยไม่ต้องผ่านJava หรือ Objective-C
บทสรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ