JSI (JavaScript Interface) — це програмний шар у React Native, який надає прямий синхронний доступ із JavaScript до C++ об'єктів і функцій, замінюючи асинхронний JSON-міст Bridge. На відміну від попередника, 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-рушіями. Вона надає єдиний C++ API, який реалізується для кожного конкретного рушія: 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 надсилав запит, перемикався на інші завдання, нативна сторона обробляла запит, і результат повертався через колбек. JSI працює синхронно: якщо JS викликає метод нативного модуля через JSI, виконання JS-коду припиняється до отримання результату. Це спрощує логіку (не потрібно чекати колбеків) і усуває race conditions, але потребує обережності — тривалі синхронні виклики блокують JS-потік.
Значення, створені через JSI, живуть у середовищі виконання JS-рушія та керуються збирачем сміття. Коли C++ код створює jsi::String або jsi::Object і повертає його в JS, середовище автоматично керує пам'яттю. Якщо C++ код бажає зберегти посилання на JS-значення між викликами, використовується jsi::Value::getWeak() або глобальний jsi::Object::setProperty із збереженням посилання на кореневому об'єкті середовища виконання. Це запобігає передчасному видаленню збирачем сміття.
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 для синхронного доступу до C++ представлень UI. У старій архітектурі рендеринг проходив через Bridge: JS створював React-елементи, серіалізував їх у 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++ код, що реалізує JSI-сумісний Host Object. Це автоматизує рутинну роботу та гарантує, що типи на стороні 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, без колбеків. Це синхронний виклик, який був неможливий в архітектурі Bridge. Для тривалих операцій (читання файлу, мережевий запит) слід використовувати асинхронні патерни — JSI не відміняє необхідність у фонових потоках для важких завдань.
На практиці більшість розробників не пишуть JSI Host Objects вручну — цю роботу виконує Codegen, який генерує C++ обгортки на основі TypeScript-специфікацій. Однак розуміння того, як працює JSI всередині, необхідне для ефективного налагодження продуктивності та при створенні складних нативних модулів, що потребують прямого доступу до C++ бібліотек (Skia, FFmpeg, OpenCV).
Часто задавані питання
Bridge працює асинхронно через JSON-серіалізацію та чергу повідомлень — кожен виклик потребує 3–10 мс на перетворення даних. JSI надає синхронний прямий доступ до C++ об'єктів без серіалізації, знижуючи затримку до 0.1–0.5 мс. JSI також підтримує передачу посилань на об'єкти, а не їх копій.
Так, JSI надає єдиний C++ API, який реалізований для Hermes (React Native за замовчуванням), JavaScriptCore (iOS) і V8 (Android). Розробнику не потрібно писати різний код для різних рушіїв — Fabric і TurboModules працюють однаково на всіх підтримуваних рушіях.
Так, React Native надає шар зворотної сумісності. Нативні модулі, написані для Bridge, продовжують працювати в новій архітектурі. Однак рекомендується мігрувати їх на TurboModules для отримання переваг JSI — лінивого завантаження та синхронних викликів.
Для повсякденної розробки — ні. TypeScript-специфікації нативних модулів компілюються в C++ обв'язки автоматично через Codegen. Знання C++ необхідне лише при створенні власних C++ бібліотек або при налагодженні продуктивності JSI на рівні середовища виконання.
JSI вирішує три ключові проблеми Bridge: високу затримку через JSON-серіалізацію, відсутність синхронних викликів і неможливість передавати складні об'єкти за посиланням. Також JSI дозволяє інтегрувати C++ бібліотеки безпосередньо, без Java або Objective-C прошарку.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також