【翻译】React Native JSI 深度解析(第 4 篇):你的第一个 React Native JSI 函数

3 阅读17分钟

React Native JSI 深度解析(第 4 篇):你的第一个 React Native JSI 函数

“简单性是可靠性的先决条件。” — Edsger W. Dijkstra, How Do We Tell Truths That Might Hurt?, 1975

导读: JSI 函数本质上是“伪装成 JavaScript 函数的 C\+\+ lambda”。没有序列化、没有 bridge、没有代码生成,只有一个可由运行时直接调用的 C\+\+ callable。本文会手把手带你从零写出一个:如何注册到运行时、读取参数、校验类型、处理错误,以及从 JavaScript 调用它。读完后,你会得到一个几乎零样板代码的可运行原生模块。

系列:React Native JSI Deep Dive(12 篇)
第 1 篇:React Native 架构——线程、Hermes 与事件循环 | 第 2 篇:React Native Bridge vs JSI——变化与原因 | 第 3 篇:面向 JavaScript 开发者的 C\+\+ | 第 4 篇:你的第一个 React Native JSI 函数(你在这里) | 第 5 篇:HostObjects——将 C\+\+ 类暴露给 JavaScript | 第 6 篇:内存所有权 | 第 7 篇:平台接线 | 第 8 篇:线程与异步 | 第 9 篇:音频管线 | 第 10 篇:存储引擎 | 第 11 篇:模块方案对比 | 第 12 篇:调试与故障定位


快速回顾

第 2 篇里我们看到,JSI 用直接 C\+\+ 函数调用替代了 JSON bridge——没有序列化、没有异步队列。在第 3 篇里,我们补齐了 C\+\+ 词汇:引用(&)、指针(*)、RAII、智能指针,以及显式 capture 的 lambda。

现在把这些全部用起来。本文就是你写下第一行原生模块代码的地方。


在 JavaScript 运行时里安装原生函数

在 Web 浏览器里,你可以往全局作用域挂 JavaScript 函数(window.myFunc = ...),但你不能安装原生函数——即由 C\+\+ 实现、执行时不需要 JavaScript 引擎解释的函数。浏览器提供的原生 API 面(fetchsetTimeout、DOM)由浏览器厂商固定提供。

在 React Native 里你可以做到。JSI 允许你把 C\+\+ 函数直接安装进 JavaScript 运行时。从 JavaScript 视角看,它们和其他函数没有区别;从 C\+\+ 视角看,它们是接收 runtime 与参数的 lambda——原生执行,而非解释执行。

做这件事的核心 API 只有一个:jsi::Function::createFromHostFunction。(你也可以通过 HostObjectevaluateJavaScript 创建可调用函数,但 createFromHostFunction 是专门用于注册 C\+\+ 函数的标准 API。)


第 1 步:最简单的 JSI 函数

先从绝对最小可用版本开始:一个不接收参数、返回数字的函数。

cpp/install.cpp —— 种子版本

#include <jsi/jsi.h>

using namespace facebook;

void install(jsi::Runtime& rt) {
    auto fn = jsi::Function::createFromHostFunction(
        rt,                                         // 1. runtime
        jsi::PropNameID::forAscii(rt, "getFortyTwo"), // 2. 函数名(用于堆栈)
        0,                                          // 3. 期望参数个数
        [](jsi::Runtime& rt,                        // 4. lambda
           const jsi::Value& thisVal,
           const jsi::Value* args,
           size_t count) -> jsi::Value {
            return jsi::Value(42);
        }
    );

    rt.global().setProperty(rt, "getFortyTwo", std::move(fn));
}

App.js —— 调用

const n = getFortyTwo();
console.log(n); // 42

输出:

42

