美文网首页
React Native 分析(四)UI对象创建和管理

React Native 分析(四)UI对象创建和管理

作者: 吟游雪人 | 来源:发表于2017-08-01 17:38 被阅读0次

我们知道JS主要管理的是界面渲染逻辑和事件处理逻辑,那么渲染是怎么同步到Native端的呢?初始又是怎么创建的呢?
RCTRootView是入口,RCTRootView是RN的根视图,内部持有了一个RCTBridge,但是这个RCTBridge并没有太多的代码,而是持有了另一个RCTBatchBridge对象,大部分的业务逻辑都转发给BatchBridge,BatchBridge里面写着的大量的核心代码
RCTUIManager是管理所有UI的对象,负责创建,更新UIView对象。这样就借助了 iOS本身的UIView 渲染机制进行渲染。
RCTUIManager又是通过上篇所提到的通信机制作为一个JS对象来操作的。

在JS里面有一个所有JSComponent的tag表,在OC里面依然也有这么一个所有nativeView的Tag表_shadowViewRegistry,只有通过唯一指定的tag,这样RCTUIManager,才能知道到底应该操作哪一个nativeView

一、创建rootview

在开始正式创建RCTRootView的时候会创建一个subviewRCTContentRootView这个东西创建的时候需要一个reactTag,这个tag是一个很关键的东西,此时通过allocateRootTag方法创建了root得reactTag。

- (NSNumber *)reactTag
{
  RCTAssertMainQueue();
  if (!super.reactTag) {
    /**
     * Every root view that is created must have a unique react tag.
     * Numbering of these tags goes from 1, 11, 21, 31, etc
     *
     * NOTE: Since the bridge persists, the RootViews might be reused, so the
     * react tag must be re-assigned every time a new UIManager is created.
     */
    self.reactTag = [_bridge.uiManager allocateRootTag];
  }
  return super.reactTag;
}

从注释可以看出规则是从1开始,每次创建一个RootView实例都会累加10,如1,11,21,31,以此类推。创建完RCTContentRootView后还要去UIManager用这个reactTag注册View,也就是以Tag为Key,登记进入_viewRegistry字典表,同时创建对应的shadow view。

- (void)registerRootView:(RCTRootContentView *)rootView
{
  NSNumber *reactTag = rootView.reactTag;
  UIView *existingView = _viewRegistry[reactTag];
  CGSize availableSize = rootView.availableSize;

  _viewRegistry[reactTag] = rootView;

  dispatch_async(RCTGetUIManagerQueue(), ^{
    if (!self->_viewRegistry) {
      return;
    }

    RCTRootShadowView *shadowView = [RCTRootShadowView new];
    shadowView.availableSize = availableSize;
    shadowView.reactTag = reactTag;
    shadowView.backgroundColor = rootView.backgroundColor;
    shadowView.viewName = NSStringFromClass([rootView class]);
    self->_shadowViewRegistry[shadowView.reactTag] = shadowView;
    [self->_rootViewTags addObject:reactTag];
  });

  [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification
                                                      object:self
                                                    userInfo:@{RCTUIManagerRootViewKey: rootView}];
}

关于 shadow view,后面会有更详细的介绍(如果有后面的话_),简而言之就是优化布局用的。

/**
 * ShadowView tree mirrors RCT view tree. Every node is highly stateful.
 * 1. A node is in one of three lifecycles: uninitialized, computed, dirtied.
 * 1. RCTBridge may call any of the padding/margin/width/height/top/left setters. A setter would dirty
 *    the node and all of its ancestors.
 * 2. At the end of each Bridge transaction, we call collectUpdatedFrames:widthConstraint:heightConstraint
 *    at the root node to recursively lay out the entire hierarchy.
 * 3. If a node is "computed" and the constraint passed from above is identical to the constraint used to
 *    perform the last computation, we skip laying out the subtree entirely.
 */

然后将RCTContentRootView添加到RCTRootView上面,然后执行了一行JS代码,告诉JS你要开始绘制这个参数params的界面

- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag": _contentView.reactTag,
    @"initialProps": _appProperties ?: @{},
  };

  RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
  [bridge enqueueJSCall:@"AppRegistry"
                 method:@"runApplication"
                   args:@[moduleName, appParameters]
             completion:NULL];
}

再往后就是React.JS的工作了,React.JS会着手把JS中的页面进行计算,排版,生成对应的JS Component,准备组织绘制界面了,包含着无数个JS Component的相互嵌套。最终通过UIManager.js接口,开始call oc去创建界面。

