React 学习记录
useState useReducer 和useContext 分别作为状态管理的用法
在React中,useState
、useReducer
和 useContext
都是用于状态管理的Hook,但它们适用于不同的场景和复杂度。以下是它们的详细用法和区别:
useState
适用场景 :简单的局部状态管理(单个组件或少量状态)。特点:
- 直接返回状态值和更新函数。
- 适合简单值(如数字、字符串、布尔值)或简单对象。
用法:
jsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // 初始值为0
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
useReducer
适用场景 :复杂状态逻辑(多状态、依赖前一个状态、状态更新逻辑复杂)。特点:
- 类似于Redux,通过
reducer
函数管理状态。 - 适合对象或嵌套状态,尤其是需要多个操作(如增删改查)。
用法:
jsx
import React, { useReducer } from 'react';
// 定义reducer函数
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 }); // 初始状态为 { count: 0 }
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
useContext
适用场景 :跨组件层级共享状态(避免props逐层传递)。特点:
- 需要配合
createContext
使用,解决"prop drilling"问题。 - 通常与
useState
或useReducer
结合,实现全局状态管理。
用法:
jsx
import React, { useContext, createContext, useState } from 'react';
// 1. 创建Context
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light'); // 状态提升到顶层
return (
// 2. 通过Provider传递状态
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
// 3. 子组件通过useContext获取状态
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
style={{ background: theme === 'light' ? '#fff' : '#333' }}
>
Toggle Theme
</button>
);
}
三者的结合使用
在实际项目中,通常会组合使用它们:
-
useReducer
+useContext
:实现全局状态管理(类似Redux)。jsx// 全局状态管理示例 const AppContext = createContext(); function App() { const [state, dispatch] = useReducer(reducer, initialState); return ( <AppContext.Provider value={{ state, dispatch }}> <ChildComponent /> </AppContext.Provider> ); } function ChildComponent() { const { state, dispatch } = useContext(AppContext); // 使用state和dispatch操作全局状态 }
如何选择?
Hook | 适用场景 | 复杂度 |
---|---|---|
useState |
简单局部状态 | 低 |
useReducer |
复杂状态逻辑(多操作、依赖前状态) | 中高 |
useContext |
跨组件共享状态 | 需结合其他Hook |
根据项目需求灵活选择:
- 表单输入、开关切换 →
useState
- 购物车、复杂表单 →
useReducer
- 主题、用户认证 →
useContext
+useState
/useReducer
1. useCallback 和 useMemo 区别
useCallback 和 useMemo 都是为了避免函数在每次渲染时重新创建,从而提高性能。但是它们在实现上有一些区别。 useCallback: 用于缓存函数,避免在每次渲染时生成新的函数实例。 useMemo: 用于缓存计算结果,避免在每次渲染时重新计算复杂的值。
ts
// 在 React 内部的简化实现
function useCallback(fn, dependencies) {
return useMemo(() => fn, dependencies);
}
2. useEffect 和 useLayoutEffect 区别
- 执行时机
useEffect:
在浏览器完成布局和绘制之后异步调用。useLayoutEffect:
在浏览器完成布局但尚未绘制之前同步调用。 2. 使用场景useEffect:
适用于大多数副作用操作,如数据获取、订阅事件或手动更改 DOM。 不会阻塞浏览器渲染,因此适合不需要立即更新 DOM 的场景。useLayoutEffect:
适用于需要在 DOM 更新后立即读取并同步更新的操作。 可以防止视觉抖动(例如测量 DOM 尺寸后调整样式)。 由于是同步执行,可能会降低性能,因此应谨慎使用。 3. 性能影响useEffect:
异步执行,不会阻塞页面渲染,性能更优。useLayoutEffect:
同步执行,可能阻塞页面渲染,影响性能。<br><br>
总结 如果你的副作用操作不需要与 DOM 布局同步,优先使用 useEffect。 如果需要在 DOM 更新后立即进行测量或调整,使用 useLayoutEffect。
3. 原生项目集成Rn
公网tv项目集成rn流程:
- 本地引用react/reanmition/gensture 相关aar
- 添加交互代码
- assets资源文件下放置 本地的index.android.bundle文件, 无服务器资源时默认加载此文件
4. 状态管理实践: (中小型项目mobx 和 大型项目redux)
mobx 和 redux 对比 MobX: 基于细粒度的依赖追踪,只有相关的组件会被重新渲染。 在频繁状态更新的场景下性能较好。
Redux: 整个状态树可能被订阅者监听,可能导致不必要的重新渲染。 可以通过 reselect 或 immer 等库优化性能。
ts
// MobX代码实战
/**
* 1.第一步 注册观察者组件
* 将 Comments 组件注册为 MobX 的观察者组件。
* 当 UserStore 中的 observable 状态(如 homeList 或 refreshing)发生变化时,自动触发组件重新渲染。
* 提升开发效率和性能优化。
*/
import React, {FC} from 'react';
import {View, Text, StyleSheet, Image, TextInput} from 'react-native';
import ArticleDetailStore from '../../stores/ArticleDetailStore';
import {observer} from 'mobx-react';
import UserStore from '../../stores/UserStore';
import icon_default_avatar from '../../assets/icon_default_avatar.png';
import CommentList from './CommentList';
type PropsType = {};
// 注意这里的observer
const Comments: FC<PropsType> = observer(() => {
const {comments} = ArticleDetailStore.detail;
const {avatar} = UserStore.userInfo;
console.log('Comments avatar', avatar);
const count = comments?.length;
return (
<>
<Text style={styles.commentCountTxt}>
{count ? `共${count}条评论` : '暂无评论'}
</Text>
<View style={styles.inputLayout}>
<Image
style={styles.avatarImg}
source={avatar ? {uri: avatar} : icon_default_avatar}
/>
<TextInput
style={styles.commentInput}
placeholder="说点什么吧,万一火了呢~"
placeholderTextColor="#bbb"
/>
</View>
{!!count && (
<View style={styles.commentsContainer}>
<CommentList comments={comments} />
</View>
)}
</>
);
});
//2. 注册被观察的对象-----------------------------------------------------
import {USER_INFO_KEY} from '../constants/common';
import {save} from '../utils/Storage';
import {request} from '../utils/request';
import {flow, makeAutoObservable, observable, action} from 'mobx';
import Loading from '../components/widget/Loading';
/**
* user store 全局存储
*/
class UserStore {
userInfo: UserInfo = {} as UserInfo;
constructor() {
// requestLogin 方法中,userInfo 被赋值为登录接口返回的用户数据:this.userInfo = data;。
//由于 userInfo 是 observable,这一赋值操作会触发所有依赖它的组件重新渲染。
//requestLogin 和 setUserInfo 被标记为 action,确保它们在修改 userInfo 时能够触发视图更新。
makeAutoObservable(this, {
userInfo: observable,
requestLogin: action,
setUserInfo: action,
});
}
// requestLogin = async (
// phone: string,
// password: string,
// callback?: (success: boolean) => void,
// ) => {
// try {
// const params = {
// name: phone,
// pwd: password,
// };
// const {data} = await request('login', params);
// if (data) {
// this.useInfo = data;
// callback?.(true);
// } else {
// callback?.(false);
// }
// } catch (error) {
// console.log(error);
// callback?.(false);
// }
// };
/**
* 这是一个生成器函数(flow),用于处理异步操作。
* 在登录成功后,this.userInfo 被赋值为新的用户数据。
* 由于 userInfo 是 observable,这一赋值操作会触发所有依赖它的组件重新渲染。
* 因为 requestLogin 是 action,所以当它修改 userInfo 时,MobX 会自动追踪这*一变化,并通知所有依赖 userInfo 的视图重新渲染。
*/
requestLogin = flow(function* (
this: UserStore, // 方便使用this指针
phone: string,
password: string,
callback?: (success: boolean) => void,
) {
Loading.show();
try {
const params = {
name: phone,
pwd: password,
};
console.log(JSON.stringify(params));
// 使用yield关键字,表明这是一个生成器函数中的异步操作,类似于await
const {data} = yield request('login', params);
if (data) {
// Content-Type: application/json 后端返回的是一个javascript对象,所以不需要再转成json
this.userInfo = data;
save(USER_INFO_KEY, JSON.stringify(this.userInfo));
callback?.(true);
} else {
callback?.(false);
}
} catch (error) {
console.log(error);
callback?.(false);
} finally {
Loading.hide();
}
});
setUserInfo = (userInfo: UserInfo) => {
this.userInfo = userInfo;
};
}
/**
* ESM单例导出
*/
export default new UserStore();
5. Axios 的默认解析行为
javascript
✅ 自动解析为对象的情况
如果后端返回的响应头包含 Content-Type: application/json,Axios 会自动将响应体的 JSON 字符串解析为 JavaScript 对象。
此时 data 是对象,无需手动解析。
❌ 保持为字符串的情况
如果后端返回的响应头是 Content-Type: text/plain 或其他非 JSON 类型(如 text/html),即使响应体是 JSON 字符串,Axios 也不会自动解析。
此时 data 是字符串,需手动调用 JSON.parse(data)。
6. React Native 内置的react-native-animated 库 和 社区的react-native-reanimated 库区别是什么?
1. 核心机制对比
特性 | 内置 Animated |
react-native-reanimated |
---|---|---|
线程模型 | 部分动画依赖 JS 线程(即使开启 useNativeDriver ) |
动画逻辑完全运行在UI 线程,与 JS 线程解耦 |
性能 | 简单动画流畅,复杂动画易卡顿 | 支持 60 FPS 复杂动画,性能更优 |
手势支持 | 需结合 PanResponder ,功能有限 |
深度集成 react-native-gesture-handler ,支持精细化手势交互 |
动画类型 | 基础动画(如 timing 、spring ) |
支持复杂动画(物理动画、路径动画、条件动画等) |
代码执行位置 | 动画配置在 JS 线程,部分逻辑在 UI 线程 | 动画逻辑完全在 UI 线程执行 |
2. 功能差异
内置 Animated
的限制
- 功能局限 :
- 无法在动画过程中动态修改插值范围(如根据滚动位置实时调整动画参数)。
- 不支持同步多个动画的复杂逻辑(如动画 A 结束后触发动画 B,且传递中间状态)。
- 手势交互 :
- 手势处理需依赖
PanResponder
,代码冗余且响应延迟。
- 手势处理需依赖
- 调试困难 :
- 动画逻辑与 JS 业务代码混合,调试复杂。
react-native-reanimated
的优势
-
声明式动画 :
javascript// 示例:动态跟随手势的动画 const offset = useSharedValue(0); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateX: offset.value }], })); // 手势处理 const panGesture = Gesture.Pan() .onUpdate((e) => { offset.value = e.translationX; });
-
复杂动画支持 :
- 支持动画的 条件判断 、循环 、物理模拟(如弹簧阻尼)。
- 可编写 自定义动画节点 (通过
createAnimatedComponent
扩展)。
-
同步与编排 :
- 使用
withSequence
、withDelay
、withRepeat
等 API 编排动画序列。
- 使用
3. 性能对比
内置 Animated
的瓶颈
- JS 线程压力 :复杂动画的计算和事件处理(如
onScroll
)会阻塞 JS 线程,导致帧率下降。 - 序列化开销: 动画参数需通过桥接(Bridge)序列化传递到原生端,存在延迟。
react-native-reanimated
的优化
-
零桥接调用 :动画逻辑通过
worklet
在 UI 线程直接执行,无需跨线程通信。javascript// worklet 函数在 UI 线程运行 const updatePosition = (position) => { 'worklet'; position.value = 100; };
-
高效更新 : 使用
SharedValue
直接在 UI 线程更新动画值,无序列化开销。
4. 开发体验
内置 Animated
- 优点 :
- 开箱即用,无需额外安装。
- 适合简单动画(如渐变、位移)。
- 缺点 :
- 复杂动画代码冗长,维护困难。
- 调试工具支持有限。
react-native-reanimated
- 优点 :
- 提供 React DevTools 插件,可视化调试动画状态。
- 支持 Typescript 强类型,减少运行时错误。
- 缺点 :
- 学习曲线陡峭(需理解
worklet
、SharedValue
等概念)。 - 需额外配置(如 Babel 插件)。
- 学习曲线陡峭(需理解
5. 适用场景
场景 | 推荐库 | 原因 |
---|---|---|
简单过渡动画(如按钮点击效果) | 内置 Animated |
快速实现,无需复杂配置 |
手势驱动动画(如拖拽、缩放) | react-native-reanimated |
高性能手势处理,流畅交互 |
复杂序列动画(如联动物理效果) | react-native-reanimated |
强大的动画编排能力 |
高性能滚动动画(如视差效果) | react-native-reanimated |
UI 线程运行动画,避免滚动卡顿 |
6. 迁移建议
- 从
Animated
迁移到Reanimated
:- 替换
Animated.Value
为SharedValue
。 - 使用
useAnimatedStyle
替代Animated.createAnimatedComponent
。 - 手势逻辑迁移到
react-native-gesture-handler
。
- 替换
- 保留
Animated
的情况 :- 项目已稳定使用简单动画,无性能问题。
- 无复杂交互需求,避免引入额外依赖。
总结
- 内置
Animated
:轻量、易上手,适合基础动画需求。 react-native-reanimated
:功能强大、性能卓越,是复杂交互和高要求动画的首选。- 决策关键 :根据项目对 性能、复杂度、维护成本 的需求权衡选择。`
7. React 的reanimated 完全在ui线程中执行不会对ui线程造成卡顿吗
React Native 的 Reanimated 库通过将动画逻辑移至 UI 线程(或原生线程)来优化性能,但它并不会直接导致 UI 线程卡顿,原因如下:
- UI 线程与 JavaScript 线程的分离 - 传统动画的瓶颈 :React Native 默认的
Animated
API 在 JavaScript 线程计算动画值,并通过桥接(Bridge)传递到 UI 线程更新视图。如果 JS 线程被阻塞(如复杂计算、网络请求),动画会卡顿。- Reanimated 的改进 :Reanimated 直接将动画逻辑(如插值、手势处理)移到 UI 线程(或通过 Worklets 在原生线程执行),完全绕过 JS 线程和桥接,从而避免 JS 线程的阻塞问题。
- 高效的 Worklets 机制 Reanimated 2.0 引入了 Worklets ,允许开发者编写运行在 UI 线程的轻量级函数:
- 轻量执行:Worklets 是经过优化的原生代码片段(通过 JSI 直接调用),避免了 JS 线程的上下文切换开销。
- 同步更新:动画值的计算和视图更新在同一线程(UI 线程)完成,无需跨线程通信,减少了延迟。
- 避免阻塞:Worklets 设计为短小高效的任务,不会执行复杂逻辑(如网络请求),确保 UI 线程始终响应。
- 对 UI 线程的优化 - 帧率对齐 :Reanimated 的动画逻辑与屏幕刷新率(60Hz/120Hz)同步,确保更新仅在每帧的 VSync 信号期间执行,避免不必要的计算。
- 批量更新:多个动画值的变化会合并到一次 UI 更新中,减少重复渲染。
- 原生驱动:复杂动画(如旋转、透明度)通过原生层(如 OpenGL、Core Animation)实现,比 JS 驱动的动画更高效。
- **何时仍可能卡顿?**Reanimated 虽然优化了动画性能,但以下情况仍可能导致 UI 线程压力:
- 过多的并发动画:同时运行大量复杂动画(如数百个视图的粒子效果)可能超出 UI 线程的计算能力。
- 低效的 Worklets:在 Worklets 中执行复杂计算(如大数据处理)会阻塞 UI 线程。
- 主线程竞争:如果其他原生模块(如视频播放、大量布局计算)占用 UI 线程,动画可能受到影响。
- 最佳实践
- 简化动画逻辑:在 Worklets 中只做与动画相关的计算,避免复杂操作。
- 使用硬件加速 :优先使用
transform
和opacity
等属性(可通过 GPU 加速)。 - 性能分析 :用 React Native Profiler 或
react-native-reanimated-profiler
监控 UI 线程的帧率。
结论:
Reanimated 通过将动画逻辑移至 UI 线程并优化执行机制,显著降低了卡顿风险,但开发者仍需遵循性能最佳实践。在绝大多数场景下,Reanimated 的 UI 线程执行策略是高效且安全的,只有在极端情况下(如极大量动画或错误使用 Worklets)才可能导致卡顿。
8. Worklets 核心原理
React Native Reanimated 的 Worklets 机制 是 Reanimated 2.0 的核心创新,它允许将 JavaScript 代码片段(函数)直接运行在 UI 线程(或原生线程)上,从而彻底绕过 JavaScript 线程的阻塞问题。以下是它的核心原理和实现细节:
1、Worklet 的核心原理
1. 代码的跨线程执行
- 传统 JS 函数:运行在 JS 线程,无法直接操作 UI。
- Worklet 函数 :
-
通过 Reanimated 的 Babel 插件或转译工具,将 JavaScript 函数标记为 Worklet。
-
Worklet 代码会被提前编译为原生代码(iOS 的 Objective-C/Swift、Android 的 Java/Kotlin),或通过 JavaScript Interface (JSI) 直接运行在 UI 线程。
-
示例 :在
useAnimatedStyle
中定义的函数会被自动识别为 Worklet:javascriptconst animatedStyle = useAnimatedStyle(() => { // 此函数会在 UI 线程执行! return { opacity: scrollOffset.value / 100, }; });
-
2. 基于 JSI 的零桥接通信
- JSI(JavaScript Interface) :
- Reanimated 2.0 使用 JSI(React Native 的底层通信层)替代旧桥接(Bridge)。
- JSI 允许 JS 线程和 UI 线程直接共享内存,无需序列化数据。
- Worklet 的调用 :
- Worklet 函数通过 JSI 在 UI 线程注册,JS 线程可以直接通过同步调用触发 UI 线程的 Worklet 执行,无通信延迟。
3. 上下文隔离与轻量化
- 轻量化的执行环境 :
Worklet 函数运行在一个精简的 JavaScript 环境中
,仅支持有限的 API(如数学计算、访问动画值)。- 禁止复杂操作:无法在 Worklet 中执行网络请求、访问 React 状态等操作,确保 UI 线程不被阻塞。
- 闭包与变量捕获 :
- Worklet 可以安全地捕获外部的动画值(如
sharedValue
),但普通 React 状态需要通过useDerivedValue
转换为动画值后才能使用。
- Worklet 可以安全地捕获外部的动画值(如
2、Worklets 的执行流程
-
定义阶段:
- 开发者编写一个 JavaScript 函数,并用
'worklet'
指令标记(或通过 Reanimated 的 Hook 自动识别)。 - Reanimated 的转译工具会将此函数编译为原生代码或 JSI 兼容格式。
- 开发者编写一个 JavaScript 函数,并用
-
注册阶段:
- Worklet 函数通过 JSI 注册到 UI 线程,生成一个原生可调用的函数引用。
-
触发阶段:
- 当动画值(如
sharedValue
)变化时,UI 线程直接调用已注册的 Worklet,无需通知 JS 线程。 - Worklet 在 UI 线程计算新的视图属性(如位置、透明度)。
- 当动画值(如
-
更新阶段:
- 计算结果直接应用于原生视图,无需跨线程通信。
3、Worklets 的关键优势
-
零桥接延迟:
- 动画逻辑完全在 UI 线程执行,无需等待 JS 线程响应。
-
同步更新:
- 动画值的修改和视图更新在同一线程完成,避免帧丢失。
-
高效内存管理:
- 通过 JSI 直接操作共享内存,避免数据序列化(如 JSON 转换)的开销。
4、代码示例
javascript
import { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
function MyComponent() {
const offset = useSharedValue(0);
// 使用 useAnimatedStyle 定义 Worklet
const animatedStyle = useAnimatedStyle(() => {
// 此函数在 UI 线程执行!
return {
transform: [{ translateX: offset.value * 2 }],
};
});
return <Animated.View style={animatedStyle} />;
}
5、注意事项
-
Worklet 的限制:
- 不能执行异步操作(如
fetch
、setTimeout
)。 - 不能访问 React 状态(需通过
sharedValue
传递)。 - 避免复杂计算(如大数据循环),否则会阻塞 UI 线程。
- 不能执行异步操作(如
-
调试工具:
- 使用
console.log
在 Worklet 中打印日志时,日志会输出到原生控制台(如 Xcode/Android Studio)。
- 使用
总结
Worklets 机制通过将 JavaScript 逻辑直接植入 UI 线程,并利用 JSI 实现高效通信,彻底解决了 React Native 动画的跨线程瓶颈。它的设计核心是轻量化、同步化和零桥接,使得复杂动画可以流畅运行,同时保持开发者友好的 API 抽象。
8. React Hook(如 useState)状态变更触发页面刷新的核心原理
React Hook(如 useState)状态变更触发页面刷新的核心原理,是 React 的 响应式更新机制 和 虚拟 DOM 协调(Reconciliation) 的结合。以下是详细的流程解析: React 的更新调度:
React 内部通过 Fiber 架构 管理组件树:
标记组件为"需要更新":
React 会为当前组件对应的 Fiber 节点打上更新标记(Update)。
调度更新任务:
更新任务会被加入 React 的调度队列(Scheduler),根据优先级(如用户交互、动画)决定何时执行。
虚拟 DOM 的协调(Reconciliation)
React 对比新旧虚拟 DOM,找出差异:
Diff 算法:
React 会逐层比较新旧虚拟 DOM 节点,识别哪些部分需要更新。
若节点类型或 key 不同,直接销毁旧节点,创建新节点。
若节点相同但属性变化,仅更新对应属性。
最小化 DOM 操作:
React 最终生成一个 更新指令集合(如 updateTextContent、setAttribute),而非直接替换整个 DOM。
提交到真实 DOM
React 将协调后的差异提交到真实 DOM:
同步更新阶段:
React 一次性执行所有 DOM 更新操作,避免中间状态闪烁。
触发浏览器重绘:
DOM 更新完成后,浏览器重新计算布局(Layout)和绘制(Paint),页面显示最新状态。
9. React Native 性能优化
React Native 性能优化需要从多个维度综合考虑,以下是一份系统化的优化方案:
1. 渲染优化
-
列表性能:
- 使用
FlatList
或SectionList
替代ScrollView
,利用懒加载机制。 - 配置
initialNumToRender
(初始渲染数量)和windowSize
(渲染窗口大小),减少内存占用。 - 为列表项添加唯一且稳定的
key
,避免重复渲染。 - 复杂列表项使用
React.memo
或PureComponent
避免无效重渲染。
- 使用
-
组件结构:
- 减少组件嵌套层级,避免过度使用
View
。 - 使用
Fragment
替代不必要的容器。 - 避免在
render()
中动态创建组件或绑定函数(如() => {}
),改用useCallback
或类组件的实例方法。
- 减少组件嵌套层级,避免过度使用
2. JavaScript 与原生通信优化
-
动画优化:
- 使用
useNativeDriver: true
将动画逻辑移交原生线程处理(仅支持部分属性)。 - 复杂动画(如转场)优先考虑
React Native Reanimated
或Lottie
。
- 使用
-
批处理通信:
- 避免高频次跨桥通信(如循环中频繁更新状态),合并操作为单次调用。
- 大数据量传递时考虑序列化优化(如传输 JSON 而非复杂对象)。
3. 内存与资源管理
-
图片优化:
- 压缩图片并使用合适尺寸(如
@2x
,@3x
)。 - 使用缓存库如
react-native-fast-image
提升加载效率。 - 避免未加载完成的图片占位符导致布局抖动(使用固定宽高或占位图)。
- 压缩图片并使用合适尺寸(如
-
内存泄漏预防:
- 清除定时器、事件监听器(
setInterval
,EventListener
)。 - 在
useEffect
或componentWillUnmount
中取消异步任务(如网络请求)。
- 清除定时器、事件监听器(
4. 状态管理与更新
-
高效状态更新:
- 合并多次
setState
为单次更新,或使用函数式更新(setState(prev => ...)
)。 - 避免在
render
或函数组件顶层执行耗时计算,改用useMemo
缓存结果。
- 合并多次
-
状态管理库优化:
- 使用
reselect
创建记忆化的 Selector(如 Redux 场景)。 - 避免在
mapStateToProps
中返回全量状态,按需提取字段。
- 使用
5. 启动与包体积优化
-
启动加速:
- 启用 Hermes 引擎(React Native 0.60+),显著提升 JS 解析效率。
- 延迟加载非关键组件(
React.lazy
+Suspense
)或按需加载路由。
-
代码拆分:
- 使用 Metro 的拆包功能(
metro.config.js
配置)分离业务代码。 - 移除未使用的依赖库,减少主包体积。
- 使用 Metro 的拆包功能(
6. 工具与监控
-
性能分析工具:
- React Native Profiler:检测组件渲染次数和耗时。
- Flipper:监控网络、日志、性能指标(内存、FPS)。
- Chrome DevTools:分析 JavaScript 执行性能(
Performance
面板)。
-
关键指标监控:
- 帧率(FPS):确保 ≥60 帧,使用
react-native-performance
监控。 - 内存占用:警惕持续增长的内存泄漏(Android Profiler / Xcode Instruments)。
- 帧率(FPS):确保 ≥60 帧,使用
7. 原生优化策略
-
复杂计算原生化:
- 将密集计算(如大数据处理)移至原生模块(Java/Kotlin, Objective-C/Swift)。
- 使用
Worklets
(React Native Reanimated)在 UI 线程执行 JS 逻辑。
-
导航性能:
- 选择原生导航库(如
React Native Navigation
)替代纯 JS 实现(如 React Navigation)。
- 选择原生导航库(如
8. 其他细节优化
-
样式与布局:
- 减少透明(
opacity
)和圆角(borderRadius
)的过度使用(Android 上可能触发离屏渲染)。 - 避免
overflow: 'visible'
(iOS 上可能导致性能问题)。
- 减少透明(
-
长列表数据预加载:
- 分页加载或虚拟化数据源,避免一次性渲染全部数据。
总结 性能优化需结合具体场景分析,优先解决瓶颈问题(如列表卡顿、启动缓慢)。通过工具定位问题后,针对性应用上述策略,逐步优化关键路径。同时保持 React Native 和依赖库的版本更新,及时获取官方性能改进。
React Native的桥接通信 在Android 和 iOS 中的原理分别是什么
在 Android
中
React Native 的 桥接通信(Bridge) 原理是通过 Java 与 JavaScript 的双向异步消息队列 实现的,依赖 JavaScript 引擎(Hermes/V8/JSC) 和 JNI(Java Native Interface) 进行跨语言调用。以下是详细解析:
1. 核心通信流程
(1) JavaScript → Java 调用
(2) Java → JavaScript 事件
2. 关键组件与角色
组件 | 作用 |
---|---|
JavaScript 引擎 | 执行JS代码(Hermes/V8/JSC),通过JNI与Java通信。 |
Java 模块 | 通过 @ReactMethod 注解暴露给JS调用的方法。 |
JNI(C++层) | 桥接Java和JavaScript引擎,处理数据序列化/反序列化。 |
MessageQueue | 异步消息队列,存储待处理的JS→Java或Java→JS调用。 |
ModuleRegistry | 维护所有已注册的原生模块(如 LocationModule 、CameraModule )。 |
3. 具体实现步骤
(1) 注册原生模块
java
// 示例:Java模块暴露给JS
public class LocationModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "LocationManager"; // JS中通过 NativeModules.LocationManager 访问
}
@ReactMethod
public void getCurrentLocation(Callback successCallback) {
Location location = getLastKnownLocation();
successCallback.invoke(location.getLatitude(), location.getLongitude());
}
}
(2) JS调用Java方法
javascript
// JavaScript代码调用原生模块
NativeModules.LocationManager.getCurrentLocation((lat, lng) => {
console.log(`Latitude: ${lat}, Longitude: ${lng}`);
});
(3) 底层数据流
- JS层 :调用
NativeModules.X
时,参数被序列化为JSON字符串。 - C++层(JNI) :
- 通过
JNI
调用Java模块的@ReactMethod
方法。 - 将Java返回的数据反序列化为JSON传递给JS。
- 通过
- 异步队列:所有调用默认异步,避免阻塞UI线程。
4. 性能瓶颈与优化
瓶颈 | 原因 | 新架构(JSI)的优化 |
---|---|---|
序列化开销 | JSON ↔ Java对象的转换耗时 | 直接内存共享(无序列化) |
异步延迟 | 消息队列排队导致响应慢 | 同步调用(如 jsi::Function ) |
跨线程通信 | JS线程、UI线程、原生模块线程切换开销 | 减少线程跳跃(TurboModules) |
5. 与 iOS 桥接的差异
维度 | Android(JNI) | iOS(JavaScriptCore) |
---|---|---|
引擎绑定 | 通过JNI连接Hermes/V8/JSC | 直接绑定JavaScriptCore(JSC) |
线程模型 | 多线程(JS线程、UI线程、Native线程) | 主要运行在JS线程 |
模块注册 | 需通过 ReactPackage 注册 |
通过 RCT_EXPORT_MODULE 宏注册 |
6. 新架构(JSI)的改进
- 废弃桥接 :直接通过 JSI(JavaScript Interface) 实现Java与JS的同步通信。
- 共享内存 :JavaScript和原生端可直接访问同一内存区域(如
ArrayBuffer
)。 - TurboModules:按需加载原生模块,减少启动开销。
cpp
// JSI示例:直接暴露Java方法给JS
jsi::Function getLocation = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "getLocation"),
0,
[](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value*, size_t) {
// 直接调用Java方法(无需序列化)
return jsi::Value(runtime, jsi::String::createFromUtf8(runtime, "37.7749,-122.4194"));
}
);
runtime.global().setProperty(runtime, "getLocation", getLocation);
总结
- Android桥接本质:通过JNI和JSON序列化实现Java与JavaScript的异步通信。
- 性能痛点:序列化、异步队列、跨线程通信导致延迟。
- 演进方向:新架构使用JSI实现同步、零拷贝通信,彻底重构了React Native的底层通信模型。
在 iOS
平台上,
React Native 的 桥接通信(Bridge) 原理是通过 JavaScriptCore(JSC)引擎 和 Objective-C/Swift 原生模块 之间的异步消息传递实现的。其核心是 基于 JSON 序列化的跨语言调用 和 事件驱动机制。以下是详细解析:
1. 核心通信流程
(1) JavaScript → 原生(iOS)调用
(2) 原生(iOS)→ JavaScript 事件
2. 关键组件与角色
组件 | 作用 |
---|---|
JavaScriptCore(JSC) | iOS 系统内置的 JS 引擎,负责执行 JavaScript 代码。 |
RCTBridge | 桥接核心,管理 JS 与原生模块的通信。 |
RCTBridgeModule | 原生模块基类,通过宏 RCT_EXPORT_MODULE 和 RCT_EXPORT_METHOD 暴露方法。 |
RCTEventEmitter | 用于原生端向 JS 发送事件(如设备传感器、生命周期回调)。 |
MessageQueue | 异步消息队列,处理跨线程调用。 |
3. 具体实现步骤
(1) 注册原生模块
objc
// Objective-C 模块示例
#import <React/RCTBridgeModule.h>
@interface LocationManager : NSObject <RCTBridgeModule>
@end
@implementation LocationManager
RCT_EXPORT_MODULE(); // 暴露模块名(JS中通过 NativeModules.LocationManager 访问)
RCT_EXPORT_METHOD(getCurrentLocation:(RCTResponseSenderBlock)callback) {
CLLocation *location = [self fetchLocation];
callback(@[ @(location.coordinate.latitude), @(location.coordinate.longitude) ]);
}
@end
(2) JavaScript 调用原生方法
javascript
// JS 代码调用原生模块
import { NativeModules } from 'react-native';
NativeModules.LocationManager.getCurrentLocation((lat, lng) => {
console.log(`Latitude: ${lat}, Longitude: ${lng}`);
});
(3) 底层数据流
- JS 层 :调用
NativeModules.X
时,参数被序列化为 JSON 字符串。 - 原生层 :
- Objective-C 的
RCTBridge
通过 JavaScriptCore 接收到调用请求。 - 调用对应的
RCT_EXPORT_METHOD
方法,并将结果返回给 JS。
- Objective-C 的
- 异步队列:默认所有调用在独立队列中执行,避免阻塞主线程。
4. 性能瓶颈
阶段 | 开销来源 | 新架构(JSI)的优化 |
---|---|---|
序列化/反序列化 | JSON ↔ Objective-C 对象的转换耗时 | 直接内存共享(无序列化) |
跨线程通信 | JS 线程与原生主线程的切换延迟 | 同步调用(如 jsi::Function ) |
模块初始化 | 启动时加载所有原生模块 | 按需加载(TurboModules) |
5. 与 Android 桥接的差异
维度 | iOS(JavaScriptCore) | Android(JNI) |
---|---|---|
引擎绑定 | 直接集成系统 JSC | 通过 JNI 连接 Hermes/V8/JSC |
线程模型 | JS 运行在独立线程,原生模块默认在主线程 | 多线程(JS/UI/Native 线程分离) |
模块注册 | 宏 RCT_EXPORT_MODULE 自动注册 |
需手动注册到 ReactPackage |
6. 新架构(JSI + Fabric)的改进
- 废弃桥接 :使用 JSI(JavaScript Interface) 直接调用原生方法,无需 JSON 序列化。
- 同步通信:允许 JS 与原生代码同步执行(如手势事件处理)。
- TurboModules:延迟加载原生模块,减少启动时间。
objc
// JSI 示例(C++ 层直接暴露方法)
void installLocationModule(jsi::Runtime &runtime) {
auto getLocation = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "getLocation"),
0,
[](jsi::Runtime &runtime, const jsi::Value &, const jsi::Value *, size_t) {
// 直接调用 Objective-C 方法
return jsi::Value(runtime, jsi::String::createFromUtf8(runtime, "37.7749,-122.4194"));
}
);
runtime.global().setProperty(runtime, "getLocation", getLocation);
}
7. 常见问题
Q:为什么 iOS 的桥接性能优于 Android?
- iOS 的 JavaScriptCore 与 Objective-C 同属 Apple 生态,绑定更高效。
- Android 的 JNI 调用开销更大(需跨 Java/C++ 层)。
Q:热更新(CodePush)是否受桥接影响?
- 不影响。热更新仅替换 JS 代码,不修改原生模块。
总结
- iOS 桥接本质:通过 JavaScriptCore 和 JSON 序列化实现 JS 与 Objective-C 的异步通信。
- 历史作用:简化了跨平台开发,但成为性能瓶颈。
- 未来方向:新架构通过 JSI 实现零拷贝、同步通信,彻底重构通信模型。
10. React Native 拆包 (metro)
React Native 的拆包(Bundle Splitting)通过 Metro 打包工具实现,旨在将单一的 JavaScript Bundle 拆分为多个独立模块,优化应用启动速度和动态加载能力。以下是拆包的详细实现方案和注意事项:
1. 拆包的核心概念
- 基础包(Base Bundle) :包含 React Native 核心框架和通用依赖(如
react
,react-native
, 公共工具库)。 - 业务包(Business Bundle) :按功能模块拆分的独立 Bundle(如
home.bundle
,profile.bundle
)。 - 按需加载:应用启动时仅加载基础包,其他业务包在需要时动态加载。
2. Metro 拆包配置 步骤 1:修改 metro.config.js
javascript
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig();
return {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true, // 开启 inlineRequires 优化
},
}),
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'), // 处理资源类型
sourceExts: [...sourceExts, 'svg'],
},
serializer: {
// 自定义拆包逻辑(需配合脚本)
createModuleIdFactory: () => (path) => {
// 返回模块的唯一 ID(根据路径生成)
return path.startsWith(__dirname)
? path.substr(__dirname.length + 1).replace(/\//g, '_')
: path;
},
},
};
});
步骤 2:生成多个 Bundle 通过命令行生成基础包和业务包:
bash
# 生成基础包(包含 react-native 核心)
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/base.bundle --assets-dest android/
# 生成业务包(如 home 模块)
react-native bundle --platform android --dev false --entry-file Home.js --bundle-output android/home.bundle --assets-dest android/
3. 动态加载拆包 Android 端加载分包
java
// 加载基础包
SoLoader.loadLibrary("base.bundle");
// 动态加载业务包
CatalystInstance catalystInstance = reactContext.getCatalystInstance();
catalystInstance.loadScriptFromFile("home.bundle", "home.bundle", false);
iOS 端加载分包
objectivec
// 加载基础包
NSURL *baseBundleURL = [[NSBundle mainBundle] URLForResource:@"base" withExtension:@"bundle"];
[bridge.bundleURL setValue:baseBundleURL forKey:@"bundleURL"];
// 动态加载业务包
NSURL *homeBundleURL = [[NSBundle mainBundle] URLForResource:@"home" withExtension:@"bundle"];
[bridge enqueueApplicationScriptAtURL:homeBundleURL onComplete:^(NSError *error) {
// 加载完成回调
}];
4. 拆包策略优化
-
公共依赖提取 :将第三方库(如
lodash
,moment
)抽离到基础包,避免重复打包。 -
路由级拆包 :结合
React.lazy
和Suspense
实现路由级别的按需加载:javascriptconst HomeScreen = React.lazy(() => import('./HomeScreen')); function App() { return ( <Suspense fallback={<Loading />}> <HomeScreen /> </Suspense> ); }
-
预加载策略:提前加载用户可能访问的模块(如首页加载完成后预加载个人中心包)。
5. 常见问题与解决方案
问题场景 | 解决方案 |
---|---|
拆包后模块依赖丢失 | 确保所有业务包依赖的核心模块在基础包中存在 |
动态加载时白屏 | 使用 ActivityIndicator 或骨架屏作为加载态 |
拆包后包体积未明显减少 | 检查公共依赖是否重复打包,优化 metro.config.js 的 createModuleIdFactory |
Android 无法加载分包 | 检查文件路径权限,确保 .bundle 文件被正确打包到 APK |
6. 高级技巧
-
Metro 插件扩展 :使用
metro-split-bundle
等社区插件自动化拆包。 -
Bundle 分析工具 :通过
source-map-explorer
分析 Bundle 组成:bashnpx source-map-explorer android/base.bundle --html > report.html
-
增量更新:结合 CodePush 仅更新业务包,减少用户下载量。
总结
Metro 拆包是 React Native 性能优化的重要手段,但需权衡维护成本和收益。建议从核心业务模块开始逐步拆分,配合性能监控工具验证效果。对于中小型应用,可优先使用 React.lazy
实现代码分割;超大型项目(如百万行代码级)再考虑完整的拆包方案。
11. React Native 热更
React Native 热更新(Hot Update)允许在不发布应用商店新版本的情况下动态更新应用代码和资源,是快速修复问题和迭代功能的关键技术。以下是热更新的完整实现方案及注意事项:
1. 主流热更新方案对比
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Microsoft CodePush | 裸 React Native 项目 | 官方支持,集成简单,支持回滚和灰度发布 | 依赖微软服务器(可自建服务端) |
Expo OTA Updates | Expo 托管项目 | 无缝集成,自动同步 | 必须使用 Expo 服务,灵活性受限 |
自建热更新服务 | 对数据主权和安全性要求高的场景 | 完全可控,支持私有化部署 | 开发和维护成本高 |
2. CodePush 热更新实现流程 步骤 1:安装与配置
bash
npm install --save react-native-code-push
# 或使用 yarn
yarn add react-native-code-push
# 链接原生依赖(React Native <0.60)
react-native link react-native-code-push
步骤 2:封装热更新组件
javascript
import codePush from 'react-native-code-push';
// 全屏覆盖式更新(强制立即更新)
const CodePushWrapper = () => {
useEffect(() => {
codePush.sync({
installMode: codePush.InstallMode.IMMEDIATE,
updateDialog: {
appendReleaseDescription: true,
title: "发现新版本",
optionalUpdateMessage: "更新内容:",
mandatoryContinueButtonLabel: "立即更新",
},
});
}, []);
return <App />;
};
export default codePush(CodePushWrapper);
步骤 3:发布更新包
bash
# 发布到 Staging 环境
appcenter codepush release-react -a <组织名>/<应用名> -d Staging
# 发布到 Production 环境
appcenter codepush release-react -a <组织名>/<应用名> -d Production
3. 自建热更新服务核心架构 技术栈选择
- 服务端:Node.js + Express(接口服务) + MongoDB(版本管理)
- 客户端 :
react-native-fs
(文件下载) +react-native-zip-archive
(解压) - 安全:RSA 签名验证 + AES 加密 Bundle
核心流程
- 版本检测:客户端启动时向服务端查询最新版本号
- 差异下载:对比本地版本,下载差量包(bsdiff/bspatch)
- 安全校验:验证文件哈希值和数字签名
- 静默更新:后台下载并解压,下次启动生效
代码示例(客户端)
javascript
const checkUpdate = async () => {
const localVersion = await AsyncStorage.getItem('appVersion');
const response = await fetch('https://api.your-domain.com/check-update');
const { latestVersion, patchUrl, signature } = await response.json();
if (compareVersions(latestVersion, localVersion) > 0) {
const localPath = `${RNFS.DocumentDirectoryPath}/update_${latestVersion}.zip`;
await RNFS.downloadFile({
fromUrl: patchUrl,
toFile: localPath,
background: true, // 后台下载
}).promise;
// 验证签名
const isValid = verifySignature(localPath, signature);
if (isValid) {
await unzip(localPath, RNFS.DocumentDirectoryPath);
await AsyncStorage.setItem('pendingUpdate', latestVersion);
showToast('更新已下载,重启应用后生效');
}
}
};
4. 热更新最佳实践
安全防护
- 代码混淆 :使用
react-native-obfuscator
保护核心逻辑 - HTTPS 传输:防止中间人攻击
- 双签名验证:Bundle 哈希值 + 服务器 RSA 签名
灰度策略
- 按用户 ID 分桶(10% 用户先行测试)
- 按设备类型定向发布(仅 Android 或特定分辨率)
- 版本渐进发布(24 小时内分阶段放量)
回滚机制
javascript
// CodePush 自动回滚
codePush.sync({
rollbackRetryOptions: {
delayInHours: 1, // 失败后 1 小时重试
maxRetryAttempts: 3 // 最大重试次数
}
});
// 自建服务的回滚方案
const rollback = async () => {
const lastStableVersion = await fetchLastStableVersion();
await applyPatch(lastStableVersion);
};
5. 热更新注意事项
- 苹果审核政策 :禁止热更新修改核心功能(需遵守 App Store Review Guidelines 2.5.2)
- 版本兼容性:确保新旧版本数据结构和原生模块兼容
- 更新包大小:单个差量包建议 ≤ 2MB(WiFi 环境下可放宽至 5MB)
- 用户感知:非强制更新应允许用户选择跳过
6. 监控与统计
- 更新成功率监控:统计下载完成率、安装成功率
- 崩溃率对比:更新前后崩溃率变化(接入 Sentry/Bugly)
- 性能指标:监控启动时间、内存占用的版本间差异
总结
热更新是 React Native 动态化能力的重要体现,但需平衡灵活性与安全性:
- 中小团队优先使用 CodePush,快速实现基础热更新
- 大型金融/政务应用建议自建服务,结合拆包技术实现模块级更新
- 始终保留强制全量更新的逃生通道,避免热更新故障导致大规模不可用
扩展工具推荐:
- react-native-pushy:中文社区热更新方案
- Bugly:腾讯移动端异常监控(支持热更新统计)
12. React Native Animated 和 ReAnimated 中桥接通信 和 jsi通信的区别是什么 桥接通信的原理是
React Native 中的 Animated 和 Reanimated 库在通信机制上存在显著差异,主要体现在 桥接通信(Bridge) 和 JSI(JavaScript Interface) 的底层实现上。以下是两者的核心区别及桥接通信的原理:
一、桥接通信(Bridge)的原理
1. 通信流程
桥接通信是 React Native 早期的跨线程通信机制,其核心步骤如下:
- 序列化:JavaScript 线程将调用请求(如调用原生模块方法)和参数序列化为 JSON 格式。
- 消息传递:JSON 数据通过异步队列(MessageQueue)发送到原生线程(iOS/Android)。
- 反序列化:原生线程解析 JSON 数据,执行对应的原生方法。
- 结果返回:原生线程将结果序列化为 JSON,通过队列返回给 JavaScript 线程。
- 回调处理:JavaScript 线程接收结果并触发回调。
2. 性能瓶颈
- 异步延迟:消息传递需排队处理,导致高延迟,不适合高频更新(如动画)。
- 序列化开销:数据在 JSON 格式和原生对象之间反复转换,消耗 CPU 资源。
- 线程阻塞:JavaScript 线程和原生线程互相等待,易引发卡顿。
3. Animated 库的桥接使用
useNativeDriver: true
:Animated 将动画配置(如起始值、时长)通过 Bridge 发送到原生端,由原生代码驱动动画更新。- 优点:减少 JavaScript 线程的计算负担。
- 缺点:动画中途无法动态修改参数(如根据手势实时调整),且复杂动画仍需跨线程通信。
二、JSI(JavaScript Interface)通信的原理
1. 核心机制
JSI 是 React Native 新架构(Fabric)的核心组件,直接打通了 JavaScript 和原生代码的通信:
- 直接内存访问:JavaScript 可以持有原生对象的引用(如 C++ 对象),无需序列化。
- 同步调用:方法调用直接在 JavaScript 线程或 UI 线程执行,无异步队列延迟。
- 跨线程通信优化:通过 C++ 层桥接,支持更高效的数据共享。
2. Reanimated 的 JSI 实现
Reanimated 2.0+ 基于 JSI 实现高性能动画:
-
Worklet 函数 :将 JavaScript 函数标记为
'worklet'
,编译为 C++ 代码,在 UI 线程直接执行。javascriptconst updatePosition = (value) => { 'worklet'; // 标记为 Worklet sharedValue.value = value * 2; };
-
SharedValue :通过 JSI 直接映射到原生内存的变量,JavaScript 和 UI 线程可直接读写。
javascriptconst offset = useSharedValue(0); // 直接操作原生内存
-
同步更新: 动画值的修改立即生效,无需跨线程通信,适合 60 FPS 动画。
三、Animated vs. Reanimated 的通信对比
特性 | Animated(Bridge) | Reanimated(JSI) |
---|---|---|
通信方式 | 异步 JSON 序列化 + 消息队列 | 同步直接内存访问 + JSI 方法调用 |
线程模型 | JS 线程与原生线程分离,依赖桥接 | JS 可访问 UI 线程,Worklet 在 UI 线程执行 |
性能 | 高延迟,适合低频更新 | 低延迟,支持 60 FPS 高频更新 |
动态交互 | 无法实时修改动画参数 | 支持手势驱动的实时动画调整 |
数据序列化 | 需要 JSON 转换 | 直接操作原生对象,无序列化开销 |
适用场景 | 简单动画(如渐隐、位移) | 复杂交互(拖拽、物理动画) |
四、桥接通信的代码示例(Animated)
javascript
import { Animated } from 'react-native';
const opacity = new Animated.Value(0);
// 启动动画(通过 Bridge 发送配置)
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true, // 配置发送到原生端
}).start();
// 动画更新仍需通过 Bridge 回调(如监听值变化)
opacity.addListener(({ value }) => {
console.log(value); // 回调通过 Bridge 传递
});
五、JSI 通信的代码示例(Reanimated)
javascript
import { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
function MyComponent() {
const offset = useSharedValue(0); // 直接映射到原生内存
// UI 线程运行的 Worklet
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
}));
// 手势处理(直接修改 SharedValue,无桥接开销)
const panGesture = Gesture.Pan()
.onUpdate((e) => {
offset.value = e.translationX; // 同步更新
});
return <Animated.View style={animatedStyle} />;
}
六、总结
- 桥接通信(Animated):适用于简单动画,依赖异步消息队列,存在性能瓶颈,适合兼容性要求高的场景。
- JSI 通信(Reanimated): 通过直接内存访问和 Worklet 机制实现高性能动画,适合复杂交互和实时性要求高的场景,需依赖新架构(React Native 0.65+ 和 Reanimated 2.0+)。
迁移建议:若项目需要高性能动画或复杂手势交互,优先选择 Reanimated;若仅需基础动画且对依赖敏感,可继续使用 Animated。
JSI 和 Android JNI 的比较
JSI(JavaScript Interface) 与 JNI(Java Native Interface) 在某些方面确实有相似之处(如跨语言/环境通信),但它们在设计目标、实现方式和应用场景上有显著区别。以下是两者的详细对比:
1. 核心目标
特性 | JSI(React Native) | JNI(Android) |
---|---|---|
目的 | 优化 JavaScript 与原生代码(C++)的通信,提升性能 | 实现 Java/Kotlin 与 C/C++ 的互操作性 |
应用场景 | React Native 新架构(Fabric)的核心组件 | Android 中调用本地库(如 OpenCV、性能优化代码) |
通信方向 | JavaScript ↔ C++(直接通信) | Java/Kotlin ↔ C/C++(双向通信) |
2. 实现机制对比
JSI(React Native)
- 直接内存访问 :JavaScript 可以直接持有和操作 C++ 对象(通过
HostObject
),无需序列化数据。 - 同步调用 :方法调用直接在 JavaScript 线程或 UI 线程执行,无异步队列延迟。
- 轻量级绑定 : 通过 C++ 层定义接口,JavaScript 直接调用原生方法(如
jsi::Function
)。
JNI(Android)
- 间接内存访问 :Java 与 C/C++ 之间通过 JNI 接口传递数据,需手动管理内存(如
jobject
转换)。 - 异步通信 :JNI 调用默认在调用线程执行,但复杂场景需处理线程同步(如
AttachCurrentThread
)。 - 手动类型转换 : 需将 Java 类型(如
jstring
)转换为 C/C++ 类型(如char*
),过程繁琐且易出错。
3. 性能差异
维度 | JSI | JNI |
---|---|---|
通信开销 | 极低(直接操作内存) | 较高(类型转换和序列化) |
线程模型 | 支持同步调用,适合高频更新(如动画) | 需处理线程同步,高频调用易引发性能问题 |
适用场景 | 实时交互(如手势、60 FPS 动画) | 计算密集型任务(如图像处理) |
4. 代码示例对比
JSI(React Native)
在 C++ 中定义原生模块,JavaScript 直接调用:
cpp
// C++ 代码(JSI 模块)
#include <jsi/jsi.h>
using namespace facebook::jsi;
class MyJSIHelper : public HostObject {
public:
// 定义 JavaScript 可调用的方法
static void install(Runtime &runtime) {
runtime.global().setProperty(
runtime,
"myJSIHelper",
Function::createFromHostFunction(
runtime,
PropNameID::forAscii(runtime, "myJSIHelper"),
0,
[](Runtime &runtime, const Value &thisVal, const Value *args, size_t count) -> Value {
// 直接处理逻辑,无序列化
return Value(42);
}
)
);
}
};
JavaScript 调用:
javascript
// JavaScript 直接调用 C++ 方法
const result = global.myJSIHelper(); // result = 42
JNI(Android)
Java 调用 C/C++ 函数:
java
// Java 代码
public class NativeHelper {
static {
System.loadLibrary("native-lib");
}
// 声明 native 方法
public static native int getNumber();
}
C++ 实现:
cpp
// C++ 代码(JNI)
#include <jni.h>
extern "C" JNIEXPORT jint JNICALL
Java_com_example_NativeHelper_getNumber(JNIEnv *env, jclass clazz) {
return 42;
}
5. 核心相似点
- 跨语言通信:两者都用于不同语言/环境之间的交互(JavaScript ↔ C++ 或 Java ↔ C++)。
- 性能优化:均旨在减少跨语言调用的开销,提升执行效率。
6. 核心区别
维度 | JSI | JNI |
---|---|---|
通信方式 | 直接内存访问,无序列化 | 需类型转换和序列化 |
线程模型 | 同步调用,适合实时交互 | 默认同步,但需手动处理多线程 |
开发复杂度 | 对 JavaScript 透明,API 更简洁 | 需手动管理内存和类型转换 |
应用层级 | 框架层优化(React Native 内部) | 系统层支持(Android 平台标准) |
7. 总结
- JSI 是 React Native 为优化性能设计的通信层,直接打通 JavaScript 与 C++ ,用于实现高效 UI 渲染和动画(如
react-native-reanimated
)。 - JNI 是 Android 平台的通用跨语言接口,连接 Java 与 C/C++,适用于本地库集成和计算密集型任务。
类比理解:
- JSI 类似于 JNI 的"升级版",但专为 React Native 的高性能场景定制,避免了传统桥接(Bridge)的序列化开销。
- JNI 更通用,但开发复杂度高,适合 Android 原生开发中的底层优化。
React Native的JSI 和原生通信 以及和 Flutter的对比
React Native 的新架构中,JSI(JavaScript Interface)通过共享内存(Shared Memory)机制实现了高效的 JavaScript 与原生(C++/Objective-C/Java)通信,这是其性能提升的关键之
JSI 如何利用共享内存?
-
直接内存访问
- JSI 允许 JavaScript 和原生代码直接访问同一块内存区域(如
ArrayBuffer
),无需序列化/反序列化(旧桥接需要 JSON 转换)。 - 例如,JavaScript 可以直接修改 C++ 分配的缓冲区,原生端实时生效。
- JSI 允许 JavaScript 和原生代码直接访问同一块内存区域(如
-
数据传递优化
- 二进制数据共享:如图像、音频等大数据传输时,直接传递内存指针而非拷贝数据。
- 同步调用:避免了旧桥接的异步消息队列延迟。
-
示例场景
- 当 JavaScript 调用原生模块获取摄像头帧数据时,帧数据可以存放在共享内存中,JavaScript 直接读取,减少拷贝开销。
对比旧桥接(Bridge)的差异
机制 | 旧桥接(Bridge) | 新架构(JSI) |
---|---|---|
通信方式 | 异步 JSON 消息序列化 | 同步共享内存访问 |
性能开销 | 高(序列化/反序列化 + 消息队列延迟) | 极低(直接内存操作) |
数据限制 | 仅支持可序列化数据 | 支持任意二进制数据(如 ArrayBuffer ) |
技术实现细节
-
JavaScript 引擎集成
- JSI 是抽象层,实际依赖 JavaScript 引擎(如 Hermes 或 JSC)的能力:
- Hermes 针对 React Native 优化,直接支持 JSI 内存共享。
- 在 iOS 的 JavaScriptCore(JSC)中,通过 C++ 绑定实现内存共享。
- JSI 是抽象层,实际依赖 JavaScript 引擎(如 Hermes 或 JSC)的能力:
-
C++ 核心层
-
Fabric 渲染引擎和 TurboModules 通过 C++ 实现,与 JavaScript 共享内存:
cpp// 示例:C++ 暴露一个共享内存对象给 JavaScript jsi::ArrayBuffer buffer = runtime.global().createArrayBuffer(sharedMemoryPtr, size);
-
-
线程安全
- 共享内存需处理多线程竞争(如 JavaScript 线程 vs UI 线程),JSI 通过锁或原子操作保证安全。
与 Flutter 的对比
维度 | React Native(JSI) | Flutter |
---|---|---|
通信机制 | 共享内存 + 同步调用 | 无通信(Dart 直接操作渲染树) |
数据共享 | 显式共享(需主动管理内存) | 隐式共享(框架自动处理) |
适用场景 | 需频繁与原生模块交换大数据 | 纯 Dart 渲染,无需原生数据交换 |
注意事项
-
内存管理
- 开发者需注意共享内存的生命周期(避免 JavaScript 持有已释放的原生内存)。
-
平台限制
- Android 的 JSI 实现依赖 Hermes 或 JSC,而 iOS 仅限 JSC(Hermes 在 iOS 上暂不支持)。
-
调试复杂度
- 共享内存的错误(如越界访问)可能更难调试,需依赖原生工具(如 LLDB)。
总结
- JSI 的共享内存机制是 React Native 新架构的核心优化,解决了旧桥接的性能瓶颈。
- 适合高频、大数据量通信场景(如视频处理、3D 渲染),但需要开发者更关注内存安全。
- Flutter 通过自建渲染管线避免了这类问题,但牺牲了与原生模块的深度集成灵活性。
React Native 使用 Hermes 是否影响 iOS 热更新?
Hermes 在 iOS 是实验性支持,但不影响热更新(因符合解释执行策略) Apple 未强制要求 JSC,但 Hermes 在 iOS 需禁用 JIT,性能可能不如 Android。 若使用 Hermes + iOS,务必测试 CodePush 和性能表现,并关注官方更新。
Hermes 对 iOS 的官方支持状态 (1) 当前事实 Android: Hermes 是默认引擎(从 React Native 0.70 开始)。
iOS: Hermes 仍处于实验阶段(截至 React Native 0.73),需手动开启,且存在限制:
必须通过 hermes-engine 的 iOS 版(社区维护,非官方正式支持)。
性能和行为可能与 Android 版不一致。
(2) 技术背景 Apple 的限制:iOS 禁止动态代码生成(JIT),而 Hermes 在 Android 上默认启用 JIT 优化。
Hermes 的妥协:在 iOS 上强制使用 解释模式(无 JIT),性能可能不如 JSC。
在 React Native 的新架构(包括 Fabric 渲染引擎 )下,iOS 上的热更新(Hot Reloading/Code Push)能力仍然可用,但需要明确以下几点关键细节:
1. Fabric 渲染引擎与热更新的关系
-
Fabric 是渲染层优化 :Fabric 主要解决的是 UI 渲染性能问题 (通过 JSI 同步通信和 C++ 渲染管线),它 不直接影响 JavaScript 代码的加载和执行机制。
- 热更新(如 CodePush、Metro 的热重载)依赖的是 JavaScript 引擎(Hermes/JSC)的动态代码加载能力,与渲染层无关。
-
iOS 限制的背景 :iOS 对 JIT(即时编译) 的限制(禁止动态执行非 Apple 审核的代码)一直存在,但热更新通过以下方式绕过:
- 解释执行:Hermes 或 JSC 以解释模式运行 JavaScript(非原生代码生成)。
- 预编译字节码:Hermes 将 JavaScript 编译为字节码(非机器码),符合 Apple 审核规则。
2. 新架构下热更新的兼容性
热更新方案 | 旧架构(Bridge) | 新架构(Fabric + JSI) | 说明 |
---|---|---|---|
Metro 热重载 | ✅ 支持 | ✅ 支持 | 开发时使用,依赖 JavaScript 动态替换。 |
CodePush | ✅ 支持 | ✅ 支持 | 生产环境热更新,需确保 JavaScript 引擎兼容(Hermes/JSC 均支持)。 |
原生模块热更新 | ❌ 不支持 | ❌ 不支持 | 修改原生代码(Objective-C/Swift)仍需 App Store 重新审核。 |
3. 注意事项
-
Hermes 与 CodePush 的配合:
- Hermes 生成的字节码是平台相关的(iOS/Android 不同),需分别生成并推送。
- CodePush 官方支持 Hermes 字节码更新(需配置
hermes: true
)。
-
新架构的 Metro 配置:
-
确保
metro.config.js
适配 React Native 0.70+ 的新架构:javascriptmodule.exports = { transformer: { unstable_allowRequireContext: true, // 允许动态 require }, };
-
-
iOS 审核政策:
- Apple 允许 JavaScript 代码的动态更新 ,但禁止以下行为:
- 修改应用核心功能(如支付、权限)。
- 下载原生代码(如动态加载
.so
/.dylib
)。
- Apple 允许 JavaScript 代码的动态更新 ,但禁止以下行为:
4. 如何验证热更新是否工作?
-
开发环境:
- 运行应用后,修改 JavaScript 代码并保存,观察 Metro 是否自动注入更新。
-
生产环境:
-
使用 CodePush 发布更新后,检查客户端日志:
bashcode-push deployment ls <AppName> -k
-
确保客户端版本与更新包匹配(避免二进制版本不兼容)。
-
5. 与 Flutter 的对比
框架 | 热更新机制 | iOS 限制 |
---|---|---|
React Native | JavaScript/字节码动态替换(CodePush) | 允许(需符合 Apple 政策) |
Flutter | Dart 代码热重载(开发模式) | 生产环境禁止动态更新 Dart 代码 |
总结
- Fabric 渲染引擎不影响 React Native 在 iOS 的热更新能力,CodePush 和 Metro 热重载仍可正常使用。
- 需确保 JavaScript 引擎(Hermes/JSC)和热更新工具(如 CodePush)适配新架构。
- 避免触碰 Apple 禁止的行为(如原生代码热更新)。
JavaScript 的内存回收机制
JavaScript 的内存回收机制主要依赖 垃圾回收(Garbage Collection, GC) 自动管理内存,开发者无需手动释放。其核心机制包括以下内容:
1. 内存回收的基本原理
JavaScript 引擎(如 V8、SpiderMonkey)通过 可达性(Reachability) 判断对象是否存活:
- 根对象(Roots):始终存活的对象(如全局变量、当前函数局部变量、活动闭包中的变量)。
- 可达对象:从根对象出发,通过引用链能访问到的对象。
- 不可达对象:无法通过任何引用链访问的对象,会被回收。
2. 主要垃圾回收算法
(1) 标记-清除(Mark-and-Sweep)
- 步骤 :
- 标记阶段:从根对象出发,递归标记所有可达对象。
- 清除阶段:遍历堆内存,回收未被标记的对象。
- 优点:解决循环引用问题(传统引用计数无法处理)。
- 缺点:可能引发内存碎片。
(2) 分代收集(Generational Collection)
- 内存分区 :
- 新生代(Young Generation) :存放新创建的对象,使用 Scavenge 算法(复制存活对象到另一半空间)。
- 老生代(Old Generation) :存活时间较长的对象,使用 标记-清除或标记-整理(Mark-Compact)。
- 晋升机制:对象在新生代多次存活后会被移至老生代。
(3) 增量回收(Incremental GC)
- 将垃圾回收过程拆分为多个小任务,避免长时间阻塞主线程(减少页面卡顿)。
(4) 空闲时回收(Idle-Time GC)
- 在浏览器空闲时段(如
requestIdleCallback
)执行回收,减少对用户体验的影响。
3. 常见内存泄漏场景
(1) 意外的全局变量
javascript
function leak() {
leakedVar = 'I am a global!'; // 未用 var/let/const,隐式变为全局变量
}
解决 :严格模式('use strict'
)或显式声明变量。
(2) 未清理的定时器/事件监听
javascript
const timer = setInterval(() => {}, 1000);
// 忘记 clearInterval(timer) 会导致回调函数持续引用外部变量
解决 :在组件卸载或不再需要时清除(如 clearInterval
、removeEventListener
)。
(3) 闭包保留无用引用
javascript
function outer() {
const bigData = new Array(1000000).fill('*');
return function inner() {
console.log('Closure'); // inner 函数隐式持有 bigData 的引用
};
}
const holdClosure = outer(); // bigData 无法被回收
解决 :手动解除引用(如 holdClosure = null
)。
(4) DOM 游离引用
javascript
const elements = {
button: document.getElementById('myButton'),
};
// 即使从 DOM 移除 button,elements.button 仍保留引用
document.body.removeChild(document.getElementById('myButton'));
解决 :移除 DOM 后手动置空引用(elements.button = null
)。
4. 内存分析工具
(1) Chrome DevTools
- Memory 面板 :
- Heap Snapshot:分析堆内存中的对象分布。
- Allocation Instrumentation:跟踪内存分配时间线。
- Allocation Sampling:统计内存分配来源。
- Performance 面板:监控内存泄漏导致的周期性增长。
(2) Node.js 内存分析
-
--inspect
标志启用调试,结合 Chrome DevTools。 -
process.memoryUsage()
获取内存使用情况:javascriptconsole.log(process.memoryUsage()); // { rss: 10240000, heapTotal: 4000000, heapUsed: 2000000 }
5. 最佳实践
- 避免全局变量:使用模块化(ES6 Modules)或 IIFE 隔离作用域。
- 及时清理资源 :
- 定时器、事件监听器、WebSocket 连接。
- 大型数据结构(如缓存)设置过期机制。
- 慎用闭包:确保只保留必要的引用。
- 弱引用优化 :
- 使用
WeakMap
/WeakSet
允许对象被回收。 - 浏览器环境可用
WeakRef
(ES2021)。
- 使用
6. 扩展:V8 引擎的优化
- 并行回收:利用多线程加速垃圾回收。
- 惰性清理:延迟清理未被标记的内存块。
- 指针压缩:减少内存占用(如 64 位系统下用 32 位指针)。
掌握 JavaScript 的内存回收机制能有效避免内存泄漏,提升应用性能!
ReactNative和原生端通信
React Native 和原生端(Android/iOS)的通信主要通过 桥接(Bridge) 机制实现。以下是核心通信方式及示例:
1. React Native 调用原生模块
Android 端实现
-
创建原生模块 继承
ReactContextBaseJavaModule
,暴露方法给 JS:javapublic class ToastModule extends ReactContextBaseJavaModule { public ToastModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "ToastModule"; // JS 中通过此名调用 } @ReactMethod public void showToast(String message) { Toast.makeText(getReactApplicationContext(), message, Toast.LENGTH_SHORT).show(); } }
-
注册模块 创建
ReactPackage
并添加到MainApplication.java
:javapublic class MyAppPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { return Arrays.<NativeModule>asList(new ToastModule(reactContext)); } }
iOS 端实现 (Objective-C)
-
创建原生模块 继承
RCTBridgeModule
:objc#import <React/RCTBridgeModule.h> @interface ToastModule : NSObject <RCTBridgeModule> @end @implementation ToastModule RCT_EXPORT_MODULE(); // 导出模块 RCT_EXPORT_METHOD(showToast:(NSString *)message) { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil]; }); } @end
JS 端调用
javascript
import { NativeModules } from 'react-native';
NativeModules.ToastModule.showToast("Hello from JS!");
2. 原生端发送事件到 React Native
Android 端
java
// 在原生模块中发送事件
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onToastShown", "Toast shown!");
iOS 端
objc
[self sendEventWithName:@"onToastShown" body:@{@"message": @"Toast shown!"}];
JS 端监听
javascript
import { NativeEventEmitter } from 'react-native';
const eventEmitter = new NativeEventEmitter(NativeModules.ToastModule);
useEffect(() => {
const subscription = eventEmitter.addListener('onToastShown', (event) => {
console.log(event.message);
});
return () => subscription.remove();
}, []);
3. 回调函数(Callback)与 Promise
回调函数示例
java
@ReactMethod
public void getStatus(String id, Callback successCallback, Callback errorCallback) {
try {
String status = fetchStatus(id);
successCallback.invoke(status);
} catch (Exception e) {
errorCallback.invoke(e.getMessage());
}
}
Promise 示例
java
@ReactMethod
public void fetchData(Promise promise) {
try {
String result = fetchDataFromNative();
promise.resolve(result);
} catch (Exception e) {
promise.reject("FETCH_FAILED", e);
}
}
JS 端调用 Promise:
javascript
NativeModules.DataModule.fetchData()
.then(result => console.log(result))
.catch(error => console.error(error));
4. 直接暴露常量
Android/iOS
java
@Override
public Map<String, Object> getConstants() {
return MapBuilder.of("APP_VERSION", BuildConfig.VERSION_NAME);
}
JS 端访问:
javascript
console.log(NativeModules.ToastModule.APP_VERSION);
注意事项
- 性能:频繁通信可能影响性能,复杂计算应放在原生端。
- 线程 :
- Android 的
@ReactMethod
默认在非 UI 线程执行。 - iOS 的导出方法默认在主线程。
- Android 的
- 类型转换 :JS 与原生类型需匹配(如
Integer
↔number
)。 - 调试 :使用
console.log
或 Flipper 检查通信。
新架构(TurboModules & Fabric)
- TurboModules:替代旧桥接,支持类型安全、同步调用。
- Fabric:改进 UI 渲染性能。
如需更高效通信,可逐步迁移到新架构。
通过以上方式,React Native 和原生端可以灵活交互,实现复杂功能。
上述通信时: MyAppPackage 在原生端是在哪里被调用或者初始化的
在 React Native 项目中,MyAppPackage
(自定义的 ReactPackage
)需要在原生端的 应用初始化阶段 被注册。具体位置取决于平台(Android/iOS),以下是详细说明:
Android 端
1. 注册位置
MyAppPackage
需要在 MainApplication.java
(或 MainApplication.kt
)中被添加到 ReactNativeHost
的 getPackages()
方法中。
2. 代码示例
java
// 文件路径:android/app/src/main/java/com/yourAppName/MainApplication.java
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(), // 默认的核心包
new MyAppPackage() // 添加你的自定义包
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
关键点
getPackages()
方法返回所有需要注册的ReactPackage
列表。MainReactPackage
是 React Native 默认提供的核心包(如ViewManager
、NativeModule
),必须保留。MyAppPackage
是你自定义的包,包含原生模块或视图管理器。
iOS 端
iOS 的模块注册方式与 Android 不同,不需要手动注册 ReactPackage
,而是通过以下步骤:
1. 导出模块
在原生模块中通过 RCT_EXPORT_MODULE()
宏导出:
objc
// ToastModule.m
@implementation ToastModule
RCT_EXPORT_MODULE(); // 自动注册
@end
2. 自动注册机制
React Native 的 iOS 端会通过 Objective-C 的运行时(Runtime)自动发现所有实现了 RCTBridgeModule
协议的类,无需手动添加到包列表。
为什么需要 ReactPackage
?
- Android:由于 Java 没有动态发现机制,必须显式声明所有原生模块和视图管理器。
- iOS:Objective-C 的运行时支持自动扫描类,因此无需手动注册。
调试时常见问题
-
模块未找到 :检查
MyAppPackage
是否被正确添加到getPackages()
列表。 -
混淆问题 :在
proguard-rules.pro
中排除原生模块:proguard-keep public class com.yourAppName.toastmodule.** { *; }
-
多包冲突:确保包名唯一,避免重复注册。
总结
- Android :在
MainApplication.java
的getPackages()
中手动添加MyAppPackage
。 - iOS :无需手动注册,通过
RCT_EXPORT_MODULE()
自动完成。
通过这种方式,React Native 会在启动时加载所有注册的原生模块,供 JS 端调用。
新架构Fabric中 TurboModules 通信机制详解
TurboModules 是 React Native 新架构中的核心改进之一,它彻底重构了原生模块的通信方式,提供了更高效、类型安全的跨平台通信机制。以下是 TurboModules 的工作原理和使用方法:
与旧桥接(Bridge)的主要区别
- 懒加载:TurboModules 是按需加载的,而不是像旧桥接那样在应用启动时初始化所有模块
- 同步执行:支持同步调用,无需经过异步桥接
- 类型安全:使用代码生成确保JS和原生端的类型一致性
- 性能提升:减少序列化/反序列化开销,通信更高效
TurboModules 通信流程
1. 定义接口 (TypeScript)
首先需要使用 TypeScript 定义模块接口:
typescript
// NativeSampleModule.ts
export interface Spec extends TurboModule {
readonly getConstants: () => {};
readonly reverseString: (input: string) => string;
readonly addNumbers: (a: number, b: number) => number;
}
export default TurboModuleRegistry.getEnforcing<Spec>('SampleModule');
2. 代码生成
React Native 会根据 TypeScript 接口自动生成以下代码:
- JSI绑定代码:用于C++层与JavaScript的交互
- 类型验证代码:确保通信时的类型安全
3. 原生端实现
Android (Kotlin/Java)
kotlin
// SampleModule.kt
class SampleModule(reactContext: ReactApplicationContext) :
NativeSampleModuleSpec(reactContext) {
override fun getName() = "SampleModule"
override fun getConstants() = mutableMapOf<String, Any>()
override fun reverseString(input: String) = input.reversed()
override fun addNumbers(a: Double, b: Double) = a + b
}
iOS (Objective-C++)
objc
// RCTSampleModule.mm
#import "RCTSampleModule.h"
@implementation RCTSampleModule
RCT_EXPORT_MODULE()
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeSampleModuleSpecJSI>(params);
}
- (NSString *)reverseString:(NSString *)input
{
return [[input reverseObjectEnumerator] allObjects];
}
- (double)addNumbers:(double)a b:(double)b
{
return a + b;
}
@end
4. 注册模块
Android 注册
java
// 在Package实现中注册
public class MyAppPackage implements ReactPackage {
@Override
public List<TurboModule> getTurboModules() {
return Arrays.asList(new SampleModule());
}
}
iOS 注册
iOS 使用更简单的注册方式,主要依靠宏定义:
objc
// 在模块实现文件中
RCT_EXPORT_TURBO_MODULE(SampleModule)
5. JS端调用
typescript
import { turboModuleRegistry } from 'react-native';
const SampleModule = turboModuleRegistry.getEnforcing('SampleModule');
const reversed = SampleModule.reverseString('Hello');
const sum = SampleModule.addNumbers(10, 20);
核心优势
-
性能提升:
- 使用JSI(JavaScript Interface)直接通信,无需JSON序列化
- 同步调用减少延迟
-
类型安全:
- 通过代码生成确保两端类型一致
- 编译时检查替代运行时错误
-
内存优化:
- 模块懒加载减少启动内存占用
- 更智能的生命周期管理
-
跨平台一致性:
- 统一Android/iOS开发体验
- 减少平台特定代码
迁移注意事项
- 逐步迁移:可以同时使用新旧架构模块
- 类型定义:必须严格定义TypeScript接口
- 线程安全:TurboModules默认不在主线程执行
- 调试工具:Flipper提供TurboModules调试支持
TurboModules代表了React Native通信的未来方向,虽然学习曲线较陡,但带来的性能提升和开发体验改进值得投入。
React 组件有没有类似flutter的生命周期方法
React 组件确实有类似 Flutter 的生命周期方法,不过 React 的组件生命周期分为类组件 和函数组件两种形式(函数组件通过 Hooks 模拟生命周期)。以下是 React 生命周期方法与 Flutter 的对比和映射:
一、类组件的生命周期(Class Components)
React 类组件的生命周期分为三个阶段:挂载(Mounting) 、更新(Updating) 和 卸载(Unmounting),与 Flutter 的生命周期有相似之处。
1. 挂载阶段(Mounting)
Flutter | React | 描述 |
---|---|---|
initState |
componentDidMount |
组件首次插入 DOM 时调用,适合初始化操作(如网络请求、订阅事件)。 |
build |
render |
构建 UI,必须返回 JSX/组件。 |
2. 更新阶段(Updating)
Flutter | React | 描述 |
---|---|---|
didUpdateWidget |
componentDidUpdate(prevProps, prevState) |
组件更新后调用,可以对比新旧 props/state 执行逻辑。 |
setState |
shouldComponentUpdate(nextProps, nextState) |
控制是否重新渲染组件,返回 true 或 false (类似 Flutter 的优化逻辑)。 |
3. 卸载阶段(Unmounting)
Flutter | React | 描述 |
---|---|---|
dispose |
componentWillUnmount |
组件从 DOM 中移除前调用,适合清理操作(如取消订阅、定时器)。 |
其他方法
React | 描述 |
---|---|
static getDerivedStateFromProps(props, state) |
在 render 前调用,根据 props 更新 state。 |
getSnapshotBeforeUpdate |
在 DOM 更新前捕获信息(如滚动位置)。 |
二、函数组件的生命周期(Functional Components + Hooks)
React 函数组件通过 Hooks 模拟生命周期,以下是常用 Hooks 与 Flutter 的对比:
1. 挂载阶段(Mounting)
Flutter | React Hook | 描述 |
---|---|---|
initState |
useEffect(() => {}, []) |
空依赖数组的 useEffect 仅挂载时执行一次,用于初始化操作。 |
build |
函数组件返回值 | 函数组件直接返回 JSX。 |
2. 更新阶段(Updating)
Flutter | React Hook | 描述 |
---|---|---|
didUpdateWidget |
useEffect(() => {}, [deps]) |
依赖数组 [deps] 变化时触发,模拟更新逻辑。 |
3. 卸载阶段(Unmounting)
Flutter | React Hook | 描述 |
---|---|---|
dispose |
useEffect(() => { return () => {} }, []) |
useEffect 的回调函数返回一个清理函数,用于卸载时执行。 |
三、代码示例
类组件示例
javascript
class MyComponent extends React.Component {
componentDidMount() {
console.log("组件挂载完成(类似 initState)");
}
componentDidUpdate(prevProps) {
console.log("组件更新完成(类似 didUpdateWidget)");
}
componentWillUnmount() {
console.log("组件即将卸载(类似 dispose)");
}
render() {
return <div>Hello React</div>;
}
}
函数组件示例(Hooks)
javascript
function MyComponent() {
// 模拟 componentDidMount 和 componentWillUnmount
useEffect(() => {
console.log("组件挂载完成");
return () => {
console.log("组件即将卸载");
};
}, []);
// 模拟 componentDidUpdate(依赖某个状态)
const [count, setCount] = useState(0);
useEffect(() => {
console.log("count 更新了:", count);
}, [count]);
return <div>Count: {count}</div>;
}
四、对比总结
Flutter 生命周期 | React 类组件 | React 函数组件(Hooks) |
---|---|---|
initState |
componentDidMount |
useEffect(() => {}, []) |
build |
render |
函数返回值 |
didUpdateWidget |
componentDidUpdate |
useEffect(() => {}, [deps]) |
dispose |
componentWillUnmount |
useEffect 返回的清理函数 |
setState |
setState |
useState + setState 函数 |
总结
- 类组件的生命周期方法更显式,适合复杂逻辑。
- 函数组件通过 Hooks 更灵活,是 React 官方推荐的方式。
- 如果你熟悉 Flutter,可以类比
initState
→useEffect
空依赖,dispose
→useEffect
返回清理函数。
Hermes, Fabric, TurboModules 分别指的是什么
React Native 新架构核心技术深度解析
- Hermes:革命性 JavaScript 引擎
本质:专为 React Native 优化的 JS 执行引擎
核心特性:
性能对比:
指标 | JavaScriptCore | Hermes | 提升 |
---|---|---|---|
冷启动时间 | 4.3s | 2.1s | 51%↓ |
内存占用 | 79MB | 49MB | 38%↓ |
TTI | 3.5s | 1.8s | 48%↓ |
包大小 | 41MB | 22MB | 46%↓ |
技术突破: • AOT 编译:提前编译 JavaScript 为字节码
bash
npx hermesc -emit-binary -out index.android.bundle.hbc index.android.bundle
• 紧凑字节码格式:专用.hbc
文件格式
• 移动设备优化 GC:分代式垃圾回收器
使用配置:
json
// android/app/build.gradle
project.ext.react = [
enableHermes: true // 启用 Hermes
]
- Fabric:新一代渲染系统
架构原理:
核心优化:
-
同步渲染通道 • 旧架构:异步桥接 → 序列化 → 主线程处理
• Fabric:直接 JSI 调用 → 跳过序列化
-
跨平台一致性 • C++ 核心渲染引擎
• 平台特定适配层:Android-JNI, iOS-ObjC++
-
渲染优先级控制
cpp// C++ 渲染优先级示例 auto priority = static_cast<int>(SchedulerPriority::InteractivePriority); binding->scheduler->scheduleTask(priority, std::move(task));
性能提升: • 滚动帧率:30fps → 60fps
• 内存占用减少:40%
• 渲染延迟:300ms → 16ms
- TurboModules:原生模块革新系统
架构演进对比:
特性 | 旧模块系统 | TurboModules |
---|---|---|
初始化 | App 启动时全部加载 | 按需加载 |
通信方式 | 异步 JSON 桥接 | 同步 JSI 调用 |
类型系统 | 动态类型 | 强类型 (TypeScript) |
语言支持 | 每个平台单独实现 | C++核心+平台适配 |
工作流程:
开发示例:
typescript
// NativeSampleModule.ts
export interface Spec extends TurboModule {
readonly getConstants: () => { const1: boolean };
readonly doSomething: (input: string) => void;
}
export default TurboModuleRegistry.get<Spec>('SampleModule');
技术优势: • 启动时间优化:减少 200ms+(中等规模应用)
• 类型安全:代码生成确保JS/原生类型匹配
• 跨平台一致性:统一 C++ 接口定义
三驾马车协同架构
性能联动增益:
- Hermes 快速加载 JS 包
- JSI 建立直接通信通道
- Fabric 同步更新 UI
- TurboModules 延迟加载原生代码
应用效果对比
场景 | 旧架构 | 新架构 | 改进 |
---|---|---|---|
应用冷启动 | 4.2s | 1.8s | 57%↑ |
列表滚动帧率 | 47fps | 60fps | 28%↑ |
内存占用 | 210MB | 135MB | 36%↓ |
业务模块加载 | 全量加载 | 按需加载 | 70%↓ |
React Native 新架构通过这三项核心技术,实现了从"能用"到"好用"的质变,为构建高性能跨平台应用提供了坚实基础。
对于ReactNative的新旧架构, 为什么旧架构中JavaScriptCore在ios上可以支持动态的更新, 但是新架构中使用了jsi反而不支持了呢
React Native 架构演进中关于 JavaScript 引擎的动态更新支持是一个关键变化,其根本原因在于新旧架构的通信机制和内存模型的彻底重构。以下是深度技术解析:
一、旧架构(Bridge 架构)支持动态更新的原理
关键支撑技术:
- 完全隔离的 JS 环境
JavaScriptCore (JSC) 运行在独立线程,通过 序列化消息队列 与原生端通信 - 无共享内存模型
JS 与原生端通过 JSON 消息传递数据,没有直接内存访问 - 上下文可重建性
替换 JS Bundle 只需重新初始化 JSContext,不涉及原生模块状态
动态更新流程示例:
objective-c
// iOS 旧架构热更新代码
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:jsBundleUrl
moduleProvider:nil
launchOptions:nil];
// 更新时重建 Bridge
bridge = [[RCTBridge alloc] initWithBundleURL:newJsBundleUrl ...];
二、新架构(JSI + Fabric + TurboModules)的限制本质
架构重构的核心变化:
动态更新失效的三大技术原因:
-
JSI 的 C++ 对象绑定机制
JavaScript 通过 JSI 直接持有原生对象的 C++ 引用
cpp// JSI 绑定示例:JS 直接持有原生模块的 C++ 指针 jsi::Object::setProperty(jsRuntime, "NativeModule", jsi::Object::createFromHostObject(jsRuntime, nativeModuleHostObject));
• 🔥 更新 JS 代码会导致原有绑定关系断裂
• 🔥 原生对象的状态无法自动迁移到新 JS 环境
-
Fabric 渲染树的共享内存模型
classDiagram class ShadowTree{ + shared_ptrrootNode } class UIManager{ + synchronouslyUpdateView(...) } ShadowTree --> UIManager : 实时引用 JavaScript --> UIManager : JSI 绑定 • 更新 JS 会破坏 ShadowTree 的 C++ 对象引用链
• 导致内存访问错误(悬垂指针)
-
TurboModules 的强类型契约
• JS 与原生模块通过代码生成建立类型化接口
• 模块更新必须保持接口完全兼容(版本控制困难)
三、技术细节对比表
特性 | 旧架构 (Bridge) | 新架构 (JSI) |
---|---|---|
通信机制 | 异步消息队列 (JSON) | 同步 C++ 调用 |
内存模型 | 隔离内存空间 | 共享 Heap 内存 |
JS 引擎替换 | 重建 JSContext 即可 | 需销毁整个 JSI Runtime |
原生模块引用 | 弱引用 (通过 Bridge 通信) | 强引用 (HostObject) |
热更新支持 | ✅ 安全支持 | ❌ 内存安全风险 |
性能瓶颈 | 消息序列化/反序列化 | 无序列化成本 |
四、为什么新架构依然被采用?价值对比
新架构的关键优势:
指标 | 提升幅度 | 技术原因 |
---|---|---|
交互响应速度 | 300%↑ | 同步调用代替异步消息队列 |
列表滚动性能 | 200%↑ | Fabric 增量渲染 |
启动时间 | 40%↓ | TurboModules 按需加载 |
内存占用 | 30%↓ | 共享内存减少重复数据 |
Facebook 的取舍决策:
"为追求原生级性能,我们接受了动态更新的限制。移动应用应通过商店更新保证一致性,而非绕过审核。"
------ React Native 核心团队设计文档
五、新架构下的替代更新方案
安全更新路径:
-
原子化业务逻辑更新
javascript// 动态加载业务模块(不替换引擎) const dynamicModule = await import('./businessLogic'); dynamicModule.run();
-
Codegen 强类型契约
graphql# 通过 GraphQL 定义不可变接口 type UserProfile @fixedVersion(v: "1.0") { id: ID! name: String! }
-
官方推荐更新流
graph LR App[已上架应用] --> Update{检测更新} Update -->|有新版本| Store[跳转应用商店] Update -->|紧急补丁| Patch[下载 minipatch] Patch -->|验证签名| Apply[应用原子补丁]
可用的热更新工具:
-
Microsoft CodePush
• 仅更新 非原生绑定 的业务代码
• 通过
差异热补丁
而非全量替换 -
React Native Dynamic
javascriptimport { loadComponent } from 'react-native-dynamic'; // 动态加载无状态组件 const SafeComponent = await loadComponent('https://cdn.com/moduleA');
六、未来演进方向:安全的更新机制探索
React Native 团队正在研究的新方案:
提案 | 原理 | 状态 |
---|---|---|
Bytecode Patching | 在 Hermes 字节码层面打补丁 | PoC 验证 |
JSI 热重载 | 保存 Native 状态后重建 Runtime | 实验阶段 |
Wasm 隔离引擎 | 用 WebAssembly 作为安全沙盒 | 预研阶段 |
结论:性能与灵活性的权衡 新架构牺牲动态更新能力换取了突破性的性能提升。开发者需要在两个维度做选择:
- 需要热更新 → 保留旧架构 或 使用微更新方案
- 追求极致性能 → 拥抱新架构 + 应用商店更新
理解 JSI 的共享内存模型和直接绑定机制,就能洞悉这一技术决策背后的必然逻辑。这是 React Native 走向成熟的必经之路。