createFromHostFunction 里发生了四件事:

  1. rt:运行时实例。每次 JSI 调用都要它;它就是通往 JavaScript 世界的句柄。
  2. PropNameID::forAscii(rt, "getFortyTwo"):函数名。它会显示在错误堆栈里,但不决定函数安装位置;安装位置由 setProperty 决定。
  3. 0:期望参数个数。它只是信息(对应 JS 的 .length),运行时不会强制校验。
  4. lambda:真正执行的 C\+\+ 代码。JavaScript 调用函数时,它会接收 runtime、this 值、参数数组指针和参数计数。

最后一行 rt.global().setProperty(...) 会把函数挂到 JavaScript 全局对象上。调用之后,任意 JavaScript 代码都能执行 getFortyTwo()

关键洞察: 传给 PropNameID 的函数名和传给 setProperty 的属性名是互相独立的。你可以在堆栈里把函数命名为 "internalMathOp",但把它安装成 global.getFortyTwo。实际工程里建议保持一致,避免混淆。


第 2 步:读取参数

忽略参数的函数没太大用。下面改成两个数相加。

cpp/install.cpp —— 读取参数(⚠️ 先不做校验)

void install(jsi::Runtime& rt) {
    auto add = jsi::Function::createFromHostFunction(
        rt,
        jsi::PropNameID::forAscii(rt, "nativeAdd"),
        2,  // 期望 2 个参数
        [](jsi::Runtime& rt,
           const jsi::Value& thisVal,
           const jsi::Value* args,
           size_t count) -> jsi::Value {
            double a = args[0].asNumber();  // ← 第一个参数转 double
            double b = args[1].asNumber();  // ← 第二个参数
            return jsi::Value(a + b);
        }
    );

    rt.global().setProperty(rt, "nativeAdd", std::move(add));
}

App.js

console.log(nativeAdd(3, 7)); // 10
console.log(nativeAdd(1.5, 2.5)); // 4

输出:

10
4

args 参数是一个 jsi::Value 数组的指针(和第 3 篇讲的一样,是 C 风格数组传递)。args[0] 是第一个参数,args[1] 是第二个。count 表示实际传入了多少个参数。

坑点: 这段代码是为了简化演示,故意没做校验——不要照这个模式上线。 若 JavaScript 只调用 nativeAdd(5)args[1] 会越界读取参数数组。这在 C\+\+ 里是未定义行为,可能崩溃、破坏内存,或悄悄产出脏数据。第 3 步会用 count 校验修复。索引 args 前必须先检查 count

asNumber() 会把 jsi::Value 转成 C\+\+ double。那如果 JavaScript 传的是字符串呢?

想一想: nativeAdd("hello", 7) 会发生什么?args[0].asNumber() 遇到字符串时,会返回 NaN?抛异常?还是崩溃?

它会抛出 C\+\+ 异常;JSI 运行时会捕获并转成 JavaScript Error,所以 JS 侧可用 try/catch 捕获。应用不会直接崩,但这次调用会失败,并给出类似 “expected a number” 的通用错误。比静默返回垃圾值好,但仍应显式做参数校验,而不是依赖转换时抛错——既为了更好的报错信息,也为了安全性(见上面的“缺参数”问题)。


第 3 步:校验参数

生产级 JSI 函数必须校验输入。jsi::Value 类型提供了类型判断方法:isNumber()isString()isObject()isUndefined()isNull()isBool()isSymbol()isBigInt()

cpp/install.cpp —— 增加参数校验

void install(jsi::Runtime& rt) {
    auto add = jsi::Function::createFromHostFunction(
        rt,
        jsi::PropNameID::forAscii(rt, "nativeAdd"),
        2,
        [](jsi::Runtime& rt,
           const jsi::Value& thisVal,
           const jsi::Value* args,
           size_t count) -> jsi::Value {
            // 校验参数数量
            if (count < 2) {                                       // ← NEW
                throw jsi::JSError(rt, "nativeAdd requires 2 arguments");
            }

            // 校验参数类型
            if (!args[0].isNumber() || !args[1].isNumber()) {      // ← NEW
                throw jsi::JSError(rt, "nativeAdd arguments must be numbers");
            }

            double a = args[0].asNumber();
            double b = args[1].asNumber();
            return jsi::Value(a + b);
        }
    );

    rt.global().setProperty(rt, "nativeAdd", std::move(add));
}