js 中如何处理 tag 的呢?
会跳过哪些 native 保留的 tag,也就是对10取余为1的那些 tag。然后自增。

/**
 * Keeps track of allocating and associating native "tags" which are numeric,
 * unique view IDs. All the native tags are negative numbers, to avoid
 * collisions, but in the JS we keep track of them as positive integers to store
 * them effectively in Arrays. So we must refer to them as "inverses" of the
 * native tags (that are * normally negative).
 *
 * It *must* be the case that every `rootNodeID` always maps to the exact same
 * `tag` forever. The easiest way to accomplish this is to never delete
 * anything from this table.
 * Why: Because `dangerouslyReplaceNodeWithMarkupByID` relies on being able to
 * unmount a component with a `rootNodeID`, then mount a new one in its place,
 */
var INITIAL_TAG_COUNT = 1;
var ReactNativeTagHandles = {
  tagsStartAt: INITIAL_TAG_COUNT,
  tagCount: INITIAL_TAG_COUNT,

  allocateTag: function(): number {
    // Skip over root IDs as those are reserved for native
    while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) {
      ReactNativeTagHandles.tagCount++;
    }
    var tag = ReactNativeTagHandles.tagCount;
    ReactNativeTagHandles.tagCount++;
    return tag;
  },

  assertRootTag: function(tag: number): void {
    invariant(
      this.reactTagIsNativeTopRootID(tag),
      'Expect a native root tag, instead got %s',
      tag,
    );
  },

  reactTagIsNativeTopRootID: function(reactTag: number): boolean {
    // We reserve all tags that are 1 mod 10 for native root views
    return reactTag % 10 === 1;
  },
};

实际跑一下,创建的第一个 RootView

self->_shadowViewRegistry:
{
    17 = "<RCTShadowView: 0x121ed46e0; viewName: RCTView; reactTag: 17; frame: {{0, 0}, {0, 0}}>";
    15 = "<RCTShadowText: 0x123940ff0; viewName: RCTText; reactTag: 15; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}; text: Tap me to load the next scene>";
    13 = "<RCTShadowView: 0x123940690; viewName: RCTView; reactTag: 13; frame: {{77.666666666666671, 357.66666666666669}, {258.66666666666669, 37.666666666666664}}>";
    9 = "<RCTShadowText: 0x12393f670; viewName: RCTText; reactTag: 9; frame: {{0, 0}, {132.33333333333334, 17}}; text: Current Scene: haha>";
    7 = "<RCTShadowView: 0x12393f2b0; viewName: RCTView; reactTag: 7; frame: {{0, 0}, {414, 736}}>";
    5 = "<RCTShadowView: 0x12393cf60; viewName: RCTNavigator; reactTag: 5; frame: {{0, 0}, {414, 736}}>";
    3 = "<RCTShadowView: 0x121e17f30; viewName: RCTView; reactTag: 3; frame: {{0, 0}, {414, 736}}>";
    1 = "<RCTRootShadowView: 0x121ec5cf0; viewName: RCTRootContentView; reactTag: 1; frame: {{0, 0}, {414, 736}}>";
    16 = "<RCTShadowRawText: 0x121ed3ee0; viewName: RCTRawText; reactTag: 16; frame: {{0, 0}, {nan, nan}}; text: Tap me to load the next scene>";
    14 = "<RCTShadowView: 0x123940eb0; viewName: RCTView; reactTag: 14; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}>";
    12 = "<RCTShadowRawText: 0x12393fc00; viewName: RCTRawText; reactTag: 12; frame: {{0, 0}, {nan, nan}}; text: haha>";
    10 = "<RCTShadowRawText: 0x12393f8b0; viewName: RCTRawText; reactTag: 10; frame: {{0, 0}, {nan, nan}}; text: Current Scene: >";
    8 = "<RCTShadowView: 0x121ecf2d0; viewName: RCTView; reactTag: 8; frame: {{141, 340.66666666666669}, {132.33333333333334, 17}}>";
    6 = "<RCTShadowView: 0x121ec73b0; viewName: RCTNavItem; reactTag: 6; frame: {{0, 0}, {414, 736}}>";
    4 = "<RCTShadowView: 0x12393ce20; viewName: RCTView; reactTag: 4; frame: {{0, 0}, {414, 736}}>";
    2 = "<RCTShadowView: 0x12393cce0; viewName: RCTView; reactTag: 2; frame: {{0, 0}, {414, 736}}>";
}

