JSI (JavaScript Interface) — to warstwa programowa w React Native, która zapewnia bezpośredni synchroniczny dostęp z JavaScript do obiektów i funkcji C++, zastępując asynchroniczny most JSON — Bridge. W przeciwieństwie do poprzednika, JSI pozwala wywoływać metody natywne bez serializacji wiadomości i przekazywać referencje do obiektów C++ bezpośrednio do środowiska JS. Według danych React Native Team (2025), JSI zapewnia do 10-krotnego przyspieszenia interakcji JS z kodem natywnym w scenariuszach z intensywną wymianą danych.
Najważniejsze
JSI (JavaScript Interface) — to warstwa C++, która umożliwia silnikowi JavaScript (Hermes, JavaScriptCore, V8) bezpośredni dostęp do obiektów, funkcji i pamięci C++. W przeciwieństwie do Bridge, który serializował wywołania do JSON i przesyłał je przez asynchroniczną kolejkę, JSI pozwala kodowi JS synchronicznie wywoływać metody C++ i natychmiast otrzymywać wynik.
JSI została wprowadzona w React Native 0.64 jako część nowej architektury (New Architecture). Głównym celem było wyeliminowanie wąskiego gardła, jakim był Bridge: każda interakcja między JS a kodem natywnym wymagała czasu na serializację, deserializację i transmisję przez kolejkę komunikatów. JSI rozwiązuje ten problem, zapewniając silnikowi JS bezpośredni dostęp do obiektów C++ poprzez nakładki implementujące interfejs jsi::Value, jsi::Object i jsi::Function.
JSI nie jest zamiennikiem Bridge „jeden do jednego” — to zasadniczo inne podejście do integracji. Bridge działał jak skrzynka pocztowa: JS wysyłał komunikat, przechodził przez kolejkę, strona natywna go przetwarzała i wysyłała odpowiedź. JSI działa jak wskaźnik: JS otrzymuje referencję do obiektu C++ i może wywoływać jego metody synchronicznie, jak zwykłe funkcje JS. To zasadnicza różnica w architekturze interakcji dwóch środowisk.
Potrzeba JSI wynikała z ograniczeń oryginalnego Bridge, stworzonego w React Native 2015 roku. Wraz ze wzrostem popularności frameworka i komplikowaniem się aplikacji, problem wydajności stał się oczywisty: każde wywołanie modułu natywnego wymagało co najmniej 3–5 ms na serializację. Dla prostych operacji, takich jak odczyt wartości czujnika czy pobranie rozmiaru ekranu, było to akceptowalne, ale dla animacji, pracy z grafiką i strumieniowego przetwarzania danych — krytyczne. Zespół React Native rozpoczął pracę nad nową architekturą w 2019 roku, a JSI stała się jej fundamentem.
JSI została zaprojektowana jako abstrakcja nad silnikami JavaScript. Zapewnia jednolity interfejs API C++, który jest implementowany dla każdego konkretnego silnika: Hermes, JavaScriptCore (iOS), V8 (Android). Oznacza to, że programista nie musi martwić się różnicami między silnikami — Fabric i TurboModules działają tak samo niezależnie od tego, który silnik JS jest używany.
U podstaw JSI leży koncepcja Host Objects — obiektów C++, które są eksportowane do środowiska JS jako natywne obiekty JS. Gdy kod JS odwołuje się do właściwości lub metody takiego obiektu, JSI przechwytuje wywołanie i deleguje je do odpowiedniej metody C++. Dzieje się to synchronicznie, w tym samym wątku, bez przełączania kontekstu i bez alokacji pamięci na ciąg JSON.
Każdy Host Object implementuje interfejs jsi::HostObject z metodami get, set i getPropertyNames. Silnik JS wywołuje te metody przy każdym odwołaniu do właściwości obiektu. Na przykład przy wywołaniu NativeModule.someMethod() w JS, JSI przekształca to wywołanie na wywołanie C++ odpowiedniej metody Host Object. Zwracana wartość jest przekazywana z powrotem do JS jako jsi::Value — uogólniony typ, który może reprezentować liczbę, ciąg znaków, wartość logiczną, obiekt lub undefined.
Ważną cechą JSI jest brak kolejki komunikatów. Bridge używał asynchronicznej kolejki: JS wysyłał żądanie, przełączał się na inne zadania, strona natywna przetwarzała żądanie, a wynik wracał przez callback. JSI działa synchronicznie: jeśli JS wywołuje metodę modułu natywnego przez JSI, wykonanie kodu JS jest wstrzymywane do momentu uzyskania wyniku. Upraszcza to logikę (nie trzeba czekać na callbacki) i eliminuje race conditions, ale wymaga ostrożności — długie synchroniczne wywołania blokują wątek JS.
Wartości utworzone przez JSI żyją w środowisku wykonawczym silnika JS i są zarządzane przez garbage collector. Gdy kod C++ tworzy jsi::String lub jsi::Object i zwraca go do JS, środowisko automatycznie zarządza pamięcią. Jeśli kod C++ chce zachować referencję do wartości JS między wywołaniami, używa jsi::Value::getWeak() lub globalnego jsi::Object::setProperty z zachowaniem referencji na głównym obiekcie runtime. Zapobiega to przedwczesnemu usunięciu przez garbage collector.
JSI nie jest domyślnie bezpieczne wątkowo. Wszystkie wywołania metod JSI muszą pochodzić z wątku, w którym wykonywany jest JS (zwykle jest to wątek JS React Native). Jeśli moduł natywny uruchamia pracę w tle w osobnym wątku, wynik musi być przekazany z powrotem przez wątek JS za pomocą wywołania runOnJS z TurboModules. To ograniczenie jest ceną za synchroniczność i brak serializacji.
Różnica między JSI a Bridge ma fundamentalny charakter i dotyczy wszystkich aspektów interakcji JS z kodem natywnym. Bridge był asynchroniczny, serializował dane do JSON i używał kolejki komunikatów; JSI jest synchroniczny, działa z natywnymi referencjami i nie wymaga serializacji.
| Parametr | Bridge | JSI |
|---|---|---|
| Model wywołania | Kolejka asynchroniczna | Synchroniczne bezpośrednie wywołanie |
| Serializacja | JSON (serializacja + deserializacja) | Brak (bezpośrednie referencje do obiektów C++) |
| Opóźnienie | 3–10 ms na wywołanie | 0.1–0.5 ms na wywołanie |
| Typowanie | Dynamiczne (przez JSON) | Statyczne (przez Codegen) |
| Integracja C++ | Tylko przez moduły natywne (Java/ObjC) | Bezpośrednia, bez pośredników |
| Wątek | Osobny wątek natywny | Wątek JS (synchronicznie) |
Według danych React Native Team, migracja z Bridge na JSI w aplikacji Facebook Marketplace skróciła czas uruchamiania o 35% i zmniejszyła zużycie pamięci o 20% dzięki eliminacji duplikowania danych między JS a stroną natywną.
Bridge nie był „błędem” — był rozwiązaniem architektonicznym uzasadnionym w momencie tworzenia React Native w 2015 roku. Natywne programowanie dla dwóch platform z różnymi językami wymagało uniwersalnego formatu wymiany. JSON jako format serializacji był dostępny na wszystkich platformach i umożliwiał ujednolicenie interakcji. Problem ujawnił się później, gdy React Native zaczęto używać do złożonych aplikacji z tysiącami wywołań modułów natywnych na sekundę.
React Native zachowuje wsteczną kompatybilność: moduły natywne napisane dla Bridge nadal działają w nowej architekturze poprzez warstwę zgodności. Jednak w przypadku nowych modułów zaleca się natychmiastowe użycie JSI przez TurboModules. Migracja istniejących modułów polega na wymianie protokołu interakcji bez zmiany logiki biznesowej samego modułu.
JSI jest fundamentalną warstwą, na której zbudowane są wszystkie komponenty nowej architektury React Native. Bez JSI niemożliwa byłaby praca ani Fabric (nowy renderer), ani TurboModules (zoptymalizowane moduły natywne). JSI zapewnia jednolity sposób interakcji JS z C++ na wszystkich poziomach.
Fabric — nowy renderer React Native, który używa JSI do synchronicznego dostępu do reprezentacji UI w C++. W starej architekturze renderowanie przechodziło przez Bridge: JS tworzył elementy React, serializował je do JSON, wysyłał przez Bridge, strona natywna deserializowała i tworzyła UI. Fabric przez JSI tworzy obiekty C++ Shadow Tree bezpośrednio z JS, synchronicznie oblicza Layout przez Yoga i przekazuje gotowe ramki do natywnego renderera — bez ani jednej serializacji.
TurboModules — to ewolucja natywnych modułów React Native. Zamiast rejestrować moduł w Bridge i wywoływać jego metody przez JSON, TurboModules używa JSI do leniwego ładowania i bezpośredniego wywoływania. Gdy kod JS po raz pierwszy odwołuje się do modułu, JSI tworzy Host Object — ładuje on moduł natywny i udostępnia jego metody jako funkcje C++. Leniwe ładowanie oznacza, że moduł nie zużywa pamięci do pierwszego użycia — jest to szczególnie ważne w aplikacjach z dziesiątkami modułów natywnych, z których wiele jest używanych tylko na określonych ekranach.
Do pracy z JSI w nowej architekturze używany jest Codegen — narzędzie, które generuje nakładki C++ na podstawie specyfikacji JavaScript. Programista opisuje interfejs modułu natywnego w TypeScript lub Flow, a Codegen generuje kod C++ implementujący kompatybilny z JSI Host Object. Automatyzuje to rutynową pracę i gwarantuje, że typy po stronie JS i C++ są zsynchronizowane.
Rozważmy, jak wygląda praca z JSI w praktyce. W tym przykładzie tworzymy prostą klasę C++, która jest eksportowana do JS przez JSI, i wywołujemy jej metodę z kodu JavaScript React Native.
// Calculator.h — nagłówek klasy C++ dostępny z JS
class Calculator {
public:
double add(double a, double b) { return a + b; }
double multiply(double a, double b) { return a * b; }
};
Klasa Calculator zawiera dwie metody arytmetyczne. Musimy udostępnić ją z JS. W tym celu tworzymy Host Object, który opakowuje Calculator i udostępnia jego metody przez JSI.
// CalculatorHostObject.cpp — implementacja nakładki 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();
}
};
W tym kodzie metoda get jest wywoływana za każdym razem, gdy JS odwołuje się do właściwości obiektu. Jeśli nazwa właściwości to „add”, zwracana jest funkcja C++, która przyjmuje dwa argumenty z JS i wywołuje calc.add(). Wartość jest zwracana jako jsi::Value — JSI automatycznie konwertuje double na liczbę JS.
Po zarejestrowaniu Host Object w środowisku JS, wywołanie wygląda jak zwykła funkcja JS. Wszystkie typy są sprawdzane na etapie kodogeneracji, co eliminuje błędy niezgodności typów podczas wykonania.
// JavaScript — wywoływanie kalkulatora C++ przez JSI
import { Calculator } from 'react-native-calculator'
const result = Calculator.add(5, 3)
console.log(result) // 8 — synchroniczne, bez opóźnienia
const product = Calculator.multiply(4, 2.5)
console.log(product) // 10 — natychmiastowy wynik
Zwróć uwagę: wynik jest zwracany natychmiast, bez Promise, bez await, bez callbacków. To wywołanie synchroniczne, które było niemożliwe w architekturze Bridge. W przypadku długotrwałych operacji (odczyt pliku, zapytanie sieciowe) należy stosować asynchroniczne wzorce — JSI nie eliminuje potrzeby korzystania z wątków tła dla ciężkich zadań.
W praktyce większość programistów nie pisze JSI Host Objects ręcznie — tę pracę wykonuje Codegen, który generuje nakładki C++ na podstawie specyfikacji TypeScript. Jednak zrozumienie, jak działa JSI pod maską, jest niezbędne do efektywnego debugowania wydajności i przy tworzeniu złożonych modułów natywnych wymagających bezpośredniego dostępu do bibliotek C++ (Skia, FFmpeg, OpenCV).
Często zadawane pytania
Bridge działa asynchronicznie przez serializację JSON i kolejkę komunikatów — każde wywołanie wymaga 3–10 ms na konwersję danych. JSI zapewnia synchroniczny bezpośredni dostęp do obiektów C++ bez serializacji, zmniejszając opóźnienie do 0.1–0.5 ms. JSI obsługuje również przekazywanie referencji do obiektów, a nie ich kopii.
Tak, JSI zapewnia jednolity interfejs API C++, który jest zaimplementowany dla Hermes (domyślnie w React Native), JavaScriptCore (iOS) i V8 (Android). Programista nie musi pisać innego kodu dla różnych silników — Fabric i TurboModules działają tak samo na wszystkich obsługiwanych silnikach.
Tak, React Native zapewnia warstwę wstecznej kompatybilności. Moduły natywne napisane dla Bridge nadal działają w nowej architekturze. Zaleca się jednak migrację ich na TurboModules w celu uzyskania korzyści JSI — leniwego ładowania i synchronicznych wywołań.
Do codziennego programowania — nie. Specyfikacje TypeScript modułów natywnych są kompilowane do nakładek C++ automatycznie przez Codegen. Znajomość C++ jest potrzebna tylko przy tworzeniu własnych bibliotek C++ lub przy debugowaniu wydajności JSI na poziomie runtime.
JSI rozwiązuje trzy kluczowe problemy Bridge: wysokie opóźnienie z powodu serializacji JSON, brak wywołań synchronicznych i niemożność przekazywania złożonych obiektów przez referencję. JSI pozwala również integrować biblioteki C++ bezpośrednio, bez warstwy Java lub Objective-C.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również