App.js —— 错误处理

try {
  nativeAdd("hello", 7);
} catch (e) {
  console.log(e.message); // "nativeAdd arguments must be numbers"
}

try {
  nativeAdd(5);
} catch (e) {
  console.log(e.message); // "nativeAdd requires 2 arguments"
}

输出:

"nativeAdd arguments must be numbers"
"nativeAdd requires 2 arguments"

模式始终一样:

  1. 检查 count:JavaScript 是否传够参数?
  2. 检查类型:参数类型是否符合预期?
  3. 抛出 jsi::JSError:校验失败时,转成可被 JavaScript 捕获的错误。

坑点: 一定要在 asNumber()asString() 等转换前先做校验。类型不匹配时,这些转换会抛 C\+\+ 异常(JSI 会转成 JS 错误),但报错信息很泛(例如 “Value is string, expected a number”)。你自己的错误信息(例如 "nativeAdd arguments must be numbers")更利于排查。更重要的是,args 索引前必须先校验 counti >= count 时访问 args[i] 属于未定义行为,任何异常处理都兜不住。


第 4 步:错误处理(jsi::JSError

jsi::JSError 是 C\+\+ 异常和 JavaScript 错误之间的桥梁。在 host function 内抛出 jsi::JSError 后,它会以普通 Error 对象形式回到 JavaScript,可被 try/catch 捕获。

JSI 运行时确实会捕获 host function 抛出的 std::exception 子类,并转成 JavaScript 错误(见 jsi.h 文档:如果抛出了 C++ 异常,系统会创建一个 JS Error 并将其抛到 JS;如果该 C++ 异常继承自 std::exception,则 Error 的 message 就是 what() 的返回值。原文:“If a C\+\+ exception is thrown, a JS Error will be created and thrown into JS; if the C\+\+ exception extends std::exception, the Error's message will be whatever what() returns”)。但不继承 std::exception 的异常,或根本不会抛异常的未定义行为(比如数组越界),仍会导致应用崩溃。只依赖运行时兜底并不稳健——报错泛化,且对非异常型 UB 无效。

稳健做法是:在 native 逻辑外包一层 try/catch,掌控报错并兜住所有异常。

cpp/install.cpp —— 安全错误边界

[](jsi::Runtime& rt,
   const jsi::Value& thisVal,
   const jsi::Value* args,
   size_t count) -> jsi::Value {
    try {
        // 你的 native 逻辑
        auto result = someCppFunction(args[0].asNumber());
        return jsi::Value(result);
    } catch (const jsi::JSError&) {
        throw;  // 已经是 JS 错误,直接透传
    } catch (const std::exception& e) {
        throw jsi::JSError(rt, std::string("Native error: ") + e.what());
    } catch (...) {
        throw jsi::JSError(rt, "Unknown native error");
    }
}

这三级 catch 的意义:

  • jsi::JSError 原样透传(它本来就是 JS 错误)。
  • 标准 C\+\+ 异常(std::runtime_errorstd::invalid_argument 等)用更清晰的消息包装后抛出。
  • 未知异常有兜底,不让应用直接崩掉。

关键洞察: 每个 JSI host function 都是两个世界的边界。JSI 能自动处理 std::exception 子类,但未定义行为(悬空指针、越界访问)会绕过所有异常处理并直接崩溃。try/catch 包装提供的是“纵深防御”:更清晰的报错、对非标准异常的兜底、以及对 JavaScript 可见错误的明确控制。你可以把它看作 native 世界的 React error boundary。


jsi::Value 类型系统

在构建更复杂模块前,先搞清你会接触的类型。jsi::Value 是 tagged union——单个类型可承载任意 JavaScript 值。

读取值(JS → C\+\+)

JavaScript 类型类型检查转换方法C\+\+ 类型
numberval.isNumber()val.asNumber()double
stringval.isString()val.asString(rt)jsi::String
booleanval.isBool()val.getBool()bool
objectval.isObject()val.asObject(rt)jsi::Object
nullval.isNull()
undefinedval.isUndefined()

图 1:jsi::Value 的类型检查与转换。转换前务必先检查类型。

注意这个不对称性:asNumber() 不需要 rt,而 asString(rt)asObject(rt) 需要。原因是 number/boolean 是纯 C\+\+ 值(doublebool),而 string/object 由 JS 引擎管理,访问时需要 runtime 句柄。

要把 jsi::String 变成 std::string,调用 .utf8(rt)

读取字符串参数:

jsi::String jsStr = args[0].asString(rt);   // jsi::String(引擎管理)
std::string cppStr = jsStr.utf8(rt);        // std::string(C\\+\\+ 持有拷贝)

创建值(C\+\+ → JS)

C\+\+ 值JSI 构造方式JavaScript 结果
423.14jsi::Value(42)number
true / falsejsi::Value(true)boolean
"hello"jsi::String::createFromUtf8(rt, "hello")string
jsi::Value::null()null
jsi::Value::undefined()undefined
jsi::Object(rt){}(空对象)

图 2:从 C\+\+ 构造 JavaScript 值。数字和布尔可直接包裹,字符串和对象需要 runtime。

返回不同类型示例:

// 返回数字
return jsi::Value(42);

// 返回字符串
return jsi::String::createFromUtf8(rt, "hello from C\\+\\+");

// 返回带属性的对象
auto obj = jsi::Object(rt);
obj.setProperty(rt, "name", jsi::String::createFromUtf8(rt, "JSI"));
obj.setProperty(rt, "version", jsi::Value(4));
return obj;  // JS 收到:{ name: "JSI", version: 4 }

组合起来:一个 Math 模块

来做个真实一点的例子:一个小型数学模块,包含多个函数,并把它们作为同一个对象的属性安装,而不是污染全局作用域。

cpp/MathModule.cpp —— 完整模块

#include <jsi/jsi.h>
#include <cmath>
#include <string>

using namespace facebook;

void installMathModule(jsi::Runtime& rt) {

    // Helper:校验下标 i 的参数是否为 number
    auto requireNumber = [](jsi::Runtime& rt,
                            const jsi::Value* args,
                            size_t count,
                            size_t index,
                            const char* fnName) {
        if (index >= count) {
            throw jsi::JSError(rt,
                std::string(fnName) + ": missing argument at index "
                + std::to_string(index));
        }
        if (!args[index].isNumber()) {
            throw jsi::JSError(rt,
                std::string(fnName) + ": argument " + std::to_string(index)
                + " must be a number");
        }
    };

    // --- add(a, b) ---
    auto add = jsi::Function::createFromHostFunction(
        rt, jsi::PropNameID::forAscii(rt, "add"), 2,
        [requireNumber](jsi::Runtime& rt, const jsi::Value&,
                        const jsi::Value* args, size_t count) -> jsi::Value {
            requireNumber(rt, args, count, 0, "add");
            requireNumber(rt, args, count, 1, "add");
            return jsi::Value(args[0].asNumber() + args[1].asNumber());
        }
    );

    // --- multiply(a, b) ---
    auto multiply = jsi::Function::createFromHostFunction(
        rt, jsi::PropNameID::forAscii(rt, "multiply"), 2,
        [requireNumber](jsi::Runtime& rt, const jsi::Value&,
                        const jsi::Value* args, size_t count) -> jsi::Value {
            requireNumber(rt, args, count, 0, "multiply");
            requireNumber(rt, args, count, 1, "multiply");
            return jsi::Value(args[0].asNumber() * args[1].asNumber());
        }
    );

    // --- sqrt(x) ---
    auto sqrt = jsi::Function::createFromHostFunction(
        rt, jsi::PropNameID::forAscii(rt, "sqrt"), 1,
        [requireNumber](jsi::Runtime& rt, const jsi::Value&,
                        const jsi::Value* args, size_t count) -> jsi::Value {
            requireNumber(rt, args, count, 0, "sqrt");
            double x = args[0].asNumber();
            if (x < 0) {
                throw jsi::JSError(rt, "sqrt: argument must be non-negative");
            }
            return jsi::Value(std::sqrt(x));
        }
    );

    // --- describe() — 返回对象 ---
    auto describe = jsi::Function::createFromHostFunction(
        rt, jsi::PropNameID::forAscii(rt, "describe"), 0,
        [](jsi::Runtime& rt, const jsi::Value&,
           const jsi::Value* args, size_t count) -> jsi::Value {
            auto obj = jsi::Object(rt);
            obj.setProperty(rt, "name",
                jsi::String::createFromUtf8(rt, "NativeMath"));
            obj.setProperty(rt, "version", jsi::Value(1));
            obj.setProperty(rt, "engine",
                jsi::String::createFromUtf8(rt, "JSI"));
            return obj;
        }
    );

    // 把函数安装到同一个对象上
    auto mathModule = jsi::Object(rt);
    mathModule.setProperty(rt, "add", std::move(add));
    mathModule.setProperty(rt, "multiply", std::move(multiply));
    mathModule.setProperty(rt, "sqrt", std::move(sqrt));
    mathModule.setProperty(rt, "describe", std::move(describe));

    rt.global().setProperty(rt, "NativeMath", std::move(mathModule));
}

App.js —— 使用模块

console.log(NativeMath.add(3, 7)); // 10
console.log(NativeMath.multiply(6, 7)); // 42
console.log(NativeMath.sqrt(144)); // 12
console.log(NativeMath.describe()); // { name: "NativeMath", version: 1, engine: "JSI" }

try {
  NativeMath.sqrt(-1);
} catch (e) {
  console.log(e.message); // "sqrt: argument must be non-negative"
}

try {
  NativeMath.add("hello", 7);
} catch (e) {
  console.log(e.message); // "add: argument 0 must be a number"
}

输出:

10
42
12
{ "name": "NativeMath", "version": 1, "engine": "JSI" }
"sqrt: argument must be non-negative"
"add: argument 0 must be a number"

本文和第 3 篇的所有核心概念都在这个例子里了:

模式实际发生了什么
jsi::Runtime& rt引用:借用 runtime
const jsi::Value* args指针:C 风格参数数组
requireNumber lambda通过值捕获进入每个 host function
jsi::JSErrorC\+\+ 异常 → JavaScript Error
std::move(add)move 语义:把所有权转移到模块对象
jsi::Object(rt)栈上 JSI 对象:句柄由 RAII 管理
mathModulesetProperty挂载到对象而非全局:命名空间更干净

全局安装 vs 对象安装

函数安装位置有两种选择:

全局安装:函数在所有地方可见。

全局方式(裸函数可直接调用):

rt.global().setProperty(rt, "nativeAdd", std::move(fn));
// JS: nativeAdd(3, 7)

对象安装:函数放在命名空间下。

对象方式(挂在模块名下):

auto module = jsi::Object(rt);
module.setProperty(rt, "add", std::move(fn));
rt.global().setProperty(rt, "NativeMath", std::move(module));
// JS: NativeMath.add(3, 7)

优先使用对象安装。它能避免污染全局命名空间,把相关函数自然分组,也更符合 JavaScript 模块习惯。只有非常简单的单函数模块才值得考虑全局安装。


取舍:这种方式做不到什么

纯 JSI 函数(即本文展示方式)很强,但也有边界:

能力JSI Host Functions你需要的替代方案
同步调用可以(运行在 JS 线程)
返回值可以(任意 jsi::Value
有状态模块可通过 shared_ptr capture 实现,但冗长(无属性、无 thisHostObjects(第 5 篇):以更清晰接口暴露 C\+\+ 类
异步操作不行(必须同步返回)CallInvoker(第 8 篇):后台线程 + Promise
平台 API 接入不行(仅纯 C\+\+)平台接线(第 7 篇):Obj-C\+\+/JNI 桥接
从 JS 获得类型安全不行(手写校验)TurboModules(第 11 篇):由 Flow/TS 规范代码生成

图 3:JSI host function 的能力与边界。第 5-11 篇会逐一补上这些能力。

最大的人体工学限制是:没有干净的有状态接口。你可以在 lambda 里 capture shared_ptr 来共享状态(第 3 篇 key-value store 示例就是这样),但很快会变得啰嗦——没有属性访问、没有 this,也无法把一组方法组织成 JavaScript 可检查的对象。当你需要数据库连接、缓存或流式音频会话时,你要的是一个可被 JavaScript 当成一等对象交互的 C\+\+ 对象。这正是 HostObjects 的作用,也是第 5 篇要讲的内容。


关键结论

  • createFromHostFunction 是核心 API。 它接收 runtime、函数名(用于堆栈)、参数个数和一个 C\+\+ lambda。lambda 就是 JavaScript 实际调用的逻辑。这就是全部机制。
  • 参数校验必须做。 访问 args[index] 前先检查 count;调用 asNumber() / asString(rt) / asObject(rt) 前先检查 isNumber() / isString() / isObject()。永远不要假设 JavaScript 一定按你想的传参。
  • 原生错误用 jsi::JSError 包装。 JSI 会自动捕获 std::exception 子类,但报错通常太泛。加 try/catch 能得到更清晰报错,也可兜住非标准异常。jsi::JSError 直接透传即可。未定义行为(越界、悬空指针)会绕过异常系统,所以仍需先做输入校验。
  • 优先安装到对象,不要挂全局。 把相关函数放在命名空间对象下(NativeMath.add)而非污染全局(nativeAdd),更干净、更符合 JS 习惯。
  • host function 是同步的。 它们运行在 JS 线程并立即返回。若操作耗时超过 16ms 帧预算中的可用份额,就会明显阻塞事件循环。异步模式(后台线程 + Promise)在第 8 篇展开。

回看崩溃栈

再看一次第 1 篇里的崩溃栈。在 mqt_js 线程上,最后一帧是:

facebook::jsi::Runtime::PointerValue::invalidate()

这就是 JSI 层。PointerValue 是 JSI 用来在 C\+\+ 侧引用 JavaScript 对象的内部类型——每个 jsi::Objectjsi::Functionjsi::String 都包装了一个 PointerValue。当 invalidate() 运行时,运行时正在释放一个 JSI 引用。这正是你在第 3 篇旁注中看到的 HostObject 析构链触发机制。JSI 运行时判定该对象不再被 JavaScript 可达,于是开始清理 native 侧。本文你学会创建的 host function 与 HostObject,正是那段 trace 中被销毁对象的类型

系统目前长这样:

 JS Thread            UI Thread           Native Thread
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│    Hermes     │   │   Platform    │   │    C\\+\\+ code   │
│               │   │               │   │               │
│  nativeAdd()──┼───┼──── JSI ─────┼──▶│  C\\+\\+ lambda   │  ← NEW
│               │   │               │   │  stack / heap │
│               │   │               │   │  shared_ptr   │
└───────────────┘   └───────────────┘   └───────────────┘

常见问题(FAQ)

在 React Native 里如何创建 JSI 函数?

使用 jsi::Function::createFromHostFunction(),把一个 C\+\+ lambda 注册成 JavaScript 函数。该 lambda 会接收 runtime、以 jsi::Value 表示的参数,并返回 jsi::Value

JSI 函数可以是同步的吗?

可以。JSI 函数在 JS 线程上同步执行,立即返回结果,不需要 Promise 或回调。通常只适合在约 ~1ms 内完成的操作。

JSI 函数里抛异常会怎样?

继承 std::exception 的 C\+\+ 异常会被 JSI 运行时捕获,并转换成 JavaScript Error 对象,可在 JS 侧通过 try/catch 捕获。


下一篇

你现在已经能把 C\+\+ 函数安装到 JavaScript 里了。但这些函数是无状态的——每次调用彼此独立。如果你想暴露的是一个 C\+\+ 对象呢?比如能记住状态的数据库连接、可读写的缓存、可启动/暂停/停止的音频会话。

第 5 篇:HostObjects——将 C\+\+ 类暴露给 JavaScript中,你会学到如何把 C\+\+ 类以一等对象形式暴露给 JavaScript(有属性也有方法)。HostObjects 是 JSI 从“有趣机制”走向“完整原生模块框架”的关键节点。你将构建一个 key-value store,让 storage.get('key') 同步调用 C\+\+——无需 await、无需 bridge、无需序列化。

第 4 篇给你函数,第 5 篇给你对象。


参考资料与延伸阅读

  1. JSI Header — jsi.h(完整 API 面,facebook/react-native)
  2. React Native — The New Architecture(官方文档)
  3. react-native-mmkv — 生产级 JSI 模块(源码参考)
  4. react-native-vision-camera — 生产级 JSI + HostObject 模块(源码参考)
  5. cppreference — Lambda Expressions
  6. cppreference — std::exception

快速参考

createFromHostFunction

auto fn = jsi::Function::createFromHostFunction(
    rt,                                          // jsi::Runtime&
    jsi::PropNameID::forAscii(rt, "name"),      // 调试名(用于堆栈)
    2,                                           // 参数个数(JS .length,不强制)
    [](jsi::Runtime& rt, const jsi::Value& thisVal,
       const jsi::Value* args, size_t count) -> jsi::Value {
        // your code here
        return jsi::Value::undefined();
    }
);

读取参数

类型检查读取值C\+\+ 类型
args[i].isNumber()args[i].asNumber()double
args[i].isString()args[i].asString(rt).utf8(rt)std::string
args[i].isBool()args[i].getBool()bool
args[i].isObject()args[i].asObject(rt)jsi::Object
args[i].isUndefined()
args[i].isNull()

创建返回值

return jsi::Value(42);                           // number
return jsi::Value(true);                         // boolean
return jsi::String::createFromUtf8(rt, "hello");// string
return jsi::Value::undefined();                  // undefined
return jsi::Value::null();                       // null

挂载到全局

rt.global().setProperty(rt, "fnName", std::move(fn));

向 JavaScript 抛错

throw jsi::JSError(rt, "error message");

系列:React Native JSI Deep Dive(12 篇)
第 1 篇:React Native 架构——线程、Hermes 与事件循环 | 第 2 篇:React Native Bridge vs JSI——变化与原因 | 第 3 篇:面向 JavaScript 开发者的 C\+\+ | 第 4 篇:你的第一个 React Native JSI 函数(你在这里) | 第 5 篇:HostObjects——将 C\+\+ 类暴露给 JavaScript | 第 6 篇:内存所有权 | 第 7 篇:平台接线 | 第 8 篇:线程与异步 | 第 9 篇:音频管线 | 第 10 篇:存储引擎 | 第 11 篇:模块方案对比 | 第 12 篇:调试与故障定位

术语表(本篇命中)

英文术语中文译法说明
JSIJSIReact Native 的 JavaScript Interface
Host FunctionHost Function由 C\+\+ 提供、可被 JS 调用的函数
HostObjectHostObject向 JS 暴露 C\+\+ 对象的机制
runtime运行时jsi::Runtime 实例
bridgebridgeRN 旧架构的跨端通信层
serialization序列化跨边界数据编码过程
lambdalambdaC\+\+ 匿名函数对象
RAIIRAII资源获取即初始化
smart pointer智能指针shared_ptr
undefined behavior未定义行为C\+\+ 标准未规定的行为结果
event loop事件循环JS 线程任务调度循环
frame budget帧预算每帧可用计算时间窗口
TurboModulesTurboModulesRN 新架构模块方案
CallInvokerCallInvoker跨线程调度调用工具