TL;DR
Below call stack should cover this chapter.
Since you want to read it anyway...
We ended with below code block in Chapter1 -
- (void)runApplication:(RCTBridge *)bridge
{
//...Code removed to make it more clear for reading.
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}
Which called AppRegistry.runApplication
with 'module name' and 'parameters' as argument. So let's open the JavaScript project and navigate to AppRegistry.js
to find out what's going on here.
AppRegistry.js
runApplication(appKey: string, appParameters: any): void {
//...Assertion code
runnables[appKey].run(appParameters);
},
The runnables
is a global dictionary which held the appKey => component
mapping. We've already registered our App.js
with key RNTLDR
in index.js
of our project. This code is generated by react-native
command line tool since we've created our app with it.
index.js
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('RNTLDR', () => App);
AppRegistry.js
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;
},
So the run
function will call renderApplication
to render our application.
renderApplication.js
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);
let renderable = (
/**
* Bob's note:
* `RootComponent` is our app's root component ('App.js')
*/
<AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>
);
//...Async component check removed (still marked unstable in v52.0.0)
renderable = (
<AppContainerAsyncWrapper>{renderable}</AppContainerAsyncWrapper>
);
}
ReactNative.render(renderable, rootTag);
}
This is pretty straight forward - first it will wrap our component in AppContainer
, then it will render our app using ReactNative.render
.
The AppContainer
part is rather simple. I will not paste any code for this. If you looking into AppContainer.js
, it basically wrapped 'yellow box', 'inspector' and our app root to a View
. The previous two are for debugging use. It also injected the debug inspector directly in 'React DOM'.
So the key part is ReactNative.render
. ReactNative
module is defined in '[your project root]/node_modules/react-native/Libraries/Renderer/shims/ReactNative.js'. And it will use different 'renderer' for debugging and production but they are basically the same thing.
ReactNativeRenderer
will use render engine from React
to render our 'DOM tree'. This is a big topic (also a big source code - it has 10k+ lines) and we won't talk about it here. You can read more about how React
rendering 'DOM trees' from this article.
Long story short - this renderer will use UIManager
to render our APP's UI. I'll paste one snippet about how does UIManager
getting used when creating and manipulating views.
ReactNativeRenderer-dev.js
var NativeRenderer = reactReconciler({
//...
createInstance: function(
//...
) {
//...
UIManager.createView(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload
);
//...
return component;
},
//...
appendChild: function(parentInstance, child) {
var childTag = typeof child === "number" ? child : child._nativeTag;
var children = parentInstance._children;
var index = children.indexOf(child);
if (index >= 0) {
children.splice(index, 1);
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[index], // moveFromIndices
[children.length - 1], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[]
);
} else {
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[children.length - 1], // addAtIndices
[]
);
}
},
});
UIManager
is also an important part. It handles all native UI components. We will talk about it in an individual chapter.
So basically this 'renderer' in JavaScript only render a virtual 'DOM tree' in memory. The actual drawings is handled by native ui modules.
That's concluded how ReactNative start in JavaScript.