创建了多个 RootView 以后

{
    410 = "<RCTShadowRawText: 0x1014d0af0; viewName: RCTRawText; reactTag: 410; frame: {{0, 0}, {nan, nan}}; text: Tap me to load the next scene>";
    399 = "<RCTShadowView: 0x1016812f0; viewName: RCTNavigator; reactTag: 399; frame: {{0, 0}, {414, 736}}>";
    407 = "<RCTShadowView: 0x10169bc10; viewName: RCTView; reactTag: 407; frame: {{77.666666666666671, 357.66666666666669}, {258.66666666666669, 37.666666666666664}}>";
    396 = "<RCTShadowView: 0x101425af0; viewName: RCTView; reactTag: 396; frame: {{0, 0}, {414, 736}}>";
    221 = "<RCTRootShadowView: 0x101487710; viewName: RCTRootContentView; reactTag: 221; frame: {{0, 0}, {414, 736}}>";
    404 = "<RCTShadowText: 0x1016281d0; viewName: RCTText; reactTag: 404; frame: {{0, 0}, {132.33333333333334, 17}}; text: Current Scene: haha>";
    412 = "<RCTShadowView: 0x101405e40; viewName: RCTView; reactTag: 412; frame: {{0, 0}, {0, 0}}>";
    409 = "<RCTShadowText: 0x10148cf10; viewName: RCTText; reactTag: 409; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}; text: Tap me to load the next scene>";
    398 = "<RCTShadowView: 0x1014069b0; viewName: RCTView; reactTag: 398; frame: {{0, 0}, {414, 736}}>";
    406 = "<RCTShadowRawText: 0x10169c840; viewName: RCTRawText; reactTag: 406; frame: {{0, 0}, {nan, nan}}; text: haha>";
    403 = "<RCTShadowView: 0x101441830; viewName: RCTView; reactTag: 403; frame: {{141, 340.66666666666669}, {132.33333333333334, 17}}>";
    400 = "<RCTShadowView: 0x101677f00; viewName: RCTNavItem; reactTag: 400; frame: {{0, 0}, {414, 736}}>";
    408 = "<RCTShadowView: 0x1014c9b60; viewName: RCTView; reactTag: 408; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}>";
    397 = "<RCTShadowView: 0x10146c180; viewName: RCTView; reactTag: 397; frame: {{0, 0}, {414, 736}}>";
    405 = "<RCTShadowRawText: 0x1016820d0; viewName: RCTRawText; reactTag: 405; frame: {{0, 0}, {nan, nan}}; text: Current Scene: >";
    402 = "<RCTShadowView: 0x10e41c750; viewName: RCTView; reactTag: 402; frame: {{0, 0}, {414, 736}}>";
}

那么RCTUIManager都有哪些API提供给了JS呢,大致如下:

createView
updateView
setChildren
removeRootView
manageChildren
findSubviewIn
measure
dispatchViewManagerCommand

createView的作用是创建一个个的UIView,RCTView,各种nativeView,并且把传过来的JS的属性参数,一一赋值给nativeView
updateView的作用是,当JSComponent的布局信息,界面样子发生变化,JS来通知nativeView来更新对应的属性变化,样子变化
setChildren的作用是,告诉OC,那个tag的View是另一个tag的view的子view,需要执行addsubview,insertsubview等

如果要创建一个 view,会经过以下流程:
js传来viewName,通过初始化的_componentDataByName表获取RCTComponentData
dispatch_async(mainqueue)从JS通信线程抛到主线程创建UI
js传来了ReactTag,通过RCTComponentData的createViewWithTag方法创建界面
js传来了属性props,通过RCTComponentData的setProps:forView:方法进行属性赋值

  mountComponent: function mountComponent(transaction, hostParent, hostContainerInfo, context) {
    var tag = ReactNativeTagHandles.allocateTag();

    this._rootNodeID = tag;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

    var updatePayload = ReactNativeAttributePayload.create(this._currentElement.props, this.viewConfig.validAttributes);

    var nativeTopRootTag = hostContainerInfo._tag;
    UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);

    ReactNativeComponentTree.precacheNode(this, tag);

    this.initializeChildren(this._currentElement.props.children, tag, transaction, context);
    return tag;
  }

这样一来,基本上就完成了React.JS创建一个纯native界面的过程:

相关文章

网友评论

      本文标题:React Native 分析(四)UI对象创建和管理

      本文链接:https://www.haomeiwen.com/subject/srsmqxtx.html