【源码解析】React Native组件渲染
一、引言
React Native,简称RN,是FaceBook推出的一款跨平台的代码框架,其主要是为了解决Android和iOS两端代码逻辑不统一的痛点,可实现一套代码逻辑,运行于Android和iOS两端的功能。
由于其底层原理还是依赖于Android和iOS两个原生系统,只是这部分复杂逻辑是FaceBook在React Native框架中帮我们做了而已,使得开发者只需要关注框架提供给应用层的API就可以进行移动开发。
本文从源码的角度分析React Native的渲染过程,深扒一下React Native是怎么一步一步的创建原生View的,让我们开始吧~
二、Android端源码分析 I
在Android端集成过RN的小伙伴应该都知道,其最终的形态就是一个ReactRootView,一般都是使用关键字new出来的。
如果我们将该View集成到Activity,那么该Activity就是一个RN形态的Activity;如果我们将该View集成到Fragment,那么该Fragment就是一个RN形态的Fragment。
首先我们看一下ReactRootView的类型。
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView, MeasureSpecProvider {
}
public class SizeMonitoringFrameLayout extends FrameLayout {
}
通过源码可以知道ReactRootView间接继承了FrameLayout,也就是说它是一个容器。所以RN的渲染过程就是将JS中的Component映射到View,并将View添加到ReactRootView的过程。
目前,我们只是拿到了一个空的容器(ReactRootView),接下来还需要将View添加到该容器中。那么RN是如何将JS中的Component映射到View的呢?
我们注意到了ReactRootView有一个方法叫做startReactApplication。
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties) {
try {
mReactInstanceManager = reactInstanceManager;
mJSModuleName = moduleName;
mAppProperties = initialProperties;
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
mReactInstanceManager.createReactContextInBackground();
}
attachToReactInstanceManager();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
该方法接收三个形式参数:
- reactInstanceManager:ReactInstanceManager类型,CatalystInstance对象管理器。
- moduleName:String类型,模块名,和JS中通过AppRegistry.registerComponent注册的模块名一一对应。
- initialProperties:Bundle类型,初始化参数,该参数会传递到JS的Component中,JS端通过构造函数props可以进行获取。
该方法的逻辑比较简单,就是将三个形式参数存储起来后,然后调用了attachToReactInstanceManager方法。
private void attachToReactInstanceManager() {
try {
...省略
mIsAttachedToInstance = true;
Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
判断mReactInstanceManager对象不为空(该对象在startReactApplication方法中已赋值),则调用mReactInstanceManager对象的attachRootView方法。
public void attachRootView(ReactRootView rootView) {
mAttachedRootViews.add(rootView);
// Reset view content as it's going to be populated by the application content from JS.
rootView.removeAllViews();
rootView.setId(View.NO_ID);
// If react context is being created in the background, JS application will be started
// automatically when creation completes, as root view is part of the attached root view list.
ReactContext currentContext = getCurrentReactContext();
if (mCreateReactContextThread == null && currentContext != null) {
attachRootViewToInstance(rootView, currentContext.getCatalystInstance());
}
}
attachRootView方法清空了ReactRootView中的所有子View,并设置其Id为View.NO_ID,然后调用了attachRootViewToInstance方法。
private void attachRootViewToInstance(
final ReactRootView rootView,
CatalystInstance catalystInstance) {
UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, rootView.getUIManagerType());
final int rootTag = uiManagerModule.addRootView(rootView);
rootView.setRootViewTag(rootTag);
rootView.runApplication();
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
Systrace.endAsyncSection(
TRACE_TAG_REACT_JAVA_BRIDGE,
"pre_rootView.onAttachedToReactInstance",
rootTag);
rootView.onAttachedToReactInstance();
}
});
}
attachRootViewToInstance方法中给每一个ReactRootView返回了一个整型的rootTag,该rootTag是唯一的。当该ReactRootView被destroy后,也是通过该rootTag找到相应的ReactRootView。最后执行了ReactRootView的runApplication方法。
void runApplication() {
try {
...省略
CatalystInstance catalystInstance = reactContext.getCatalystInstance();
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", getRootViewTag()); //ReactRootView唯一的rootTag
@Nullable Bundle appProperties = getAppProperties(); //需要传递给JS Component的属性
if (appProperties != null) {
appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
}
if (getUIManagerType() == FABRIC) {
appParams.putBoolean("fabric", true);
}
mShouldLogContentAppeared = true;
String jsAppModuleName = getJSModuleName(); //JS Component的模块名
//通过动态代理形式执行AppRegistry.runApplication方法,最终映射到RN中的AppRegistry.js的同名方法
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
runApplication方法构造了一个WritableNativeMap对象(类似于HashMap),并将rootTag、初始化参数写入对象中。
通过catalystInstance.getJSModule(Class clazz)方法可以获取到JS中的同名类对象(底层实现是JNI),因此我们可以认为最后调用的是JS端AppRegistry的runApplication方法。
三、JS端源码分析
从上面的Android端源码分析得知,最后调用了JS端的AppRegistry的runApplication方法。
runApplication(appKey: string, appParameters: any): void {
...省略
SceneTracker.setActiveScene({name: appKey});
runnables[appKey].run(appParameters);
}
在runApplication方法中,其调用了一个runnables[appKey]的run方法。而runnables[appKey]返回一个Runnable对象,所以这里调用的是Runnable对象的run方法。
那么这个Runnable对象的run方法究竟执行了什么样的逻辑呢???在分析之前,我们先来看一下runnables[appKey]是怎么被赋值的。
我们知道在JS端,需用通过AppRegistry的registerComponent方法注册模块名。
registerComponent(
appKey: string,
componentProvider: ComponentProvider,
section?: boolean,
): string {
runnables[appKey] = {
componentProvider,
run: appParameters =>
renderApplication(
componentProviderInstrumentationHook(componentProvider),
appParameters.initialProps,
appParameters.rootTag,
wrapperComponentProvider && wrapperComponentProvider(appParameters),
),
};
if (section) {
sections[appKey] = runnables[appKey];
}
return appKey;
}
在registerComponent方法中,直接给runnables[appKey]进行了赋值操作。
所以,上面的runApplication方法中的runnables[appKey]方法就是在这里被赋值的!!!所以其中的run方法也是在这里被定义,我们单独拿出这方法块进行分析。
run: appParameters =>
renderApplication(
componentProviderInstrumentationHook(componentProvider),
appParameters.initialProps,
appParameters.rootTag,
wrapperComponentProvider && wrapperComponentProvider(appParameters),
),
run方法接收一个参数,叫做appParameters。由上面的分析可知,appParameters就是Android端传递进来的WritableNativeMap对象,其代表的是一些初始化参数。run方法中没有做过多的逻辑,而是直接调用了renderApplication.js中的renderApplication方法。
function renderApplication<Props: Object>(
RootComponent: React.ComponentType<Props>,
initialProps: Props,
rootTag: any,
WrapperComponent?: ?React.ComponentType<*>,
) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
ReactNative.render(
<AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>,
rootTag,
);
}
renderApplication方法没有过多复杂的逻辑,而是直接调用了ReactNative的render方法。
也不知道是不是React版本太低还是什么原因,在源码中始终找不到ReactNative的render方法。
百度、Google都试过了就是找不到相关的文章,最后茅塞顿开地想到不是可以利用debug调试看调用栈吗???,最后随便找了个demo,将断点断在render的return语句中,得到如下的调用栈。

从上面的调用栈可以看出,renderApplication.js的renderApplication方法调用了ReactNativeRenderer-dev.js的render方法。
render: function(element, containerTag, callback) {
var root = roots.get(containerTag);
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(containerTag, false, false);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
return getPublicRootInstance(root);
}
注意:开发环境下调用的是ReactNativeRenderer-dev.js的render方法,生产环境下调用的是ReactNativeRenderer-prod.js的render方法。由于两者的方法逻辑基本一致,故这里只讨论开发环境的render方法。
render方法中又调用了updateContainer方法,从调用栈来看,中间调用了一些ReactNativeRenderer-dev.js的其他方法,这里就不一一展开进行详细说明了。我们直接跳到performUnitOfWork这个方法。
function performUnitOfWork(workInProgress) {
...省略
var next = void 0;
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
if (workInProgress.mode & ProfileMode) {
// Record the render duration assuming we didn't bailout (or error).
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
}
} else {
next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
}
...省略
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner$2.current = null;
return next;
}
当next === null时,表示接下来并没有其他子View了,则执行completeUnitOfWork方法,而completeUnitOfWork方法中又执行了completeWork方法。
function completeWork(current, workInProgress, renderExpirationTime) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
...省略
case HostComponent: {
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
...省略
} else {
var currentHostContext = getHostContext();
var wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext
)
) {
markUpdate(workInProgress);
}
} else {
var instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress
);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef$1(workInProgress);
}
}
break;
}
...省略
}
return null;
}
completeWork方法中最重要的是执行了createInstance方法,因为该方法执行了创建View对象的逻辑。
function createInstance(
type,
props,
rootContainerInstance,
hostContext,
internalInstanceHandle
) {
...省略
UIManager.createView(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload // props
);
...省略
return component;
}
这里最重要的是调用了UIManager的createView方法。而这里的UIManager对应的是Android端的UIManagerModule,createView方法对应的是UIManagerModule中的createView方法。
四、Android端源码分析 II
让我们继续回到Android端,找到UIManagerModule。
protected static final String NAME = "UIManager";
@Override
public String getName() {
return NAME;
}
可以看到UIManagerModule的getName方法返回的是UIManager,这样验证了上面的JS端中的结论。我们继续看createView方法。
@ReactMethod
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
mUIImplementation.createView(tag, className, rootViewTag, props);
}
createView方法中直接调用了UIImplementation的createView方法。
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
ReactShadowNode cssNode = createShadowNode(className);
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist");
cssNode.setReactTag(tag);
cssNode.setViewClassName(className);
cssNode.setRootTag(rootNode.getReactTag());
cssNode.setThemedContext(rootNode.getThemedContext());
mShadowNodeRegistry.addNode(cssNode);
ReactStylesDiffMap styles = null;
if (props != null) {
styles = new ReactStylesDiffMap(props);
cssNode.updateProperties(styles);
}
handleCreateView(cssNode, rootViewTag, styles);
}
该方法中最后又调用了handleCreateView方法。
protected void handleCreateView(
ReactShadowNode cssNode,
int rootViewTag,
@Nullable ReactStylesDiffMap styles) {
if (!cssNode.isVirtual()) {
mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles);
}
}
在handleCreateView方法中,又调用了NativeViewHierarchyOptimizer的handleCrateView方法。
public void handleCreateView(
ReactShadowNode node,
ThemedReactContext themedContext,
@Nullable ReactStylesDiffMap initialProps) {
if (!ENABLED) {
int tag = node.getReactTag();
mUIViewOperationQueue.enqueueCreateView(
themedContext,
tag,
node.getViewClass(),
initialProps);
return;
}
boolean isLayoutOnly = node.getViewClass().equals(ViewProps.VIEW_CLASS_NAME) &&
isLayoutOnlyAndCollapsable(initialProps);
node.setIsLayoutOnly(isLayoutOnly);
if (!isLayoutOnly) {
mUIViewOperationQueue.enqueueCreateView(
themedContext,
node.getReactTag(),
node.getViewClass(),
initialProps);
}
}
而handleCreateView方法又去调用了UIViewOperationQueue的enqueueCreateView方法。
public void enqueueCreateView(
ThemedReactContext themedContext,
int viewReactTag,
String viewClassName,
@Nullable ReactStylesDiffMap initialProps) {
synchronized (mNonBatchedOperationsLock) {
mNonBatchedOperations.addLast(
new CreateViewOperation(
themedContext,
viewReactTag,
viewClassName,
initialProps));
}
}
这里的mNonBatchedOperations是一个队列,所以这里的逻辑是new了一个CreateViewOperation对象,并加入到了mNonBatchedOperations这个队列中。而该队列最终会将其中的CreateViewOperation对象取出,并执行其中的execute方法。
public CreateViewOperation(
ThemedReactContext themedContext,
int tag,
String className,
@Nullable ReactStylesDiffMap initialProps) {
super(tag);
mThemedContext = themedContext;
mClassName = className;
mInitialProps = initialProps;
Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
}
@Override
public void execute() {
Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
mNativeViewHierarchyManager.createView(
mThemedContext,
mTag,
mClassName,
mInitialProps);
}
这里的mClassName是从JS端传过来的,表示的是需要创建的View的名字,这是双端约定好的一个名字。例如,JS中的Text对应着Android端的TextView,它们双端约定的名字就是RCTText(这在ReactTextViewManager中可以查询到)。
在execute方法中,调用了NativeViewHierarchyManager的createView方法。
public synchronized void createView(
ThemedReactContext themedContext,
int tag,
String className,
@Nullable ReactStylesDiffMap initialProps) {
try {
ViewManager viewManager = mViewManagers.get(className);
View view = viewManager.createView(themedContext, mJSResponderHandler);
mTagsToViews.put(tag, view);
mTagsToViewManagers.put(tag, viewManager);
view.setId(tag);
if (initialProps != null) {
viewManager.updateProperties(view, initialProps);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
mViewManagers.get(className)返回一个ViewManager对象。上面提到,className是JS端和Android端双端约定的一个名字。拿上面的例子来说,当JS中的标签为<Text />时,className为RCTText,返回的是,ReactTextViewManager对象,其他组件以此类推。然后执行了ViewManager对象的createView方法。
public final T createView(
ThemedReactContext reactContext,
JSResponderHandler jsResponderHandler) {
T view = createViewInstance(reactContext);
addEventEmitters(reactContext, view);
if (view instanceof ReactInterceptingViewGroup) {
((ReactInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler);
}
return view;
}
createView方法中通过执行createViewInstance方法返回一个View对象,而createViewInstance方法是一个抽象方法,其具体的实现在子类中。拿ReactTextViewManager举例的话,是这样的。
@Override
public ReactTextView createViewInstance(ThemedReactContext context) {
return new ReactTextView(context);
}
简单粗暴的返回一个ReactTextView,而ReactTextView就是一个TextView。我们看一下其继承关系。
public class ReactTextView extends TextView implements ReactCompoundView {
...
}
到这里,我们终于理清了React Native中的View标签是怎么映射到原生View的。既然View已经创建出来了,那么其更新过程基本上也是大同小异,本文就不再详述了。
五、总结
React Native在JS端的View标签本质上只是一个标记位,其最后还是需要通过和原生的交互,并将View标签转换成具体的View对象。其中View的渲染过程还是比较复杂的,本文也只是分析了其渲染的一个整体流程,有些具体的细节并没有深究下去,有兴趣的同学可以深入的分析一下。