14-AI智能管家JSBridge深度问答

AI 智能管家 JSBridge 深度问答(面试 + 实战)

用途:把 JSBridge 双向通信机制讲透,含 6 个深度问题 + 完整开门案例

适合:被追问「getToken 是步骤几」「method 命名规则」「原生→H5 怎么回传数据」时脱口而出

核心思想:method 名字串起 H5→RN→H5 三步,requestId 串起一次会话


一、JSBridge 双向通信机制(10 步)

先记住这张图,后面所有问题都围绕它展开。
业务后端 原生 handler webViewHandlers.js WebView H5 (web/agent) 业务后端 原生 handler webViewHandlers.js WebView H5 (web/agent) #mermaid-svg-QsisKGIStXRz1YrO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QsisKGIStXRz1YrO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QsisKGIStXRz1YrO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QsisKGIStXRz1YrO .error-icon{fill:#552222;}#mermaid-svg-QsisKGIStXRz1YrO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QsisKGIStXRz1YrO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QsisKGIStXRz1YrO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QsisKGIStXRz1YrO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QsisKGIStXRz1YrO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QsisKGIStXRz1YrO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QsisKGIStXRz1YrO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QsisKGIStXRz1YrO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QsisKGIStXRz1YrO .marker.cross{stroke:#333333;}#mermaid-svg-QsisKGIStXRz1YrO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QsisKGIStXRz1YrO p{margin:0;}#mermaid-svg-QsisKGIStXRz1YrO .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QsisKGIStXRz1YrO text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-QsisKGIStXRz1YrO .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QsisKGIStXRz1YrO .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-QsisKGIStXRz1YrO .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-QsisKGIStXRz1YrO .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-QsisKGIStXRz1YrO #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-QsisKGIStXRz1YrO .sequenceNumber{fill:white;}#mermaid-svg-QsisKGIStXRz1YrO #sequencenumber{fill:#333;}#mermaid-svg-QsisKGIStXRz1YrO #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-QsisKGIStXRz1YrO .messageText{fill:#333;stroke:none;}#mermaid-svg-QsisKGIStXRz1YrO .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QsisKGIStXRz1YrO .labelText,#mermaid-svg-QsisKGIStXRz1YrO .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-QsisKGIStXRz1YrO .loopText,#mermaid-svg-QsisKGIStXRz1YrO .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-QsisKGIStXRz1YrO .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QsisKGIStXRz1YrO .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-QsisKGIStXRz1YrO .noteText,#mermaid-svg-QsisKGIStXRz1YrO .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-QsisKGIStXRz1YrO .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QsisKGIStXRz1YrO .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QsisKGIStXRz1YrO .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QsisKGIStXRz1YrO .actorPopupMenu{position:absolute;}#mermaid-svg-QsisKGIStXRz1YrO .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-QsisKGIStXRz1YrO .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QsisKGIStXRz1YrO .actor-man circle,#mermaid-svg-QsisKGIStXRz1YrO line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-QsisKGIStXRz1YrO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} H5 ->> 原生 的入口 WebView 的 onMessage 回调 查 handlers 表 找到对应方法 注意是 ctx.sendMessage不是 webViewRef.sendMessageToWebView step1 postMessage method params requestId1step2 onMessage 事件触发2step3 handleWebViewMessage 分发3step4 调用对应 handler4step5 调业务 API5step6 返回数据6step7 调用 ctx.sendMessage method data requestId7step8 调用 webViewRef.injectJavaScript 注入 JS8step9 dispatchEvent nativeMessage 事件9step10 拿到数据 渲染到聊天界面10

步骤速查表

步骤 谁 调什么 代码位置
step1 H5 window.ReactNativeWebView.postMessage H5 端 SDK
step2 WebView onMessage 回调 自动触发 WebView.js prop
step3 webViewHandlers.js handleWebViewMessage 函数 utils/webViewHandlers.js
step4 webViewHandlers.js handlers[method] 按 method 查表 同上
step5 原生 handler 调业务 API(fetch) utils/agentToolHandlers/doorHandlers.js
step6 业务后端 返回数据 ---
step7 原生 handler ctx.sendMessage(method, data, requestId) doorHandlers.js
step8 webViewHandlers.js 到 WebView.js ctx.sendMessage 到 webViewRef.injectJavaScript WebView.js
step9 H5 window.dispatchEvent nativeMessage 注入的 JS 触发 H5 监听
step10 H5 拿到数据 渲染聊天界面 H5 端 SDK

