Some time ago, I did a technical sharing of “ReactNative principle analysis” in the company. Due to the lack of detailed PPT, I sorted out a series of articles on ReactNative principle analysis to meet the needs of some partners, hoping that we could have a systematic and comprehensive understanding of the underlying principle of ReactNative.

  • “ReactNative Principle” startup process
  • “ReactNative principle” JS layer rendering diff algorithm
  • “ReactNative Principle” Native layer rendering process

UI rendering is all about how do you create a view? How do I refresh the view? . In the DIff algorithm of JS layer rendering of “ReactNative Principle”, it introduces in detail what ReactNative does when JS layer is refreshed. We know that the final display on the interface is rendered by Native layer. Then how does Native layer render?

In Native layer rendering, the core is UIManager and ShadowView. View creation and view refresh are basically carried out around UIManager and ShadowView.

What is ShadowView?

See UIMananger source code, we will find that UIMananger exposed to the JS side of the View operation API interface will operate on View and ShadowView at the same time, so what is ShadowView?

Let’s take a look at ShadowView’s official explanation:

Translation:

  • ShadowView tree is the mirror image of RCTView View tree, and the two are one-to-one correspondence. ShadowView saves the layout and sub-controls of the corresponding View and manages the loading of the View

  • JSBridge can call the setters method in ShadowView to set properties such as styles.

  • Each JSBridge batch ends, is called collectUpdatedFrames: widthConstraint: heightConstraint to refresh the view layout.

To put it simply, ShadowView of Native layer has a similar function to VirtualDom of JS terminal (it exists to improve program performance). ShadowView mainly calculates View layout through YGNode, and refreshes View View in batches after calculation. Improves application performance by preventing frequent operations on real views. Therefore, operations on the ShadowView are usually performed on the child thread, while operations on the real View are always performed on the main thread

The understanding of ShadowView should be very abstract, then look at how to create a view, how to refresh the view, you will have a deeper understanding.

UIManager

If you have looked at the source code, or have written a custom RN native component, you will find that the UI component provided to the JS side of RN (such as RNImageView) creates the View at the same time, It is necessary to create a bound ~UIManager (for example, RNImageView corresponds to RNImageViewManager) to manage the communication between the Native end and js end. The UIManager corresponding to the root view RCTRootView is described in detail here.

How was UIManager created?

    1. After RCTRootView is started in RN, it enters the rendering process. So let’s create oneRCTRootContentViewObject that runs the JS code in the app and creates the RCTRootContentView object as the entry view container. The main code flow is as follows:
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
 .
  // Create a RCTRootContentView object
  _contentView = [[RCTRootContentView alloc] initWithFrame: bridge: reactTag: sizeFlexiblity:];
  // Run app JS code logic
  [self runApplication:bridge];
.
  // Use the RCTRootContentView object as the entry view container
  [self insertSubview:_contentView atIndex:0];
.
}
Copy the code
    1. In creating the instance method of RCTRootContentView, instantiation and instance methods of UIManager are triggeredregisterRootView. Macros pass through UIManagerRCT_EXPORT_METHOD()Export a series of methods to the JS side to call, for the JS side to the OC side of the View component operations, such as creating and removing the View, adjust the relationship of the View, set the View properties, etc..
- (instancetype)initWithFrame: bridge: reactTag: sizeFlexiblity:
{
  if ((self = [super initWithFrame:frame])) {
  .
  // Instantiate UIManager
    [_bridge.uiManager registerRootView:self];
  .
  }
  return self;
}
Copy the code

What is the use of UIManager when creating and refreshing views in the Native layer? And then we look down

How do I create views?

How does the React code written on the JS side render as a native component?

  1. A method is calledcreateView:Creating a container View
  2. Call immediately aftersetChildrenAdding child components to the container view is the core of this methodinsertReactSubview:atIndex:

The entire flowchart for creating a view is as follows:

1. createView

Start to finish, began to render the createView: by using the method of UIManager viewName: rootTag: props: to map the JS code into Native end View View. When creating a View, create a ShadowView according to the module name viewName and reactTag, and create a View corresponding to ShadowView in the main thread. The core code is as follows:

RCT_EXPORT_METHOD(createView:viewName:rootTag:props:)
{
  // 1. View module name viewName to obtain module configuration information componentData
  RCTComponentData *componentData = _componentDataByName[viewName];
 .
  // 2. Create shadowView according to the module configuration information
  RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];
  // Set the properties and register shadowView
  [componentData setProps:props forShadowView:shadowView];
  _shadowViewRegistry[reactTag] = shadowView;
  .
  // 3. Create a View based on the module configuration information
  RCTExecuteOnMainQueue(^{
   .
    UIView *view = [componentData createViewWithTag:reactTag];
    // Set the properties and register the view
    [componentData setProps:props forView:view];
    uiManager->_viewRegistry[reactTag] = view;
   .
  });
}
Copy the code

2. setChildren

In the calling createView: viewName: rootTag: props: method, will then trigger setChildren: reactTags: method to render the child views, the method is mainly to do three things, the code is as follows:

2.1 shadowView

RCTShadowView insertReactSubview: atIndex: method, insert the corresponding child nodes in the tree YGNode, at this point, is not added to the view hierarchy tree! The code implementation is as follows:

2.2 pendingUIBlocks

All JS to Native UI operations do not execute immediately, but instead call addUIBlock: addUI changes to queue _pendingUIBlocks and wait for the right time to execute the queue in batches. The code implementation is as follows:

2.3 the View

UIView + the Rect insertReactSubview: atIndex: method According to the hierarchical order (index) will be added to the reactSubviews subView, at this point, there is still no really added to the view hierarchy tree! The code implementation is as follows:

3. flushUIBlocks

After completing a batch of operations (called every 16ms by a timer), JS calls the flushUIBlocks method of RCTUIManager to execute a UI block on the main thread. The code implementation is as follows:

- (void)flushUIBlocks
{
  // First copy the previous blocks into a temporary variable, then reset the
  // pending blocks to a new array. This guards against mutation while
  // processing the pending blocks in another thread.
  NSArray<RCTViewManagerUIBlock> *previousPendingUIBlocks = _pendingUIBlocks;
  _pendingUIBlocks = [NSMutableArray new];

  if (previousPendingUIBlocks.count) {
    // Execute the previously queued UI blocks
    RCTExecuteOnMainQueue(^{
      for (RCTViewManagerUIBlock block in previousPendingUIBlocks) {
        block(self.self->_viewRegistry);
      }
      .}); }}Copy the code

4. didUpdateReactSubViews

After executing the flushUIBlocks method, the didUpdateReactSubviews method of UIView+Rect is finally called to add the View to the View hierarchy tree. The code implementation is as follows:

How do I refresh the view?

Refresh a view in two main cases:

  1. Pass when updating node properties, etcupdateView:Refresh the view
  2. When a node is inserted, deleted, or sorted, passmanageChildren:Update the view

To update the View, we need to calculate the layout through ShadowView first, and then refresh the real View by batch

The flowchart for the entire refresh view is as follows:

1. updateView

After setState on the JS side, when attributes change, the JS side will update the interface through UIManager updateView method after diff algorithm calculation. The source code is as follows:

Similar to createView and setChildren, it does three things

1. Update the props of ShadowView 2. Add UI changes to the queue _pendingUIBlocks 3. Update the props of the ViewCopy the code

2. manageChildren

After the setState of the JS side, if the nodes of the old and new Virtual DOM are increased, deleted, sorted and other node changes, then the JS side through the diff algorithm, is through the UIManager manageChildren method to update the interface, source code is as follows:

Similar to createView, setChildren, and updateView, it does three things

1. Update ShadowView (call RCTShadowView ` insertReactSubview: atIndex: ` method) 2. Add UI changes to queue _pendingUIBlocks 3. Update the View (called UIView + the Rect ` insertReactSubview: atIndex: ` method)Copy the code

You think it’s nice give me a small onepraiseEncouragement ~

ReactNative principle analysis of the same series of articles:

  • “ReactNative Principle” startup process
  • “ReactNative principle” JS layer rendering diff algorithm
  • “ReactNative Principle” Native layer rendering process

conclusion

If you are also a programmer interested in investment and financial management, welcome to pay attention to my public account “flying dragon at the bottom of the valley”, and become the investment leader of the technology industry.