React Native 0.86 新架构实战:Fabric 原生组件开发的 4 个致命坑

0 阅读4分钟

React Native 0.86 新架构实战:Fabric 原生组件开发的 4 个致命坑

用亲身踩坑经历,帮你少走弯路。


背景

最近在 React Native 0.86 新架构(Fabric + TurboModule)下开发一个带事件推送的 Fabric 原生组件 NativeColoredView,功能本身很简单:JS 侧通过 isActive 控制原生侧定时器,原生侧每秒通过 onValueChange 事件推送递增值到 JS。

从写 JS Spec → 跑 Codegen → 编译原生代码 → 运行,踩了 4 个坑,每一个都是"编译报错能看懂、但不知道为什么会这样"的类型。本文逐一复盘。


坑 1:Codegen 不认你手写的泛型事件类型

现象

Spec 文件里定义了一个本地泛型:

// ❌ 错误写法
type DirectEvent<T> = { readonly nativeEvent: T };

export interface NativeProps extends ViewProps {
  onValueChange?: DirectEvent<{
    readonly value: Double;
    readonly timestamp: Double;
  }>;
}

执行 pod install 触发的 codegen 报错:

[Codegen] Error: Unknown prop type for "nativeEvent": "T"

原因

React Native 的 codegen 是一个静态 AST 分析器,不是完整的 TypeScript 编译器。它不支持泛型类型别名展开,遇到 DirectEvent<{...}> 时,T 无法被解析成具体类型。

解决

react-native/Libraries/Types/CodegenTypes 导入官方类型:

// ✅ 正确写法
import type { DirectEventHandler, Double } from 'react-native/Libraries/Types/CodegenTypes';

export interface NativeProps extends ViewProps {
  onValueChange?: DirectEventHandler<{
    readonly value: Double;
    readonly timestamp: Double;
  }>;
}

DirectEventHandler 是 codegen 内置能识别的事件回调类型,它能正确展开内联的 nativeEvent 结构。


坑 2:typeof 在 Objective-C++ 中编译不过

现象

.mm 文件中用经典的 weak-strong dance:

// ❌ 报错: Expected unqualified-id
__weak typeof(self) weakSelf = self;
self.valueTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    __strong typeof(weakSelf) self = weakSelf;
    // ...
}];

原因

typeof 是 GNU C 扩展,不是 C++ 标准关键字。React Native 新架构的 Fabric 组件以 Objective-C++ (.mm) 编译,编译器使用严格 C++ 标准,typeof 不被识别。

可以用 __typeof__(双下划线编译器内置),但依然不够干净。

解决

直接使用具体类名,避免依赖编译器扩展:

// ✅ 正确写法
__weak NativeColoredView *weakSelf = self;
self.valueTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NativeColoredView *strongSelf = weakSelf;
    if (!strongSelf) {
        [timer invalidate];
        return;
    }
    [strongSelf emitValueChange];
}];

另外用 strongSelf 替代 block 内 self 遮蔽,不会引起 self 的循环引用警告误报,更安全。


坑 3:prepareForReuse vs prepareForRecycle

现象

// ❌ No visible @interface for 'RCTViewComponentView' declares the selector 'prepareForReuse'
- (void)prepareForReuse {
    [self stopValueTimer];
    self.currentValue = 0.0;
    [super prepareForReuse];
}

编译报错:RCTViewComponentView 没有 prepareForReuse 方法。

原因

架构基类复用清理方法
旧架构(Paper)RCTViewprepareForReuse
新架构(Fabric)RCTViewComponentViewprepareForRecycle

Fabric 换了基类,方法名也换了。经验来自旧架构的惯性写法直接套到新架构就会踩坑。

解决

// ✅ Fabric 正确写法
- (void)prepareForRecycle {
    [self stopValueTimer];
    self.currentValue = 0.0;
    [super prepareForRecycle];
}

坑 4:首次挂载时 oldProps 解引用崩溃

现象

RN 页面一打开就 EXC_BAD_ACCESS 崩溃,堆栈指向:

const auto &oldViewProps = *std::static_pointer_cast<const NativeColoredViewProps>(oldProps);

// crash ↓
if (newViewProps.isActive != oldViewProps.isActive) { ... }

原因

Fabric 在首次挂载组件时,updateProps:oldProps: 传入的 oldProps shared_ptr 可能为 null。这是 Fabric 与旧架构的一个重要语义差异:

  • 旧架构:首次 mount 时也有一个默认 props 对象
  • Fabric:首次 mount 时 oldProps 为空 shared_ptr,表示"之前没有 props"

代码中对 oldProps 直接解引用*static_pointer_cast<...>(oldProps)),碰到 null shared_ptr 导致 EXC_BAD_ACCESS。

解决

不要对 oldProps 直接解引用,改用 shared_ptr 的值语义 + 防御判断:

// ✅ 安全写法
- (void)updateProps:(const Props::Shared &)props
           oldProps:(const Props::Shared &)oldProps {
  const auto &newViewProps = *std::static_pointer_cast<const NativeColoredViewProps>(props);
  const auto oldViewProps = std::static_pointer_cast<const NativeColoredViewProps>(oldProps);

  [super updateProps:props oldProps:oldProps];

  // ... color, cornerRadius 处理 ...

  // 防御:oldProps 在首次挂载时可能为 null
  BOOL wasActive = oldViewProps ? oldViewProps->isActive : false;
  if (newViewProps.isActive != wasActive) {
    if (newViewProps.isActive) {
      [self startValueTimer];
    } else {
      [self stopValueTimer];
    }
  }
}

关键变化:oldViewProps引用改为(shared_ptr),通过 shared_ptr 的 bool 转换操作符判断是否为空,再安全访问。


总结

#现象根因解法
1codegen 报 Unknown prop type泛型别名不被 codegen 解析用官方 DirectEventHandler<T>
2编译报 Expected unqualified-idObjective-C++ 不认 typeof用具体类名,不用扩展语法
3No visible @interface新旧架构 API 名称不同Fabric 用 prepareForRecycle
4首次打开页面 EXC_BAD_ACCESSFabric 首次挂载 oldProps 为 nullshared_ptr 解引用前做 null 检查

这四个坑的共同特征:都源于新架构与旧架构/常规写法的细微差异。React Native 新架构带来了更好的性能,但 API 契约和编译约束也更加严格,写 Fabric 组件时不能照搬旧架构的经验。


如果你也在 React Native 新架构中踩过坑,欢迎评论区补充交流。