美文网首页
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