3. 创建第一个原生模块
好,咱们直接动手。这一章我会带你走一遍完整的流程——从项目结构长什么样,到模块怎么注册,最后跑通一个 Hello World。我当年第一次写原生模块时,光找文件位置就花了半小时……嗯,这些坑我会帮你避开。
3.1 项目结构长什么样?
先别急着写代码。你得先知道文件都放哪。以 React Native 0.70+ 为例,一个标准的原生模块项目结构是这样的:
MyAwesomeModule/
├── android/
│ ├── src/main/java/com/myawesomemodule/
│ │ ├── MyAwesomeModule.java // 模块实现
│ │ ├── MyAwesomePackage.java // 模块注册
│ │ └── MyAwesomeModuleManager.java // 可选,管理类
│ ├── build.gradle
│ └── src/main/AndroidManifest.xml
├── ios/
│ ├── MyAwesomeModule.m // 模块实现(ObjC)
│ ├── MyAwesomeModule.swift // 或 Swift
│ └── MyAwesomeModuleBridge.m // 桥接头文件
├── index.js // JS 入口
├── package.json
└── README.md
我个人习惯把 Java 文件放在 com.xxx.xxx 包路径下,这样后续维护时一眼就能找到。你想想看,如果所有文件都堆在根目录,那找起来多痛苦。
核心要点:
- android/ 目录下放 Android 原生代码
- ios/ 目录下放 iOS 原生代码
- index.js 是 JS 端的入口,负责导出模块
- package.json 定义模块名称、版本、依赖
3.2 模块注册流程——到底怎么连起来的?
很多新手会问:我写了个 Java 类,JS 怎么就能调用了?
说白了,中间有个「注册」环节。React Native 在启动时会扫描所有注册的原生模块,然后通过 Bridge 把方法暴露给 JS。流程大致如下:
- 创建模块类:继承
ReactContextBaseJavaModule(Android)或实现RCTBridgeModule协议(iOS) - 创建 Package 类:实现
ReactPackage接口,把模块注册进去 - 在 MainApplication 中注册 Package:把 Package 加到
getPackages()列表里 - JS 端导入:通过
NativeModules或TurboModuleRegistry获取模块
我在项目中遇到过一个问题:注册了模块但 JS 端一直报 undefined。查了半天,原来是 Package 类里忘了把模块加进 createNativeModules() 方法里。嗯,这种低级错误最容易犯。
小技巧: 如果你用 TypeScript,建议给模块写一个类型声明文件(.d.ts),这样调用时能有代码提示,省得每次都要翻文档。
3.3 Hello World 模块实现(Android 篇)
好,咱们直接写代码。先看 Android 端。
3.3.1 创建模块类
// MyHelloModule.java
package com.myhellomodule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
public class MyHelloModule extends ReactContextBaseJavaModule {
public MyHelloModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "MyHelloModule"; // 这个名称就是 JS 端调用的模块名
}
@ReactMethod
public void sayHello(String name, Promise promise) {
try {
String result = "Hello, " + name + "! Welcome to native module.";
promise.resolve(result);
} catch (Exception e) {
promise.reject("HELLO_ERROR", e.getMessage());
}
}
}
注意 getName() 返回的字符串,它就是 JS 端 NativeModules.MyHelloModule 里的那个名字。大小写敏感,别写错了。
3.3.2 创建 Package 类
// MyHelloPackage.java
package com.myhellomodule;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyHelloPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyHelloModule(reactContext)); // 注册模块
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList(); // 没有 UI 组件,返回空列表
}
}
3.3.3 在 MainApplication 中注册
// MainApplication.java
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyHelloPackage()); // 加上这一行
return packages;
}
注意: 如果你用的是 React Native 0.60+ 的自动链接(Autolinking),其实不需要手动改 MainApplication。但为了教学清晰,我建议你手动注册一次,理解流程后再用自动链接。
3.4 Hello World 模块实现(iOS 篇)
iOS 端稍微有点不同。我们用 Objective-C 来写,因为 Swift 还需要桥接头文件,步骤多一点。
3.4.1 创建模块实现文件
// MyHelloModule.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MyHelloModule, NSObject)
RCT_EXTERN_METHOD(sayHello:(NSString *)name
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end
等等,上面这个是声明文件。真正的实现要写在 .swift 或 .mm 文件里。我个人习惯用 Swift,因为语法更现代。
// MyHelloModule.swift
import Foundation
@objc(MyHelloModule)
class MyHelloModule: NSObject {
@objc
func sayHello(_ name: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let result = "Hello, \(name)! Welcome to native module."
resolve(result)
}
}
3.4.2 创建桥接头文件
如果你用 Swift,还需要一个 MyHelloModule-Bridging-Header.h 文件:
// MyHelloModule-Bridging-Header.h
#import <React/RCTBridgeModule.h>
然后在 Xcode 的 Build Settings 里设置 Objective-C Bridging Header 路径指向这个文件。
避坑指南: 我曾经因为忘记设置桥接头文件,导致编译时一直报 RCTPromiseResolveBlock 未定义。嗯,这种问题查起来特别费时间,所以建议你一开始就配好。
3.5 JS 端调用
两端都写好了,JS 端怎么调?很简单:
// index.js
import { NativeModules } from 'react-native';
const { MyHelloModule } = NativeModules;
// 调用原生方法
MyHelloModule.sayHello('React Native')
.then(result => {
console.log(result); // 输出: Hello, React Native! Welcome to native module.
})
.catch(error => {
console.error(error);
});
如果你用 TypeScript,可以加个类型声明:
// types.ts
export interface MyHelloModuleInterface {
sayHello(name: string): Promise<string>;
}
declare module 'react-native' {
interface NativeModulesStatic {
MyHelloModule: MyHelloModuleInterface;
}
}
3.6 常见问题与排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
NativeModules.MyHelloModule 为 undefined |
模块未注册或名称不匹配 | 检查 getName() 返回的名称,以及 Package 是否已注册 |
调用方法时返回 Promise 未 resolve |
原生方法未调用 resolve 或 reject |
确保所有分支都调用了 Promise 回调 |
iOS 编译报错 RCTBridgeModule 未找到 |
缺少桥接头文件或未导入框架 | 检查 Bridging Header 配置,确保 #import <React/RCTBridgeModule.h> |
Android 编译报错 ReactContextBaseJavaModule 找不到 |
缺少 React Native 依赖 | 检查 build.gradle 是否添加了 implementation 'com.facebook.react:react-native:+' |
总结一下:
- 项目结构要清晰,android/ 和 ios/ 分开
- 模块注册三步走:创建模块类 → 创建 Package → 注册到应用
- JS 端通过
NativeModules调用,方法名和参数要一一对应 - 遇到问题先检查名称是否匹配,再检查注册流程