二、6 个深度问题速答

Q1:智能体云服务(nexa.newbest.com.cn)是哪个厂商?

A :公司域名 newbest.com.cn 的子域名,大概率是采购的第三方 SaaS (思必驰/涂鸦/大厂之一)。LLM 包含在云服务里,前端不需要单独买。

判断依据:

证据 推断
域名 <YOUR_AGENT_DOMAIN> 公司自有域名,不是第三方云厂商
tenantId + agentId 多租户配置 SaaS 服务典型特征
SDK AgentChatSDK 第三方厂商提供的标准 SaaS SDK 命名

LLM 处理 :智能体云服务内部封装,前端零感知。买的套餐里包含 LLM 调用次数。


Q2:前端需要理解这么详细吗?RN 项目能区分「智能体云服务」和「LLM」吗?

A :前端不用理解这么深 。RN 代码完全看不见 LLM ,把云服务当黑盒。

前端只需要理解 3 句话:

复制代码
1. RN 端把用户说的话发给智能体云服务
2. 等结果
3. 把结果显示出来

中间的「云服务怎么调 LLM、怎么过滤权限 」完全在云服务内部,前端零感知。

层级 RN 项目能看到 看不到
智能体云服务 能看到(URL <YOUR_AGENT_DOMAIN>) ---
LLM 完全看不到 模型名称 调用次数 prompt
工具注册表 完全看不到 哪些工具注册了
权限过滤 完全看不到 用户能用哪些工具

Q3:智能体云服务怎么处理工具权限?

A :大致流程是用户身份 → 权限过滤 → 工具列表 + 用户消息 → LLM → 二次校验 → 返回。前端不可见。

推测的流程:

复制代码
用户发请求「帮我开一号楼大门」
   智能体云服务 收到请求
   1. 识别用户身份 拿到 userId/token
   2. 查该用户的权限(A 能调哪些工具)
   3. 过滤工具注册表(A 只能用 [查天气, 开门])
   4. 把用户身份 + 过滤后的工具列表 + 用户消息 一起发给 LLM
   5. LLM 返回 要调开门
   6. 智能体云服务再次校验权限(防止 LLM 幻觉)
   7. 返回结果给前端

关键 :LLM 不可信 ,可能幻觉瞎说或被 prompt 注入越权,云服务永远要做最终权限校验。


Q4:onMessage / injectedJavaScript / injectJavaScript() 是啥?

答:3 个相似但完全不同的东西
名称 类型 何时调用 用途
injectedJavaScript WebView 的 prop 初始化时 跑一次 注入静态配置 BASE_URL 禁用缩放
injectJavaScript() WebView 实例的 方法 任何时候手动调用 动态注入 JS 业务数据回传
onMessage WebView 的 prop 回调 H5 发消息时触发 接收 H5 发来的数据

对照代码:

javascript 复制代码
// WebView.js 第 421 440 行
<WebView
  onMessage={this.handleWebViewMessage}              // Q4-A 监听 H5 发来的消息
  injectedJavaScript={injectedJavaScript}            // Q4-B 初始化时跑一次
  injectedJavaScriptBeforeContentLoaded={...}        // Q4-C DOM 加载前注入
/>
javascript 复制代码
// WebView.js sendMessageToWebView 方法
sendMessageToWebView = (method, data, requestId) => {
  const jsCode = `
    window.dispatchEvent(new CustomEvent('nativeMessage', {
      detail: { method: '${method}', requestId: '${requestId}', data: ${JSON.stringify(data)} }
    }));
    true;
  `;
  // 这里调用的是 webViewRef 的实例方法 不是 prop
  this.webViewRef.current?.injectJavaScript(jsCode);
};

对照表:

