1.ReactNative源碼分析 - 概述
2.ReactNative源碼分析 - JavaScriptCore C語言篇
3.ReactNative源碼分析 - 啟動流程
4.ReactNative源碼分析 - 通信機制
5.ReactNative源碼分析 - 渲染原理
- 一、前言
- 二、React簡介
- 三、原生端與渲染流程相關(guān)的類
- 1.原生端UI組件體系
- 2.原生端UI管理者體系
- 四、原生控件信息如何傳遞到JS端
- 1.RCTViewManager定義導(dǎo)出屬性
- 2.RCTUIManager收集原生控件信息
- 3.UIManager獲取原生控件信息
- 4.原生控件信息處理、獲取
- 五、渲染流程
- 1.注冊JS組件
- 2.運行JS組件,執(zhí)行渲染
- 3.獲取React計算結(jié)果
- 4.驅(qū)動Native執(zhí)行渲染流程
- 六、JS端調(diào)用原生控件導(dǎo)出函數(shù)
- 七、結(jié)語
一、前言
- 1.本文主要分析JS端業(yè)務(wù)層編寫的React組件最終如何渲染為原生控件。渲染原理的完整流程梳理起來有點困難,難在此處開始與React銜接,而React又是一個非常龐大的框架。本文對React不過多展開討論,首先筆者并不擅長React,沒有從源碼層面完整梳理過它的工作流程,再者這個流程分析起來又是一個系列的文章。本文以React最終產(chǎn)生的結(jié)果(組件信息JSON數(shù)據(jù))為基礎(chǔ),分析它如何驅(qū)動原生端進行UI繪制。
- 2.ReactNative以React的模式開發(fā),最終通過底層Bridge驅(qū)動原生端進行相應(yīng)操作。從UI渲染原理層面分析,大致流程是:
- 承載業(yè)務(wù)邏輯的React組件最終生成描述原生控件信息的JSON數(shù)據(jù),通過底層Bridge傳遞到原生端,驅(qū)動原模塊繪制原生UI控件;
- 用戶行為觸發(fā)原生UI控件交互事件/回調(diào)事件從原生端傳遞到JS端以處理業(yè)務(wù)邏輯,業(yè)務(wù)邏輯處理結(jié)果通常表現(xiàn)為原生控件信息的更新,新的信息數(shù)據(jù)會再次傳遞到原生端,驅(qū)動原生模塊進行新一輪UI更新……
- 3.本文很多邏輯是基于通信機制(eg:原生模塊信息導(dǎo)出流程、Native&JS通信),本文假設(shè)你已了解這些知識。
二、React簡介
import React from 'react';
import {View, TouchableOpacity} from 'react-native';
export default class TestRender extends React.Component {
render() {
return (
<View style={{ backgroundColor: 'white', paddingTop: 64,}} testID="white">
<View style={{ backgroundColor: "yellow",width: 50, height: 50}} testID="white-yellow"/>
<View
style={{backgroundColor: 'green',width: 100, height: 100,justifyContent:"center",alignItems:"center"}} testID="white-green"
>
<View style={{backgroundColor: 'red', width: 50, height: 50,}} testID="white-green-red"></View>
</View>
<TouchableOpacity
style={{backgroundColor: 'blue', width: 50, height: 50}}
testID="white-blue"
onPress={() => console.log("onPress") }
/>
</View>
);
}
}
上述JSX轉(zhuǎn)碼后變成如下代碼。每個React元素轉(zhuǎn)化為React.createElement
函數(shù)調(diào)用,最終返回一個描述組件信息的JS對象,其中嵌套關(guān)系表示父子組件關(guān)系。
export default class TestRender extends React.Component {
render() {
return
React.createElement(View, {
style: {
backgroundColor: 'white',
paddingTop: 64
},
testID: "white"
}, React.createElement(View, {
style: {
backgroundColor: "yellow",
width: 50,
height: 50
},
testID: "white-yellow"
}),
React.createElement(View, {
style: {
backgroundColor: 'green',
width: 100,
height: 100,
justifyContent: "center",
alignItems: "center"
},
testID: "white-green"
}, React.createElement(View, {
style: {
backgroundColor: 'red',
width: 50,
height: 50
},
testID: "white-green-red"
})),
React.createElement(TouchableOpacity, {
style: {
backgroundColor: 'blue',
width: 50,
height: 50
},
testID: "white-blue",
onPress: () => console.log("onPress")})
);
}
}
- React Element即React元素是React應(yīng)用的最小單元。
const element = <h1>Hello, world</h1>;
-
ReactComponent即React組件是獨立、可復(fù)用的UI模塊,它可同時包含UI渲染、業(yè)務(wù)邏輯。組件是由元素構(gòu)成的,React應(yīng)用由一系列的React組件組合起來的。
關(guān)于React的源碼分析,可以參考React源碼解析(一):組件的實現(xiàn)與掛載,結(jié)合React源碼分析React.createElement
最終返回JS對象的過程。
三、原生端與渲染流程相關(guān)的類
1.原生端UI組件體系
原生端與UI渲染相關(guān)的視圖類體系如圖所示,ReactNative封裝了一系列原生控件,把控件信息導(dǎo)出到JS端并且包裝為對應(yīng)的組件供JS業(yè)務(wù)層使用,我們暫且把前者稱為控件
,后者稱為組件
,兩者在Native端、JS端一一對應(yīng)。
-
RCTComponent
協(xié)議定義了一套標(biāo)準(zhǔn)接口,作為視圖樹的邏輯結(jié)點。RCTShadowView
和UIView
遵守該協(xié)議,使得對這兩者組成的樹View Tree、ShadowView Tree的操作更統(tǒng)一。 - ReactNative封裝的原生控件(RCTView、RCTTextView、RCTImageView……)最終都繼承自
UIView
,即默認實現(xiàn)了RCTComponent
協(xié)議;
RCTShadowView
可理解為布局結(jié)點,js端傳遞到原生端的控件信息,與布局相關(guān)的會賦值給它,并通過它驅(qū)動yoga計算出組件的布局信息(frame、center);
總的來說:在ReactNative渲染過程中,ShadowView與UIView是對應(yīng)關(guān)系,每個UIView都有一個與之對應(yīng)的ShadowView。UIView是最終渲染出來的視圖、ShadowView是它的布局結(jié)點,負責(zé)為它計算布局信息。渲染過程中存在兩棵樹:控件樹、布局結(jié)點樹,兩者的結(jié)點一一對應(yīng),互為鏡子。 -
RCTRootView
根視圖是ReactNative暴露給原生端使用的控件,可以像普通UIView
一樣使用。主要負責(zé)運行js端通過AppRegistry.registerComponent
注冊的根組件。混合開發(fā)應(yīng)用中通常是存在多個RCTRootView,共用同一個底層Bridge。 -
RCTRootContentView
作為RCTRootView
的子視圖,RCTRootView
只是負責(zé)運行JS組件,后續(xù)JS端驅(qū)動創(chuàng)建的控件繪制到RootContentView上。 -
RCTRootContentView
與RCTRootShadowView
是對應(yīng)關(guān)系。
2.原生端UI管理者體系
原生端與UI渲染相關(guān)的管理者類體系如圖所示
-
RCTUIManager、RCTViewManager都是原生模塊,由于懶加載機制他們實際上是單例,最終都通過底層Bridge把模塊信息導(dǎo)出到JS端使用。
- RCTViewManager與導(dǎo)出控件一一對應(yīng),負責(zé)管理對應(yīng)的原生控件,包括定義要導(dǎo)出的控件屬性、接口;接收通過
RCT_CUSTOM_VIEW_PROPERTY
導(dǎo)出的屬性賦值;創(chuàng)建控件、創(chuàng)建布局結(jié)點…… - RCTUIManager是渲染流程中的集大成者,它接收JS端發(fā)送過來的指令,執(zhí)行UI控件創(chuàng)建/移除、屬性設(shè)置/更新、動畫、調(diào)用原生控件導(dǎo)出函數(shù)等。
RCTViewManager創(chuàng)建原生控件并提供給RCTUIManager,RCTUIManager則會反過來委托它們在需要的時候去設(shè)置和更新視圖的屬性,甚至調(diào)用控件導(dǎo)出函數(shù)。
- RCTViewManager與導(dǎo)出控件一一對應(yīng),負責(zé)管理對應(yīng)的原生控件,包括定義要導(dǎo)出的控件屬性、接口;接收通過
-
RCTComponentData包裝每一個RCTViewManager,RCTUIManager通過它來:
- 1.創(chuàng)建原生控件、布局結(jié)點;
- 2.設(shè)置控件屬性、布局屬性;
- 3.獲取控件導(dǎo)出屬性集合。
四、原生控件信息如何傳遞到JS端
這里以RCTWKWebView為例分析原生控件信息導(dǎo)出流程,理解RCTWKWebView的封裝,你一定可以封裝自定義原生控件供JS端使用
1.RCTViewManager定義導(dǎo)出屬性
RCTViewManager控件屬性導(dǎo)出宏生成的對應(yīng)函數(shù),用于后續(xù)獲取導(dǎo)出屬性信息
-
RCT_EXPORT_VIEW_PROPERTY
生成屬性名、屬性類型獲取函數(shù)。后續(xù)通過Runtime獲取以propConfig_
為前綴的函數(shù)并調(diào)用以獲取這些信息。 -
RCT_EXPORT_SHADOW_PROPERTY
生成布局屬性名、屬性類型獲取函數(shù)。后序通過Runtime獲取以propConfigShadow_
為前綴的函數(shù)并調(diào)用以獲取這些信息。 -
RCT_CUSTOM_VIEW_PROPERTY
生成屬性名、屬性類型獲取函數(shù),并生成屬性setter
// RCTWKWebViewManager.m
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, RCTWKWebView) {
view.bounces = json == nil ? true : [RCTConvert BOOL: json];
}
// 生成屬性type keypath獲取函數(shù)
+ (NSArray<NSString *> *)propConfig_source {
return @[@"NSDictionary"];
}
+ (NSArray<NSString *> *)propConfig_onLoadingStart {
return @[@"RCTDirectEventBlock"];
}
+ (NSArray<NSString *> *)propConfig_bounces {
return @[@"BOOL", @"__custom__"];
}
- (void)set_bounces:(id)json forView:(RCTWKWebView *)view withDefaultView:(RCTWKWebView *)defaultView {
view.bounces = json == ((void *)0) ? 1 : [RCTConvert BOOL: json];
}
// RCTViewManager.m
RCT_EXPORT_SHADOW_PROPERTY(marginTop, YGValue)
+ (NSArray<NSString *> *)propConfigShadow_marginTop {
return @[@"YGValue"];
}
2.RCTUIManager收集原生控件信息
-
RCTUIManager
實例化后執(zhí)行setBridge:
,會收集所有的原生控件管理者RCTViewManager
,并創(chuàng)建對應(yīng)的RCTComponentData
。
// RCTUIManager.m
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
...
// 收集RCTViewManager
_componentDataByName = [NSMutableDictionary new];
for (Class moduleClass in _bridge.moduleClasses) {
if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass bridge:_bridge];
_componentDataByName[componentData.name] = componentData;
}
}
}
-
RCTComponentData
包裝了RCTViewManager
,因此它可獲取原生控件的一切導(dǎo)出信息。
RCTViewManager
通過宏導(dǎo)導(dǎo)出屬性/布局屬性,分別生成的導(dǎo)出函數(shù)前綴propConfig_
、propConfigShadow_
。
函數(shù)viewConfig
使用Runtime遍歷ViewManager所有類函數(shù)以獲取導(dǎo)出屬性信息,收集以propConfig
開頭的函數(shù),截取函數(shù)名字符串_
后面的內(nèi)容獲取屬性名;調(diào)用函數(shù)獲取屬性類型。
// RCTComponentData.m
// 獲取控件導(dǎo)出屬性
- (NSDictionary<NSString *, id> *)viewConfig
{
NSMutableArray<NSString *> *bubblingEvents = [NSMutableArray new];
NSMutableArray<NSString *> *directEvents = [NSMutableArray new];
// Runtime獲取屬性/布局屬性導(dǎo)出宏生成的函數(shù),函數(shù)前綴`propConfig`
unsigned int count = 0;
NSMutableDictionary *propTypes = [NSMutableDictionary new];
Method *methods = class_copyMethodList(object_getClass(_managerClass), &count);
for (unsigned int i = 0; i < count; i++) {
SEL selector = method_getName(methods[i]);
const char *selectorName = sel_getName(selector);
// 過濾無propConfig前綴的函數(shù)
if (strncmp(selectorName, "propConfig", strlen("propConfig")) != 0) {
continue;
}
// 控件導(dǎo)出的屬性/布局屬性生成的函數(shù)分別是propConfig_* propConfigShadow_* ,定位'_'的位置,進而獲取屬性名
const char *underscorePos = strchr(selectorName + strlen("propConfig"), '_');
if (!underscorePos) {
continue;
}
NSString *name = @(underscorePos + 1);
// 調(diào)用函數(shù)獲取屬性類型
NSString *type = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector)[0];
// 回調(diào)類型屬性,用BOOL代替
if ([type isEqualToString:@"RCTBubblingEventBlock"]) {
[bubblingEvents addObject:RCTNormalizeInputEventName(name)];
propTypes[name] = @"BOOL";
} else if ([type isEqualToString:@"RCTDirectEventBlock"]) {
[directEvents addObject:RCTNormalizeInputEventName(name)];
propTypes[name] = @"BOOL";
} else {
propTypes[name] = type;
}
}
free(methods);
Class superClass = [_managerClass superclass];
return @{
@"propTypes": propTypes,
@"directEvents": directEvents,
@"bubblingEvents": bubblingEvents,
@"baseModuleName": superClass == [NSObject class] ? (id)kCFNull : moduleNameForClass(superClass),
};
}
例子RCTWKWebViewManager返回結(jié)果如下
propTypes存放所有導(dǎo)出屬性的屬性名、類型;
directEvents、bubblingEvents存放回調(diào)類型;
baseModuleName表示控件基類,用于在JS端添加基類導(dǎo)出屬性以得到完整屬性表。
{
"propTypes": {
"allowsInlineMediaPlayback": "BOOL",
"automaticallyAdjustContentInsets":"BOOL",
"bounces" : "BOOL",
"contentInset": "UIEdgeInsets",
...
"onMessage":""BOOL,
"onShouldStartLoadWithRequest":"BOOL"
},
"directEvents": [
"topLoadingStart",
"topLoadingFinish",
"topLoadingError",
"topMessage",
"topShouldStartLoadWithRequest"
],
"bubblingEvents": [],
"baseModuleName": "RCTViewManager"
}
-
RCTUIManager
原生模塊導(dǎo)出常量constantsToExport
用來導(dǎo)出所有原生控件屬性信息。
這里遍歷所有原生控件以收集屬性信息,最終加工為一個包含所有原生控件屬性信息的集合,導(dǎo)出到JS端。
// 收集所有導(dǎo)出供JS端使用的原生控件的信息
- (NSDictionary<NSString *, id> *)constantsToExport
{
return [self getConstants];
}
- (NSDictionary<NSString *, id> *)getConstants
{
NSMutableDictionary<NSString *, NSDictionary *> *constants = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSDictionary *> *directEvents = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents = [NSMutableDictionary new];
[_componentDataByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
NSMutableDictionary<NSString *, id> *moduleConstants = moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
constants[name] = moduleConstants;
}];
return constants;
}
static NSMutableDictionary<NSString *, id> *moduleConstantsForComponent(
NSMutableDictionary<NSString *, NSDictionary *> *directEvents,
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents,
RCTComponentData *componentData) {
NSMutableDictionary<NSString *, id> *moduleConstants = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEventTypes = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSDictionary *> *directEventTypes = [NSMutableDictionary new];
moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);
NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes;
moduleConstants[@"directEventTypes"] = directEventTypes;
for (NSString *eventName in viewConfig[@"directEvents"]) {
if (!directEvents[eventName]) {
directEvents[eventName] = @{
@"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
};
}
directEventTypes[eventName] = directEvents[eventName];
}
// Add bubbling events
for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
if (!bubblingEvents[eventName]) {
NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
bubblingEvents[eventName] = @{
@"phasedRegistrationNames": @{
@"bubbled": bubbleName,
@"captured": [bubbleName stringByAppendingString:@"Capture"],
}
};
}
bubblingEventTypes[eventName] = bubblingEvents[eventName];
}
return moduleConstants;
}
最終生成一張表包含所有原生控件導(dǎo)出屬性信息<原生控件名:導(dǎo)出信息>
,格式如下。作為RCTUIManager
原生模塊常量導(dǎo)出到JS端,原生模塊信息導(dǎo)出流程詳解通信機制。
{
"RCTWKWebView": {
"Manager": "WKWebViewManager",
"NativeProps" : {
"allowsInlineMediaPlayback" :"BOOL",
...
"onLoadingError" : "BOOL",
"onLoadingFinish" : "",
"onLoadingStart" : "BOOL",
"onMessage" : "BOOL",
"source" : "NSDictionary",
},
"baseModuleName" : "RCTView",
"bubblingEventTypes" : { };
"directEventTypes" {
"topLoadingError" : { "registrationName" : "onLoadingError",},
"topLoadingFinish" : { "registrationName" : "onLoadingFinish",},
...
},
},
"RCTView": {...},
...
}
3.UIManager獲取原生控件信息
-
注:
通常情況下:ReactNative原生模塊(特別是邏輯模塊)在JS端會包裝為對應(yīng)的JS模塊,例如RCTUIManager在JS端包裝為UIManager。 這樣做有諸多好處,首先Native、JS端一一對應(yīng),其次是更清晰的模塊化,還是可以緩存原生模塊信息。
原生控件管理者ViewManager通常不會包裝,原因是原生模塊信息幾乎都通過UIManager獲取,不會直接使用。但原生控件信息導(dǎo)出到JS端,會包裝為對應(yīng)組件,例如RCTWKWebView在JS端會包裝為WebView.ios.js
,這么做的好處同上。
- UIManager實際上就做一件事情:加載所有原生控件信息并緩存,供調(diào)用者使用。
上文分析過RCTUIManager
導(dǎo)出常量是所有原生控件信息,根據(jù)通信機制:原生模塊信息如何導(dǎo)入到JS端,可知相應(yīng)的JS端模塊UIManager能獲取到這些原生控件信息。UIManager.js
腳本運行時,會遍歷UIManager
模塊信息,找出原生控件信息(判斷是否有Manager屬性),并且進行加工、緩存。
加工的過程很簡單:獲取相應(yīng)ViewManager并把導(dǎo)出函數(shù)getter、常量getter添加到原生控件信息中。至此,JS端緩存的原生控件信息相對完整
,包含原生控件的導(dǎo)出屬性、函數(shù)、常量、管理者ViewManager、基類。
UIManager把原生控件信息都命名為ViewManagerConfig
,即原生控件管理者信息,其實是就是原生控件信息。
// UIManager.js
const {UIManager} = NativeModules;
// 存放所有原生控件信息 { viewName: viewConfig }
const viewManagerConfigs = {};
// 獲取控件信息
UIManager.getViewManagerConfig = function(viewManagerName: string) {
const config = viewManagerConfigs[viewManagerName];
if (config) {
return config;
}
// 防御作用,獲取不到viewConfig時,使用同步函數(shù)獲取試圖獲取
if (UIManager.lazilyLoadView && !triedLoadingConfig.has(viewManagerName)) {
...
}
return viewManagerConfigs[viewManagerName];
};
// 原生控件配置信息viewConfig
function lazifyViewManagerConfig(viewName) {
const viewConfig = UIManager[viewName];
if (viewConfig.Manager) {
// viewConfig存入組件信息表viewManagerConfigs
viewManagerConfigs[viewName] = viewConfig;
// 定義取值函數(shù)getter,用于獲取Constants,即ViewManager導(dǎo)出常量
defineLazyObjectProperty(viewConfig, 'Constants', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const constants = {};
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value !== 'function') {
constants[key] = value;
}
});
return constants;
},
});
// 定義取值函數(shù),用于獲取Commands,即ViewManager導(dǎo)出函數(shù)(索引)
defineLazyObjectProperty(viewConfig, 'Commands', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const commands = {};
let index = 0;
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value === 'function') {
commands[key] = index++;
}
});
return commands;
},
});
}
}
// 遍歷UIManager模塊信息,獲取、加工組件信息,并緩存
if (Platform.OS === 'ios') {
Object.keys(UIManager).forEach(viewName => {
lazifyViewManagerConfig(viewName);
});
}
// 導(dǎo)出UIManager對象,單例
module.exports = UIManager;
最終原生控件信息存放在viewManagerConfigs
,格式如下
{
RCTWKWebView: {
Commands: {
getConstants: 7,
goBack: 1,
goForward: 2,
...
},
Constants: {},
Manager: "WKWebViewManager",
NativeProps: {
allowsInlineMediaPlayback: "BOOL",
bounces: "BOOL",
...
onMessage: "BOOL",
},
baseModuleName: "RCTView",
bubblingEventTypes:{ },
directEventTypes: {
topLoadingStart: {registrationName: "onLoadingStart"},
topMessage: {registrationName: "onMessage"},
...
}
},
RCTView: {
...
}
...
}
4.原生控件信息處理、獲取
上訴流程中,UIManager單例收集所有原生控件信息,但還不能直接使用,下面進一步分析信息的處理、使用流程。
- 1.原生控件在JS端封裝對應(yīng)JS組件,會執(zhí)行
requireNativeComponent
函數(shù),傳入原生端導(dǎo)入的原生組件名。
// WebView.ios.js
const RCTWKWebView = requireNativeComponent('RCTWKWebView');
該函數(shù)其實是createReactNativeComponentClass
函數(shù)的一層包裝,傳入一個回調(diào)函數(shù)。
// requireNativeComponent.js
const requireNativeComponent = (uiViewClassName: string): string =>
createReactNativeComponentClass(uiViewClassName, () =>
getNativeComponentAttributes(uiViewClassName),
);
getNativeComponentAttributes
,負責(zé)從UIManager獲取原生控件信息,并做進一步加工:添加基類屬性;添加屬性比較/處理函數(shù)。最終得到完整
的原生控件信息。它作為回調(diào)函數(shù)存放到ReactNativeViewConfigRegistry
中,以懶加載完整的原生控件信息。
// 獲取原生控件信息
// getNativeComponentAttributes.js
function getNativeComponentAttributes(uiViewClassName: string) {
// 從UIManager獲取原生控件信息
const viewConfig = UIManager.getViewManagerConfig(uiViewClassName);
// 添加基類原生模塊信息
let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig;
let nativeProps = viewConfig.NativeProps;
while (baseModuleName) {
const baseModule = UIManager.getViewManagerConfig(baseModuleName);
...
}
// 添加屬性比較、處理函數(shù)
const validAttributes = {};
for (const key in nativeProps) {
const typeName = nativeProps[key];
const diff = getDifferForType(typeName);
const process = getProcessorForType(typeName);
validAttributes[key] =
diff == null && process == null ? true : {diff, process};
}
Object.assign(viewConfig, {
uiViewClassName,
validAttributes,
bubblingEventTypes,
directEventTypes,
});
return viewConfig;
}
- 2.createReactNativeComponentClass函數(shù),則調(diào)用
ReactNativeViewConfigRegistry
模塊的register
函數(shù),注冊原生控件信息獲取回調(diào)。
// 注冊原生控件信息獲取回調(diào)
// createReactNativeComponentClass.js
const createReactNativeComponentClass = function(
name: string,
callback: ViewConfigGetter,
): string {
return register(name, callback);
};
ReactNativeViewConfigRegistry
模塊就是一個原生控件信息注冊機,它包含所有需要的原生控件信息,并且是懶加載機制。
調(diào)用register
函數(shù)注冊原生控件信息獲取回調(diào),并原路返回傳入的view name,作為組件名書寫JSX。
// ReactNativeViewConfigRegistry.js
const viewConfigCallbacks = new Map(); // 原生控件信息獲取回調(diào)函數(shù)表
const viewConfigs = new Map(); // 原生控件信息表
// 注冊原生控件信息獲取回調(diào),用于從UIManager獲取組件信息
exports.register = function(name: string, callback: ViewConfigGetter): string {
viewConfigCallbacks.set(name, callback);
return name;
};
// 獲取原生控件信息,懶加載機制
exports.get = function(name: string): ReactNativeBaseComponentViewConfig<> {
let viewConfig;
if (!viewConfigs.has(name)) {
const callback = viewConfigCallbacks.get(name);
viewConfigCallbacks.set(name, null);
viewConfig = callback();
processEventTypes(viewConfig);
viewConfigs.set(name, viewConfig);
} else {
viewConfig = viewConfigs.get(name);
}
return viewConfig;
};
總結(jié)
1.封裝組件時調(diào)用requireNativeComponent
注冊一個原生控件信息獲取/處理回調(diào)到原生控件信息注冊機ReactNativeViewConfigRegistry
;
2.真正執(zhí)行渲染,使用到對應(yīng)組件,調(diào)用get
函數(shù)從原生控件信息注冊機獲取原生控件信息,若控件信息存在,則直接使用;否則執(zhí)行原生控件信息獲取/處理回調(diào),以得到完整并原生控件信息并緩存。
這一流程驅(qū)使原生控件數(shù)據(jù)進行加工,從UIManager
流行ReactNativeViewConfigRegistry
,并且具備懶加載特性。
五、渲染流程
1.注冊JS組件
- ReactNative項目會在JS執(zhí)行的入口文件,注冊根組件到
AppRegistry
// index.js
AppRegistry.registerComponent('Main', () => Main);
-
AppRegistry
:App注冊機,準(zhǔn)確來說是根組件注冊機,因為一個應(yīng)用中可存在多個根組件。AppRegistry
主要負責(zé):根組件的注冊、運行、卸載。它注冊到BatchedBridge
作為JS模塊,可供原生端調(diào)用。- 1.JS端通過
registerComponent
注冊根組件到runnables
表中; - 2.原生端通過
runApplication
運行指定的根組件; - 3.原生端在組件銷毀時,通過
unmountApplicationComponentAtRootTag
卸載指定的根組件;
- 1.JS端通過
// AppRegistry.js
const runnables: Runnables = {};
const AppRegistry = {
// 注冊組件
registerComponent(appKey: string, componentProvider: ComponentProvider, section?: boolean): string {
runnables[appKey] = {
componentProvider,
run: ...
};
return appKey;
},
// 運行組件:獲取注冊表中的組件,運行
runApplication(appKey: string, appParameters: any): void {
runnables[appKey].run(appParameters);
},
// 卸載組件
unmountApplicationComponentAtRootTag(rootTag: number): void {
ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag);
},
}
// 注冊JS模塊 AppRegistry
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);
2.運行JS組件,執(zhí)行渲染
- 原生端
RCTRootView
在js bundle執(zhí)行完畢后,運行指定的JS組件
// RCTRootView.m
- (void)runApplication:(RCTBridge *)bridge
{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _appProperties ?: @{},
};
// 調(diào)用js模塊AppRegistry runApplication,運行組件
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}
- 上述操作會執(zhí)行JS模塊
AppRegistry
運行根組件接口runApplication
。根據(jù)組件名appKey獲取對應(yīng)的根組件,調(diào)用run
運行根組件,最終執(zhí)行renderApplication
進行渲染。
// AppRegistry.js
runnables[appKey] = {
// 組件獲取函數(shù)
componentProvider,
// 組件運行函數(shù), 執(zhí)行render
run: appParameters => {
renderApplication(
componentProviderInstrumentationHook(componentProvider),
appParameters.initialProps, // 原生層傳遞過來的初始化屬性
appParameters.rootTag, // rootTag
wrapperComponentProvider && wrapperComponentProvider(appParameters),
appParameters.fabric,
);
},
};
-
renderApplication
主要負責(zé)- 使用
AppContainer
包裝根組件。AppContainer
主要是包含了YellowBox
之類的調(diào)試組件; - 調(diào)用
ReactNative
的render
函數(shù)進行渲染。
ReactFabric
應(yīng)該是React的正在重構(gòu)的下一代渲染機制,目前還沒有生效。
- 使用
// renderApplication.js
function renderApplication<Props: Object>(
RootComponent: React.ComponentType<Props>, // 根組件
initialProps: Props,
rootTag: any,
WrapperComponent?: ?React.ComponentType<*>,
fabric?: boolean,
showFabricIndicator?: boolean,)
{
// 根組件RootComponent嵌入容器組件AppContainer(用于包裝yellowBox等調(diào)試組件)
let renderable = (
<AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>
);
if (fabric) {
require('ReactFabric').render(renderable, rootTag);
} else {
// 執(zhí)行渲染
require('ReactNative').render(renderable, rootTag);
}
}
-
ReactNative
根據(jù)運行環(huán)境執(zhí)行相應(yīng)腳本。調(diào)試環(huán)境使用ReactNativeRenderer-dev
;生產(chǎn)環(huán)境使用ReactNativeRenderer-prod
。這里分析調(diào)試環(huán)境。
// ReactNative.js
if (__DEV__) {
ReactNative = require('ReactNativeRenderer-dev');
} else {
ReactNative = require('ReactNativeRenderer-prod');
}
- 再往下追蹤,發(fā)現(xiàn)上述兩個文件的代碼量都是萬行級的,這顯然不是人看的代碼。當(dāng)然筆者相信FaceBook工程師不會寫可讀性這么差的代碼。經(jīng)探索發(fā)現(xiàn)這兩個不是源文件,真正的源文件可在React中查看。
React項目十分龐大,本文不作展開,我們只需知道它采用monorepo管理方式,一個項目拆分為多個獨立包的。React中應(yīng)用于ReactNative的包主要是如下紅框所示,負責(zé)渲染、事件系統(tǒng)、協(xié)調(diào)等,使得React與ReactNative能鏈接起來,詳見源碼概覽。
早在遠古時期react-native-0.45.0,React的源碼是直接引入ReactNative,這種應(yīng)該比較好梳理邏輯。畢竟在無法調(diào)試代碼的情況下研究源碼太考驗想象力了。
3.獲取React計算結(jié)果
React與ReactNative的銜接是個大工程,此處省略一萬字,直接分析兩者最終的計算結(jié)果如何驅(qū)動原生端執(zhí)行UI渲染。
Debug環(huán)境下,調(diào)用ReactNativeRenderer-dev.js
導(dǎo)出對象ReactNativeRenderer
的渲染函數(shù)render
執(zhí)行渲染。經(jīng)過一個長長的調(diào)用棧之后會執(zhí)行根組件的render
(可在根組件render打斷點追蹤調(diào)用棧),返回一個根React Element。這就是React計算結(jié)果了,即我們用React編寫的JSX代碼的最終產(chǎn)物。
DEMO中的例子TestRender
,最終返回React Element對象如下(省略了部分信息,testID為測試id),這其實就是一棵樹,包含原生控件信息。
{
$$typeof: Symbol(react.element),
type: {$$typeof: Symbol(react.forward_ref), displayName: "View",},
props: {
children: [
{
$$typeof: Symbol(react.element),
type: {$$typeof: Symbol(react.forward_ref), displayName: "View"},
props: {
style: {backgroundColor: "yellow", width: 50, height: 50},
testID: "white-yellow"
},
},
{
$$typeof: Symbol(react.element),
type: {$$typeof: Symbol(react.forward_ref), displayName: "View"},
props:{
children: [{
$$typeof: Symbol(react.element),
props:{
style: {backgroundColor: "red", width: 50, height: 50},
testID: "white-green-red"
},
type: {$$typeof: Symbol(react.forward_ref), displayName: "View"}
}],
style: {backgroundColor: "green", width: 100, height: 100},
testID: "white-green",
},
},
{
$$typeof: Symbol(react.element),
type: {displayName: "TouchableOpacity"},
props: {
style: {backgroundColor: "blue", width: 50, height: 50},
testID: "white-blue",
activeOpacity: 0.2,
onPress: ?
},
}
],
style: {backgroundColor: "white", paddingTop: 64},
testID: "white",
},
}
4.驅(qū)動Native執(zhí)行渲染流程
-
0.RCTUIManager簡析
前面提到RCTUIManager
是渲染流程的集大成者,分析渲染流程,得先分析RCTUIManager的多個容器和隊列ShadowQueue
。渲染流程同樣采用批處理思想。_rootViewTags:根控件集合,存放所有根控件,UI布局計算就是從根控件開始遞歸結(jié)點樹。
_pendingUIBlocks:UI操作集合,創(chuàng)建控件、設(shè)置屬性、改變視圖層級、設(shè)置布局信息…等UI操作會先緩存,在特定時機派發(fā)到主線程。
_shadowViewRegistry:布局結(jié)點集合,存放整個應(yīng)用的布局結(jié)點
_viewRegistry:原生控件集合,存放整個應(yīng)用的原生控件
_shadowViewsWithUpdatedProps:已更新屬性的shadowView集合,記錄更新了屬性值的布局結(jié)點,布局時機一到會遍歷該集合逐一更新布局結(jié)點的YOGA值
_shadowViewsWithUpdatedChildren:已更新子控件的shadowView集合,記錄子控件有變動的布局結(jié)點,布局時機一到會遍歷該集合逐一更新控件的子控件。
渲染過程大多數(shù)操作發(fā)生在隊列
ShadowQueue
中,它是串行隊列,作為RCTUIManager
導(dǎo)出函數(shù)的執(zhí)行隊列。
上述容器的使用有嚴(yán)格的線程規(guī)定,以保證線程安全和UI操作在主線程執(zhí)行。所有容器的創(chuàng)建、銷毀在主線程,使用(增刪改查)則有差別,_viewRegistry涉及UI操作(創(chuàng)建控件)需要在主線程使用,其他容器其實都是從數(shù)據(jù)層面的操作,并非正在執(zhí)行UI操作,因此都在ShadowQueue執(zhí)行;布局計算也在ShadowQueue執(zhí)行(異步計算布局結(jié)果)。
注:通過簡潔的線程管理來實現(xiàn):JS&Native(ReactNativeRenderer&RCTUIManager)的交互、控件層次結(jié)構(gòu)(數(shù)據(jù)層面)更新、布局結(jié)點屬性更新、布局計算等都在
ShadowQueue
隊列執(zhí)行;控件屬性更新、更改視圖層級、渲染等正在操作UI則主線程執(zhí)行。即:非UI操作在ShadowQueue
執(zhí)行,操作UI在主線程進行,結(jié)合批處理機制,達到為主線程減負、高效渲染的目的。
@implementation RCTUIManager
{
// 根控件集合 reactTag <reactTag>
NSMutableSet<NSNumber *> *_rootViewTags;
// 暫存UI操作;
NSMutableArray<RCTViewManagerUIBlock> *_pendingUIBlocks;
// 布局結(jié)點集合 { reactTag : RCTShadowView }
NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
// 控件集合 {reactTag : UIView}
NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
// 已更新屬性的shadowView集合 { RCTShadowView: [props key] }
NSMapTable<RCTShadowView *, NSArray<NSString *> *> *_shadowViewsWithUpdatedProps; // UIManager queue only.
// 已更新子控件的shadowView集合
NSHashTable<RCTShadowView *> *_shadowViewsWithUpdatedChildren; // UIManager queue only.
...
}
1.JS端根據(jù)計算結(jié)果,驅(qū)動原生端創(chuàng)建原生控件
- JS端根據(jù)計算結(jié)果,驅(qū)動原生端創(chuàng)建原生控件
- 生成reactTag,作為每個控件的標(biāo)識;
- 從原生控件信息注冊機獲取對應(yīng)的控件信息,并處理控件屬性(過濾非法屬性……);
- 調(diào)用原生模塊UIManager創(chuàng)建原生控件
// ReactNativeRenderer-dev.js
function createInstance(
type,
props,
rootContainerInstance,
hostContext,
internalInstanceHandle
) {
// 創(chuàng)建組件tag
var tag = allocateTag();
// 獲取原生控件信息
var viewConfig = ReactNativeViewConfigRegistry.get(type);
var updatePayload = create(props, viewConfig.validAttributes);
// 調(diào)用原生模塊,創(chuàng)建原生控件
UIManager.createView(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload // props
);
var component = new ReactNativeFiberHostComponent(tag, viewConfig);
precacheFiberNode(internalInstanceHandle, tag);
updateFiberProps(tag, props);
return component;
}
- 原生端創(chuàng)建原生控件和對應(yīng)的布局結(jié)點;設(shè)置布局屬性、控件屬性值;
設(shè)置屬性的過程相對冗長,詳見源碼,大致流程是:根據(jù)屬性名、是否是布局結(jié)點(isShadowView),構(gòu)建導(dǎo)出屬性的導(dǎo)出函數(shù)SEL以獲得屬性類型,進而構(gòu)建出屬性setter,并包裝為Block,執(zhí)行Block設(shè)置屬性,最終控件屬性值設(shè)置到原生控件,布局屬性值設(shè)置到布局結(jié)點ShadowView
。
// RCTUIManager.m
// 創(chuàng)建原生控件
RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag
viewName:(NSString *)viewName
rootTag:(nonnull NSNumber *)rootTag
props:(NSDictionary *)props)
{
RCTComponentData *componentData = _componentDataByName[viewName];
// 創(chuàng)建對應(yīng)的shadowView,存入容器_shadowViewRegistry,設(shè)置屬性
RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];
if (shadowView) {
[componentData setProps:props forShadowView:shadowView];
_shadowViewRegistry[reactTag] = shadowView;
RCTShadowView *rootView = _shadowViewRegistry[rootTag];
shadowView.rootView = (RCTRootShadowView *)rootView;
}
// 主線程 創(chuàng)建NativeView,存入View注冊表_viewRegistry
__block UIView *preliminaryCreatedView = nil;
void (^createViewBlock)(void) = ^{
if (preliminaryCreatedView) {
return;
}
preliminaryCreatedView = [componentData createViewWithTag:reactTag];
if (preliminaryCreatedView) {
self->_viewRegistry[reactTag] = preliminaryCreatedView;
}
};
RCTExecuteOnMainQueue(createViewBlock);
// 設(shè)置控件屬性(對UIView的操作都先暫存在_pendingUIBlocks,批處理)
[self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
createViewBlock();
if (preliminaryCreatedView) {
[componentData setProps:props forView:preliminaryCreatedView];
}
}];
// 更新props,存入_shadowViewsWithUpdatedProps
[self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]];
}
2.根據(jù)計算結(jié)果,設(shè)置視圖層次結(jié)構(gòu)
// ReactNativeRenderer-dev.js
function finalizeInitialChildren( parentInstance, type, props, rootContainerInstance, hostContext) {
if (parentInstance._children.length === 0) {
return false;
}
var nativeTags = parentInstance._children.map(function(child) {
return typeof child === "number" ? child // Leaf node (eg text): child._nativeTag;
});
UIManager.setChildren(
parentInstance._nativeTag, // 父控件tag
nativeTags // 子控件tag集合
);
return false;
}
原生端布局結(jié)點樹更改層次結(jié)構(gòu),此時僅從數(shù)據(jù)層面改變層次結(jié)構(gòu),并未真正addSubVew
(詳見UIView+React.m)
// RCTUIManager.m
RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag
reactTags:(NSArray<NSNumber *> *)reactTags)
{
RCTSetChildren(containerTag, reactTags,(NSDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry);
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
// 僅僅是數(shù)據(jù)層面,并不正真改成視圖層次
RCTSetChildren(containerTag, reactTags,(NSDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry);
}];
[self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]];
}
3.布局時機一到,真正執(zhí)行UI渲染
- _layoutAndMount按順序做了以下幾件事情,詳見源碼或DEMO
-
_dispatchPropsDidChangeEvents
向?qū)傩灾蛋l(fā)生改變的原生控件/布局結(jié)點發(fā)送消息,主要驅(qū)使布局結(jié)點更新YOGA屬性值; -
_dispatchChildrenDidChangeEvents
向視圖層次結(jié)構(gòu)發(fā)生改變的原生控件/布局結(jié)點發(fā)送消息,主要驅(qū)使原生控件更新視圖樹(addSubview); - 遍歷根結(jié)點,調(diào)用
uiBlockWithLayoutUpdateForRootView
,封裝渲染操作。
先驅(qū)動根結(jié)點執(zhí)行l(wèi)ayout操作,底層會從根結(jié)點開始遞歸,使用yoga計算布局結(jié)果(shadowView.layoutMetrics);
再把正真的布局(reactSetFrame)、動畫(performAnimations)操作封裝為一個uiBlock并收集到UI操作容器_pendingUIBlocks
- 最后調(diào)用
flushUIBlocksWithCompletion
,統(tǒng)一把所有UI操作派發(fā)到主線程執(zhí)行,包括最后一個渲染/動畫操作block
-
// RCTUIManager.m
// 布局時機
- (void)batchDidComplete
{
[self _layoutAndMount];
}
- (void)_layoutAndMount
{
// 通知 RCTShadowView、RCTComponent 屬性已更新
[self _dispatchPropsDidChangeEvents];
// 通知 RCTShadowView、RCTComponent 控件/布局結(jié)點層次結(jié)構(gòu)已更新(subView已改變)
[self _dispatchChildrenDidChangeEvents];
// 構(gòu)建一個UI渲染、執(zhí)行布局動畫的Block,添加到_pendingUIBlocks
for (NSNumber *reactTag in _rootViewTags) {
RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag];
[self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
}
// 統(tǒng)一執(zhí)行所有 _pendingUIBlocks,包括UI渲染
[self flushUIBlocksWithCompletion:^{
[self->_observerCoordinator uiManagerDidPerformMounting:self];
}];
}
// 構(gòu)建一個UI渲染、執(zhí)行布局動畫的Block
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView
{
...
return block;
}
-
布局時機理解:渲染過程中布局的時機是
batchDidComplete
,追溯回JSIExecutor.cpp
可知這個時間點就是執(zhí)行Native call JS、 JS callback后把所有JS端暫存JS call Native調(diào)用信息傳遞給原生端,原生端發(fā)起執(zhí)行(并未執(zhí)行完畢)后,就觸發(fā)布局。為何在這個時間點布局,筆者的猜想如下(不一定準(zhǔn)確,歡迎探討
):- 1.首先明確一個前提:UI渲染的優(yōu)先級無疑是最高的,原生開發(fā)中UI操作都是在主線程進行。因此JS端的計算結(jié)果(通常表現(xiàn)為原生控件信息的更新,即UI數(shù)據(jù)的更新)必須及時發(fā)送到原生端,執(zhí)行UI更新。
- 2.再回顧前言對ReactNative渲染原理的介紹:
用戶行為觸發(fā)原生UI控件交互事件/回調(diào)事件從原生端傳遞到JS端,JS端處理業(yè)務(wù)邏輯,得到新的UI數(shù)據(jù),再次傳遞到原生端,驅(qū)動原生模塊進行新一輪UI更新。
- 例子1:用戶點擊按鈕觸發(fā)一個彈窗。點擊事件在
RCTRootContentView
通過RCTTouchHandler
、RCTEventDispatcher
,最終調(diào)用JS模塊RCTEventEmitter
函數(shù)receiveEvent
通知JS端處理UI交互事件(Native call JS
)。JS端收到信號后進行業(yè)務(wù)邏輯處理,計算結(jié)果是帶彈窗的新UI數(shù)據(jù)。數(shù)據(jù)需要立即傳遞到原生端進行UI更新,繪制彈窗,在Native Call JS執(zhí)行完畢后進行無疑是最及時的,這就是布局時機1。 - 例子2:用戶調(diào)用原生社交分享模塊,完畢需要顯示toast條提示用戶分享結(jié)果。JS端發(fā)起一個帶回調(diào)JS call Native調(diào)起原生模塊進行社交分享,原生端分享完畢,執(zhí)行
JS callback
通知JS端處理分享結(jié)果。JS端收到信號后進行業(yè)務(wù)邏輯處理,計算結(jié)果是帶toast提示條的新UI數(shù)據(jù)。數(shù)據(jù)需要立即傳遞到原生端進行UI更新,繪制toast提示條,在JS callback執(zhí)行完畢后進行是最及時的,這就是布局時機2。
- 例子1:用戶點擊按鈕觸發(fā)一個彈窗。點擊事件在
至此,UI渲染的基本流程就分析完畢了。
六、JS端調(diào)用原生控件導(dǎo)出函數(shù)
JS端調(diào)用原生控件的導(dǎo)出函數(shù),流程如下
- 1.JS端調(diào)用
RCTUIManager
導(dǎo)出函數(shù)dispatchViewManagerCommand
,傳遞reactTag、Command - 2.觸發(fā)原生端走JS call Native流程的后半部分,最終定位到對應(yīng)原生控件的函數(shù)(可參考
WebView.ios.js
)
// RCTUIManager.m
/*
向ViewManager派發(fā)命令:用于RN端調(diào)用控件導(dǎo)出函數(shù)
reactTag 控件標(biāo)簽
commandID 命令id
commandArgs 參數(shù)
*/
RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag
commandID:(NSInteger)commandID
commandArgs:(NSArray<id> *)commandArgs)
{
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
RCTComponentData *componentData = _componentDataByName[shadowView.viewName];
Class managerClass = componentData.managerClass;
RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
id<RCTBridgeMethod> method = moduleData.methods[commandID];
// 帶上reactTag,用于獲取對應(yīng)Native控件
NSArray *args = [@[reactTag] arrayByAddingObjectsFromArray:commandArgs];
// 調(diào)用控件導(dǎo)出函數(shù)
[method invokeWithBridge:_bridge module:componentData.manager arguments:args];
}
// RCTWKWebViewManager.m
RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
RCTWKWebView *view = viewRegistry[reactTag];
if (![view isKindOfClass:[RCTWKWebView class]]) {
} else {
[view goBack];
}
}];
}
七、結(jié)語
- 這一系列文章就到此為止了,希望有助于大家理解ReactNative源碼;
- ReactNative還有很多設(shè)計值得研究,例如原生如何端驅(qū)動JS端定時任務(wù)、觀察者模式的設(shè)計等;