JSI(JavaScript Interface)는 React Native의 소프트웨어 계층으로, JavaScript에서 C++ 객체 및 함수로의 직접적인 동기 액세스를 제공하여 비동기 JSON 브리지 Bridge를 대체합니다. 전임자와 달리 JSI는 메시지 직렬화 없이 네이티브 메서드를 호출하고 C++ 객체에 대한 참조를 JS 환경으로 직접 전달할 수 있습니다. React Native 팀(2025)에 따르면, JSI는 데이터 집약적 시나리오에서 JS-네이티브 코드 상호 작용을 최대 10배까지 가속화합니다.
핵심 포인트
JSI(JavaScript Interface)는 JavaScript 엔진(Hermes, JavaScriptCore, V8)이 C++ 객체, 함수 및 메모리에 직접 액세스할 수 있는 기능을 제공하는 C++ 계층입니다. 호출을 JSON으로 직렬화하고 비동기 큐를 통해 전달했던 Bridge와 달리, JSI는 JS 코드가 동기적으로 C++ 메서드를 호출하고 즉시 결과를 얻을 수 있게 합니다.
JSI는 React Native 0.64에서 새 아키텍처(New Architecture)의 일부로 도입되었습니다. 주요 목표는 Bridge가 나타내는 병목 현상을 제거하는 것이었습니다. JS와 네이티브 코드 간의 각 상호 작용은 직렬화, 역직렬화 및 메시지 큐 통과에 시간을 소비했습니다. JSI는 jsi::Value, jsi::Object 및 jsi::Function 인터페이스를 구현하는 래퍼를 통해 JS 엔진에 C++ 객체에 대한 직접 액세스를 제공하여 이 문제를 해결합니다.
JSI는 Bridge의 일대일 대체가 아닙니다 — 통합에 대한 근본적으로 다른 접근 방식입니다. Bridge는 사서함처럼 작동했습니다. JS가 메시지를 보내고, 큐를 통과하고, 네이티브 측이 처리하고 응답을 보냈습니다. JSI는 포인터처럼 작동합니다. JS는 C++ 객체에 대한 참조를 얻고 일반 JS 함수처럼 동기적으로 해당 메서드를 호출할 수 있습니다. 이것은 두 환경 간의 상호 작용 아키텍처에서 근본적인 차이입니다.
JSI의 필요성은 React Native 2015에 설정된 원래 Bridge의 한계에서 비롯되었습니다. 프레임워크의 인기가 높아지고 애플리케이션이 더 복잡해짐에 따라 성능 문제가 분명해졌습니다. 각 네이티브 모듈 호출은 직렬화에 최소 3–5ms가 필요했습니다. 센서 값 읽기나 화면 크기 가져오기 같은 간단한 작업에서는 허용 가능했지만, 애니메이션, 그래픽 작업 및 스트리밍 데이터 처리에서는 중요했습니다. React Native 팀은 2019년에 새 아키텍처 작업을 시작했고 JSI가 그 기초가 되었습니다.
JSI는 JavaScript 엔진 위의 추상화 계층으로 설계되었습니다. Hermes, JavaScriptCore(iOS), V8(Android) 등 각 특정 엔진에 대해 구현된 통합 C++ API를 제공합니다. 즉, 개발자는 엔진 간의 차이점에 대해 걱정할 필요가 없습니다 — Fabric과 TurboModules는 내부적으로 사용되는 JS 엔진에 관계없이 동일하게 작동합니다.
JSI의 핵심에는 호스트 객체(Host Objects) 개념이 있습니다 — C++ 객체가 네이티브 JS 객체로 JS 환경에 내보내집니다. JS 코드가 이러한 객체의 속성이나 메서드에 액세스하면 JSI가 호출을 가로채서 해당 C++ 메서드에 위임합니다. 이는 동기적으로, 동일한 스레드에서, 컨텍스트 전환 없이, JSON 문자열을 위한 메모리 할당 없이 발생합니다.
각 호스트 객체는 get, set 및 getPropertyNames 메서드와 함께 jsi::HostObject 인터페이스를 구현합니다. JS 엔진은 객체의 속성에 액세스할 때마다 이러한 메서드를 호출합니다. 예를 들어, JS에서 NativeModule.someMethod()를 호출하면 JSI는 이 호출을 해당 호스트 객체 메서드의 C++ 호출로 변환합니다. 반환 값은 jsi::Value로 JS에 다시 전달됩니다 — 숫자, 문자열, 부울, 객체 또는 undefined를 나타낼 수 있는 제네릭 형식입니다.
JSI의 중요한 특징은 메시지 큐가 없다는 것입니다. Bridge는 비동기 큐를 사용했습니다. JS가 요청을 보내고, 다른 작업으로 전환하고, 네이티브 측이 요청을 처리하고, 결과가 콜백을 통해 반환되었습니다. JSI는 동기적으로 작동합니다. JS가 JSI를 통해 네이티브 모듈 메서드를 호출하면 결과를 받을 때까지 JS 코드 실행이 일시 중지됩니다. 이는 로직을 단순화하고(콜백을 기다릴 필요 없음) 경합 조건을 제거하지만 주의가 필요합니다 — 긴 동기 호출은 JS 스레드를 차단합니다.
JSI를 통해 생성된 값은 JS 엔진 런타임에 존재하며 가비지 컬렉터에 의해 관리됩니다. C++ 코드가 jsi::String 또는 jsi::Object를 생성하여 JS에 반환하면 환경이 자동으로 메모리를 관리합니다. C++ 코드가 호출 간에 JS 값에 대한 참조를 유지하려면 jsi::Value::getWeak() 또는 런타임 루트 객체에 저장된 참조와 함께 전역 jsi::Object::setProperty가 사용됩니다. 이는 가비지 컬렉터에 의한 조기 제거를 방지합니다.
JSI는 기본적으로 스레드 안전하지 않습니다. 모든 JSI 메서드 호출은 JS가 실행되는 스레드(일반적으로 React Native의 JS 스레드)에서 발생해야 합니다. 네이티브 모듈이 별도 스레드에서 백그라운드 작업을 시작하는 경우 TurboModules의 runOnJS를 사용하여 JS 스레드를 통해 결과를 반환해야 합니다. 이 제한은 동기성과 직렬화 부재의 대가입니다.
JSI와 Bridge의 차이는 근본적이며 JS-네이티브 코드 상호 작용의 모든 측면에 영향을 미칩니다. Bridge는 비동기식이었고, 데이터를 JSON으로 직렬화하고, 메시지 큐를 사용했습니다. JSI는 동기식이며, 네이티브 참조로 작업하고 직렬화가 필요하지 않습니다.
| 매개변수 | Bridge | JSI |
|---|---|---|
| 호출 모델 | 비동기 큐 | 동기 직접 호출 |
| 직렬화 | JSON (직렬화 + 역직렬화) | 없음 (C++ 객체에 대한 직접 참조) |
| 지연 시간 | 호출당 3–10ms | 호출당 0.1–0.5ms |
| 타이핑 | 동적 (JSON 통해) | 정적 (Codegen 통해) |
| C++ 통합 | 네이티브 모듈로만 가능 (Java/ObjC) | 직접, 중개자 없음 |
| 스레드 | 별도 네이티브 스레드 | JS 스레드 (동기) |
React Native 팀에 따르면, Facebook Marketplace 앱에서 Bridge에서 JSI로 마이그레이션하면 JS와 네이티브 측 간의 데이터 중복을 제거하여 시작 시간이 35% 단축되고 메모리 소비가 20% 감소했습니다.
Bridge는 “실수”가 아니었습니다 — 2015년 React Native 생성 당시 정당화된 아키텍처 결정이었습니다. 서로 다른 언어를 가진 두 플랫폼을 위한 네이티브 개발에는 보편적인 교환 형식이 필요했습니다. 직렬화 형식으로서의 JSON은 모든 플랫폼에서 사용 가능했으며 상호 작용을 통합할 수 있었습니다. 문제는 React Native가 초당 수천 개의 네이티브 모듈 호출이 있는 복잡한 애플리케이션에 사용되기 시작하면서 분명해졌습니다.
React Native는 하위 호환성을 유지합니다. Bridge용으로 작성된 네이티브 모듈은 호환성 계층을 통해 새 아키텍처에서 계속 작동합니다. 그러나 새 모듈의 경우 TurboModules를 통해 JSI를 직접 사용하는 것이 좋습니다. 기존 모듈 마이그레이션은 모듈 자체의 비즈니스 로직을 변경하지 않고 상호 작용 프로토콜을 교체하는 것을 의미합니다.
JSI는 새로운 React Native 아키텍처의 모든 구성 요소가 구축되는 기본 계층입니다. JSI가 없으면 Fabric(새 렌더러)이나 TurboModules(최적화된 네이티브 모듈) 모두 가능하지 않을 것입니다. JSI는 모든 수준에서 JS가 C++와 상호 작용하는 통합된 방법을 제공합니다.
Fabric은 C++ UI 표현에 대한 동기 액세스를 위해 JSI를 사용하는 새로운 React Native 렌더러입니다. 이전 아키텍처에서는 렌더링이 Bridge를 통해 이루어졌습니다. JS가 React 요소를 만들고, JSON으로 직렬화하고, Bridge를 통해 보내고, 네이티브 측이 역직렬화하여 UI를 만들었습니다. Fabric은 JSI를 통해 JS에서 직접 C++ 섀도 트리 객체를 만들고, Yoga를 통해 동기적으로 레이아웃을 계산하고 준비된 프레임을 네이티브 렌더러에 전달합니다 — 단 한 번의 직렬화 없이.
TurboModules는 React Native 네이티브 모듈의 진화입니다. Bridge에 모듈을 등록하고 JSON을 통해 메서드를 호출하는 대신 TurboModules는 지연 로딩 및 직접 호출을 위해 JSI를 사용합니다. JS 코드가 처음으로 모듈에 액세스하면 JSI가 호스트 객체를 만듭니다 — 네이티브 모듈을 로드하고 해당 메서드를 C++ 함수로 노출합니다. 지연 로딩은 모듈이 첫 번째 액세스까지 메모리를 소비하지 않음을 의미합니다 — 이는 수십 개의 네이티브 모듈이 있고 그 중 많은 수가 특정 화면에서만 사용되는 애플리케이션에 특히 중요합니다.
새 아키텍처에서 JSI로 작업하기 위해 Codegen이 사용됩니다 — JavaScript 사양에서 C++ 바인딩을 생성하는 도구입니다. 개발자는 TypeScript 또는 Flow로 네이티브 모듈 인터페이스를 설명하고 Codegen은 JSI 호환 호스트 객체를 구현하는 C++ 코드를 생성합니다. 이는 일상적인 작업을 자동화하고 JS 측과 C++ 측의 유형이 동기화되도록 보장합니다.
실제로 JSI로 작업하는 모습을 살펴보겠습니다. 이 예제에서는 JSI를 통해 JS로 내보내지는 간단한 C++ 클래스를 만들고 React Native의 JavaScript 코드에서 해당 메서드를 호출합니다.
// Calculator.h — JS에서 액세스 가능한 C++ 클래스 헤더
class Calculator {
public:
double add(double a, double b) { return a + b; }
double multiply(double a, double b) { return a * b; }
};
Calculator 클래스에는 두 개의 산술 메서드가 있습니다. JS에서 액세스할 수 있도록 만들어야 합니다. 이를 위해 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”이면 JS에서 두 인수를 받아 calc.add()를 호출하는 C++ 함수가 반환됩니다. 값은 jsi::Value로 반환됩니다 — JSI가 자동으로 double을 JS 숫자로 변환합니다.
JS 환경에 호스트 객체를 등록한 후 호출은 일반 JS 함수처럼 보입니다. 모든 유형은 코드 생성 단계에서 확인되어 런타임 중 유형 불일치 오류를 제거합니다.
// JavaScript — JSI를 통해 C++ 계산기 호출
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, 콜백 없이 즉시 반환됩니다. 이것은 Bridge 아키텍처에서는 불가능했던 동기 호출입니다. 오래 실행되는 작업(파일 읽기, 네트워크 요청)의 경우 비동기 패턴을 사용해야 합니다 — JSI는 무거운 작업에 대한 백그라운드 스레드의 필요성을 없애지 않습니다.
실제로 대부분의 개발자는 JSI 호스트 객체를 수동으로 작성하지 않습니다 — 이 작업은 TypeScript 사양을 기반으로 C++ 래퍼를 생성하는 Codegen이 수행합니다. 그러나 JSI가 내부적으로 어떻게 작동하는지 이해하는 것은 효과적인 성능 디버깅과 C++ 라이브러리(Skia, FFmpeg, OpenCV)에 대한 직접 액세스가 필요한 복잡한 네이티브 모듈을 만들 때 필요합니다.
자주 묻는 질문
Bridge는 JSON 직렬화 및 메시지 큐를 통해 비동기적으로 작동합니다 — 각 호출은 데이터 변환에 3–10ms가 걸립니다. JSI는 직렬화 없이 C++ 객체에 대한 동기 직접 액세스를 제공하여 지연 시간을 0.1–0.5ms로 줄입니다. JSI는 복사본 대신 객체 참조 전달도 지원합니다.
네, JSI는 Hermes(React Native 기본), JavaScriptCore(iOS) 및 V8(Android)에 대해 구현된 통합 C++ API를 제공합니다. 개발자는 다른 엔진에 대해 다른 코드를 작성할 필요가 없습니다 — Fabric과 TurboModules는 지원되는 모든 엔진에서 동일하게 작동합니다.
네, React Native는 하위 호환성 계층을 제공합니다. Bridge용으로 작성된 네이티브 모듈은 새 아키텍처에서 계속 작동합니다. 그러나 JSI의 이점 — 지연 로딩 및 동기 호출 — 을 얻기 위해 TurboModules로 마이그레이션하는 것이 좋습니다.
일상적인 개발에는 — 아니요. 네이티브 모듈의 TypeScript 사양은 Codegen을 통해 자동으로 C++ 바인딩으로 컴파일됩니다. C++ 지식은 사용자 지정 C++ 라이브러리를 만들거나 런타임 수준에서 JSI 성능을 디버깅할 때만 필요합니다.
JSI는 Bridge의 세 가지 주요 문제를 해결합니다. JSON 직렬화로 인한 높은 지연 시간, 동기 호출 부족, 복잡한 객체를 참조로 전달할 수 없는 점입니다. JSI는 Java 또는 Objective-C 중개자 없이 C++ 라이브러리를 직접 통합할 수도 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.