业务数据回调用哪个 用 prop 还是方法
初始化静态配置 injectedJavaScript prop
业务数据回传 webViewRef.current.injectJavaScript() 方法
三个 prop / 方法分别注入什么?
名称 注入时机 注入内容
injectedJavaScriptBeforeContentLoaded DOM 加载前 宿主 App 信息 安全区域 console 转发
injectedJavaScript DOM 加载后 BASE_URL isInXingzhijia 标记 viewport
onLoad 回调里 injectJavaScript() 加载完成后 最新的 __SAFE_AREA_INSETS__
sendMessageToWebView() 里的 injectJavaScript() 任何时候 业务数据回传(dispatchEvent nativeMessage)

业务 API 数据回传走的是 sendMessageToWebView 里的 injectJavaScript(),不是 injectedJavaScript prop。


Q5:onShouldStartLoadWithRequest 是 WebView 自带的吗?

A :是 react-native-webview 提供的 prop,业务自定义 URL 拦截逻辑。

项目代码:

javascript 复制代码
handleShouldStartLoadWithRequest = (request) => {
  const currentUri = this.props.route?.params?.uri;
  const requestUrl = request?.url;

  // 规则 1: 空 URL 放行
  if (!requestUrl) return true;

  // 规则 2: 非顶层 frame 放行
  if (request?.isTopFrame === false) return true;

  // 规则 3: 不是 xingzhijia-agent 场景放行
  if (!currentUri.includes('xingzhijia-agent')) return true;

  // 规则 4: javascript: 或 about:blank 放行
  if (requestUrl === 'about:blank' || requestUrl.startsWith('javascript:')) return true;

  // 规则 5: 相对路径放行
  if (!requestUrl.startsWith('//') && !/^[a-z][a-z0-9+.-]*:/i.test(requestUrl)) return true;

  // 规则 6: 相同 URL 放行
  if (currentUri.split('#')[0] === requestUrl.split('#')[0]) return true;

  // 规则 7: 外部链接 用 Linking.openURL 在外部浏览器打开
  Linking.openURL(requestUrl).catch(console.warn);
  return false;  // 阻止 WebView 内导航
};

用途 :安全防护 ,防止用户在 H5 里点恶意链接导致 WebView 跳到钓鱼网站。拦截后用外部浏览器打开,用户能看到地址栏。

⚠️ 项目里这个 prop 目前被注释了 (前面有 //),目前没有启用。


Q6:流程图对不对?injectedJavaScript 是不是传业务 API 数据?

A :你的发现完全正确!

injectedJavaScript 这个 prop ≠ 业务 API 数据回调。

业务 API 数据回调用的是:

  1. sendMessageToWebView(method, data, requestId)(自定义函数)
  2. 内部调用 webViewRef.current.injectJavaScript(jsCode)(实例方法)
  3. 注入的 JS 里 window.dispatchEvent('nativeMessage', {detail})
  4. H5 监听 nativeMessage 事件拿到数据

injectedJavaScript prop 注入的是 :BASE_URL isInXingzhijia 标记 viewport 禁用缩放(静态配置)。

修正文档时的核心澄清:

步骤 用什么 文档之前描述 修正后
step8 原生→H5 注入 JS injectJavaScript(歧义) webViewRef.current.injectJavaScript() 实例方法
初始化 静态配置 injectedJavaScript prop 明确为 prop 加实例方法

三、method 命名规则 + 完整开门案例

你提的核心问题

H5 给 RN 通信定义的方法是咋样的?RN 返回消息给 H5 又是怎样的方法命名?getToken / getUserInfo 是步骤 step4 的方法,还是步骤 step7 的 sendMessage 方法?

核心答案:method 名 = handler 名 = sendMessage 参数,三者必须一致

getToken 既是:

  • 步骤 step4 的 handler 名(handlers['getToken'])
  • 步骤 step7 的 sendMessage 第一参数(sendMessage('getToken', ...))
  • 步骤 step9 的 H5 响应 method 字段({method: 'getToken', ...})

三者必须同名,因为:

  • 步骤 step4:handlers['getToken'] 表示「H5 想调 getToken 这个能力」
  • 步骤 step7:sendMessage('getToken', ...) 表示「回传的数据 method 是 getToken」
  • 步骤 step9:H5 用 method 名字区分是哪个请求的响应

method 命名规范

类型 命名规则 例子
拉数据 查 getXxx getToken / getUserInfo / getAgentContext / getServicePhone
查列表 queryXxx / getUserXxx queryBuildings / getUserAllDoor
执行动作 verbXxx doorOpen / verifyDoorHealth / callPhone
跳转 goXxx goBack

H5 发起:

javascript 复制代码
{
  method: '方法名',       // 驼峰命名
  params: {...},         // 参数对象
  requestId: '唯一ID'    // 关联响应
}

RN 响应:

javascript 复制代码
ctx.sendMessage('方法名', result, requestId);
//                  ↑ 同一个方法名

四、完整案例:「帮我开一号楼大门」(3 轮 H5↔RN 通信)

这是 AI 智能管家最经典的场景,包含 3 个 handler 调用 + 4 次 H5↔RN 通信。

全流程时序图

业务后端 原生 handlers webViewHandlers WebView 大模型 智能体云服务 H5 聊天页 用户 业务后端 原生 handlers webViewHandlers WebView 大模型 智能体云服务 H5 聊天页 用户 #mermaid-svg-uLwxE4ExgEyF49BN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uLwxE4ExgEyF49BN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uLwxE4ExgEyF49BN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uLwxE4ExgEyF49BN .error-icon{fill:#552222;}#mermaid-svg-uLwxE4ExgEyF49BN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uLwxE4ExgEyF49BN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uLwxE4ExgEyF49BN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uLwxE4ExgEyF49BN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uLwxE4ExgEyF49BN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uLwxE4ExgEyF49BN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uLwxE4ExgEyF49BN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uLwxE4ExgEyF49BN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uLwxE4ExgEyF49BN .marker.cross{stroke:#333333;}#mermaid-svg-uLwxE4ExgEyF49BN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uLwxE4ExgEyF49BN p{margin:0;}#mermaid-svg-uLwxE4ExgEyF49BN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uLwxE4ExgEyF49BN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-uLwxE4ExgEyF49BN .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-uLwxE4ExgEyF49BN .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-uLwxE4ExgEyF49BN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-uLwxE4ExgEyF49BN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-uLwxE4ExgEyF49BN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-uLwxE4ExgEyF49BN .sequenceNumber{fill:white;}#mermaid-svg-uLwxE4ExgEyF49BN #sequencenumber{fill:#333;}#mermaid-svg-uLwxE4ExgEyF49BN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-uLwxE4ExgEyF49BN .messageText{fill:#333;stroke:none;}#mermaid-svg-uLwxE4ExgEyF49BN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uLwxE4ExgEyF49BN .labelText,#mermaid-svg-uLwxE4ExgEyF49BN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-uLwxE4ExgEyF49BN .loopText,#mermaid-svg-uLwxE4ExgEyF49BN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-uLwxE4ExgEyF49BN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-uLwxE4ExgEyF49BN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-uLwxE4ExgEyF49BN .noteText,#mermaid-svg-uLwxE4ExgEyF49BN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-uLwxE4ExgEyF49BN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uLwxE4ExgEyF49BN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uLwxE4ExgEyF49BN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uLwxE4ExgEyF49BN .actorPopupMenu{position:absolute;}#mermaid-svg-uLwxE4ExgEyF49BN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-uLwxE4ExgEyF49BN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uLwxE4ExgEyF49BN .actor-man circle,#mermaid-svg-uLwxE4ExgEyF49BN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-uLwxE4ExgEyF49BN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第 1 轮 拿楼宇信息 从 redux 直接读 不调 API 第 2 轮 拿门禁列表 第 3 轮 执行开门 帮我开一号楼大门1对话请求2理解意图3method getBuildingInfo4返回工具调用5step1 postMessage method getBuildingInfo requestId req-0016step2 onMessage7step3 step4 查 handlers.getBuildingInfo8step7 ctx.sendMessage getBuildingInfo buildingId 1 req-0019step8 webViewRef.injectJavaScript10step9 dispatchEvent nativeMessage11拿到 buildingId 112自动追问 buildingId 113理解14method getUserAllDoor params buildingId 115返回工具调用16step1 postMessage method getUserAllDoor params buildingId 1 requestId req-00217step2 onMessage18step3 step4 查 handlers.getUserAllDoor19step5 step6 调 api.getUserAllDoorNew 返回门禁列表20step7 ctx.sendMessage getUserAllDoor 门禁列表 req-00221step8 webViewRef.injectJavaScript22step9 dispatchEvent nativeMessage23拿到门禁列表24自动追问 deviceSerial abc25理解26method doorOpen params deviceSerial abc buildingId 127返回工具调用28step1 postMessage method doorOpen params req-00329step2 onMessage30step3 step4 查 handlers.doorOpen31step5 step6 调 api.doorOpen 返回 success true32step7 ctx.sendMessage doorOpen success true req-00333step8 webViewRef.injectJavaScript34step9 dispatchEvent nativeMessage35显示「已为您打开一号楼大门」36

关键代码对照

step1 H5 发请求(H5 端 SDK 调用)
javascript 复制代码
// 第一轮 拿楼宇
window.ReactNativeWebView.postMessage(JSON.stringify({
  method: 'getBuildingInfo',
  params: {},
  requestId: 'req-001'
}));

// 第二轮 拿门禁列表
window.ReactNativeWebView.postMessage(JSON.stringify({
  method: 'getUserAllDoor',
  params: {buildingId: 1, employeeNo: 'xxx'},
  requestId: 'req-002'
}));

// 第三轮 开门
window.ReactNativeWebView.postMessage(JSON.stringify({
  method: 'doorOpen',
  params: {
    deviceSerial: 'abc',
    buildingId: 1,
    employeeNo: 'xxx'
  },
  requestId: 'req-003'
}));
step4 + step7 原生 handler 实现(RN 项目代码)
javascript 复制代码
// src/utils/agentToolHandlers/doorHandlers.js

// handler 名 = method 名 = sendMessage 名(必须一致)
export async function getBuildingInfoHandler(params, requestId, context) {
  const {sendMessage} = context;
  // 从 redux 拿 不调 API
  const buildingId = context.building?.curId;
  sendMessage('getBuildingInfo', {buildingId}, requestId);
  //                ↑ 步骤 step7 调用 sendMessage
}

export async function getUserAllDoorHandler(params, requestId, context) {
  const {sendMessage} = context;
  const {buildingId, employeeNo} = params;
  // 调业务 API
  const result = await getUserAllDoorNew(buildingId, employeeNo);
  sendMessage('getUserAllDoor', result, requestId);
  //                ↑ 步骤 step7
}

export async function doorOpenHandler(params, requestId, context) {
  const {sendMessage} = context;
  const {deviceSerial, buildingId, employeeNo} = params;
  // 调开门 API
  const result = await doorOpen({deviceSerial, buildingId, employeeNo});
  sendMessage('doorOpen', result, requestId);
  //                ↑ 步骤 step7
}
step9 H5 接收响应(H5 端 SDK 内部)
javascript 复制代码
window.addEventListener('nativeMessage', (e) => {
  const {method, requestId, data} = e.detail;

  // method 区分是哪个请求的响应
  if (method === 'getBuildingInfo') {
    // 拿到 buildingId 继续下一步
  } else if (method === 'getUserAllDoor') {
    // 拿到门禁列表
  } else if (method === 'doorOpen') {
    // 开门成功 显示结果
  }
});

4 个观察点(最容易混的地方)

观察点 谁 啥时候调
method 名字一致性 H5 method = handler 名 = sendMessage 参数 全程保持一致
requestId 唯一性 每次新请求生成 H5 发起时
ctx.sendMessage 是步骤 step7 原生 handler 完成后调 handler 内部
sendMessageToWebView 是步骤 step8 ctx.sendMessage 内部调 WebView.js 内

核心口诀 :「method 名字串起 H5 RN H5 三步,requestId 串起一次会话,handler 名 = sendMessage 的 method 参数」


五、11 个工具速查表

工具名 作用 内部调什么
getCloudTalkUserInfo 拿云对讲用户信息 api.getCloudTalkUserInfo()
queryBuildings 拿楼栋列表 api.queryBuildingsNew()
getUserAllDoor 拿门禁列表 api.getUserAllDoorNew()
verifyDoorHealth 验证开门权限 api.verifyDoorHealth()
doorOpen 执行开门 api.doorOpen()
getBuildingInfo 拿当前楼宇 直接从 redux 取
getBindingStatus 拿房屋绑定状态 直接从 redux 取
getToken 拿登录 token storage.getToken()
getUserInfo 拿用户信息 直接从 redux 取
getAgentContext 拿 agent 上下文 从 redux 拼
getServicePhone 拿服务中心电话 直接从 redux 取
callPhone 拨打电话 Linking.openURL tel ...

六、5 个观察口诀

口诀 1:JSBridge 三要素

复制代码
onMessage 接收 + injectJavaScript 发送 + requestId 关联

口诀 2:method 三名一致

复制代码
handler 名 = H5 method = sendMessage 第一参数

口诀 3:业务数据回传路径

复制代码
sendMessageToWebView → webViewRef.injectJavaScript → dispatchEvent nativeMessage

口诀 4:3 个相似但不同

复制代码
injectedJavaScript 是 prop 初始化用
injectJavaScript() 是方法 动态调用
sendMessageToWebView 是自定义函数 封装了方法调用

口诀 5:method 命名规范

复制代码
查数据用 getXxx 查列表用 queryXxx 执行动作用 verbXxx 跳转用 goXxx

七、面试常见 5 问速答

Q1:什么是 JSBridge?

A :JSBridge 是 JavaScript 与原生代码之间的通信桥梁。在 RN WebView 里:

  • H5 → 原生:window.ReactNativeWebView.postMessage → WebView onMessage
  • 原生 → H5:webViewRef.injectJavaScript → 派发 CustomEvent nativeMessage

Q2:onMessage / injectedJavaScript / injectJavaScript() 三者关系?

名称 性质 用途
onMessage WebView prop 接收 H5 发来的消息
injectedJavaScript WebView prop 初始化时注入静态配置
injectJavaScript() WebView 实例方法 任何时候动态注入 JS 业务数据回传

Q3:getToken 是步骤几?

A :同时是 step4 和 step7:

  • step4:handlers['getToken'](handler 名)
  • step7:ctx.sendMessage('getToken', ...)(sendMessage 第一参数)
  • step9:H5 收到的响应 method 字段是 'getToken'

三者必须同名,这就是 JSBridge 的 method 约定。

Q4:method 命名有什么规范?

A:4 类命名:

  • 查数据:getXxx(getToken getUserInfo getAgentContext)
  • 查列表:queryXxx 或 getUserXxx(queryBuildings getUserAllDoor)
  • 执行动作:verbXxx(doorOpen callPhone)
  • 跳转:goXxx(goBack)

Q5:为什么需要 requestId?

A :关联异步请求和响应 。H5 可能同时发多个 postMessage,原生处理完不一定按顺序返回,用 requestId 让 H5 知道这个响应是对应哪个请求。


面试前 5 分钟 :看第一节(10 步机制图)+ 第四节(method 命名规则)+ 第七节(5 问速答),足够应付 80% 追问。

被追问细节:看第二节(6 个深度问题)+ 第四节(完整开门案例)。

相关推荐
挖掘狂人1 小时前
AI 一天写的代码多了 30%,软件却没多出一个
人工智能·ai编程
小玮看世界1 小时前
【推测文】AI陪伴赛道的下一程:合规倒逼、组织重构与“反迎合”变成新护城河
人工智能
for_ever_love__1 小时前
LangChain 批量调用与并发控制:batch、abatch 与速率限制
人工智能·langchain·异步·config·invoke·abatch
码流子1 小时前
高速场景视频接入:GB/T 28181 级联与 RTSP 取流,一条链路讲清楚
大数据·数据库·人工智能·物联网·音视频
卿卿的产品经理日记1 小时前
【AI产品经理实战】Day 37|拆开 JSON 与 EXE:数据都有格式
人工智能·笔记·python·json·产品经理
yunni81 小时前
政企AI落地避坑:敏感数据不出域,安全需求优先本地化部署
人工智能·安全
广东帝工1 小时前
如何利用大数据和人工智能技术实现智慧航道
大数据·人工智能
有月牙1 小时前
2026企业AI办公工具选型指南:企业办公AI怎么评估与落地
大数据·人工智能
柯南46681 小时前
【AI工程师精讲】09:LoRA:几十兆的文件,凭什么能改变一个大模型
人工智能·ai编程