「ReactNative」View建立過程淺析

做者:少林寺掃地的css

本文分析的源碼版本:node

"react": "16.3.1",
"react-native": "0.55.3",
複製代碼

如何能快速的分析view的建立過程呢?

有個好方法,經過打斷點來打印createInstance和render的調用棧,從而幫助咱們分析出RN建立view的過程。react

先看createInstance和render的調用棧與時序圖:android

createInstance調用
createInstance時序
render調用棧
render時序
頁面啓動的時候,RN會調用AppRegistry的runApplication方法,而後會調用renderApplication的renderApplication方法,以後會調用ReactNativeRenderer的renderd,這個接口是render的總入口,以後就是整個頁面的render過程。 以後咱們能夠觀察到createInstance和render的前半段過程都是同樣的,從performUnitOfWork開始不一樣,讓咱們看看performUnitOfWork函數:

function performUnitOfWork(workInProgress) {
    // The current, flushed, state of this fiber is the alternate.
    // Ideally nothing should rely on this, but relying on it here
    // means that we don't need an additional field on the work in // progress. var current = workInProgress.alternate; // See if beginning this work spawns more work. startWorkTimer(workInProgress); { ReactDebugCurrentFiber.setCurrentFiber(workInProgress); } if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) { stashedWorkInProgressProperties = assignFiberPropertiesInDEV( stashedWorkInProgressProperties, workInProgress ); } var next = beginWork(current, workInProgress, nextRenderExpirationTime); { ReactDebugCurrentFiber.resetCurrentFiber(); if (isReplayingFailedUnitOfWork) { // Currently replaying a failed unit of work. This should be unreachable, // because the render phase is meant to be idempotent, and it should // have thrown again. Since it didn't, rethrow the original error, so
        // React's internal stack is not misaligned. rethrowOriginalError(); } } if (true && ReactFiberInstrumentation_1.debugTool) { ReactFiberInstrumentation_1.debugTool.onBeginWork(workInProgress); } if (next === null) { // If this doesn't spawn new work, complete the current work.
      next = completeUnitOfWork(workInProgress);
    }

    ReactCurrentOwner.current = null;

    return next;
  }
複製代碼

從這裏咱們能夠看到,調用beginWork()函數獲取next節點,next就是下一個view容器,這個函數其實就是父render遍歷view樹的過程,過程當中還會執行每個子view的render。git

再看調用performUnitOfWork的函數workLoop就一目瞭然了,先看源碼:程序員

function workLoop(isAsync) {
    if (!isAsync) {
      // Flush all expired work.
      while (nextUnitOfWork !== null) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
    } else {
      // Flush asynchronous work until the deadline runs out of time.
      while (nextUnitOfWork !== null && !shouldYield()) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
    }
  }
複製代碼

在performUnitOfWork返回next不爲空的時候,執行while循環,就是遍歷view樹的過程,也就是render的遍歷過程了!github

再看completeUnitOfWork的調用源碼:json

if (next === null) {
      // If this doesn't spawn new work, complete the current work. next = completeUnitOfWork(workInProgress); } 複製代碼

當next===null時,也就是一個父view下的全部子view都已經遍歷過的時候,此時全部view信息都保存在workInProgress中,執行completeUnitOfWork(workInProgress),會再執行一個循環,若是這些view沒有被建立原生view的話,就會調用到createInstance建立原生view了(具體源碼能夠本身看下,比較簡單)小程序

至此,程序加載頁面時,是如何調用到各個view的render,以及是如何調用createInstance的過程就已經清晰了。react-native

接下來,以最基礎的View組件爲例,分析基礎組件render的具體過程。

node_modules/react-native/Libraries/Components/View/View.js

class View extends ReactNative.NativeComponent<Props> {
  static propTypes = ViewPropTypes;
  static childContextTypes = ViewContextTypes;

  viewConfig = {
    uiViewClassName: 'RCTView',
    validAttributes: ReactNativeViewAttributes.RCTView,
  };

  getChildContext(): ViewChildContext {
    return {
      isInAParentText: false,
    };
  }

  render() {
    invariant(
      !(this.context.isInAParentText && Platform.OS === 'android'),
      'Nesting of <View> within <Text> is not supported on Android.',
    );

    // WARNING: This method will not be used in production mode as in that mode we
    // replace wrapper component View with generated native wrapper RCTView. Avoid
    // adding functionality this component that you'd want to be available in both // dev and prod modes. return <RCTView {...this.props} />; } } const RCTView = requireNativeComponent('RCTView', View, { nativeOnly: { nativeBackgroundAndroid: true, nativeForegroundAndroid: true, }, }); 複製代碼

截取部分源碼,能夠看到viewConfig中有個uiViewClassName: 'RCTView',這個RCTView就是View控件在原生代碼中映射的控件類名。 render返回的RCTView是經過requireNativeComponent生成的,再看requireNativeComponent node_modules/react-native/Libraries/ReactNative/requireNativeComponent.js 源碼:

return createReactNativeComponentClass(viewName, getViewConfig);
複製代碼

這個部分最終返回上面函數的執行,因此再看看createReactNativeComponentClass node_modules/react-native/Libraries/Renderer/shims/createReactNativeComponentClass.js

'use strict';

const {
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');

module.exports =
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.createReactNativeComponentClass;
複製代碼

能夠看到createReactNativeComponentClass是在ReactNative中定義的 另外說下: __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 從字面理解估計是將來要廢棄的方法。 再看: node_modules/react-native/Libraries/Renderer/shims/ReactNative.js

'use strict';

import type {ReactNativeType} from 'ReactNativeTypes';

let ReactNative;

if (__DEV__) {
  ReactNative = require('ReactNativeRenderer-dev');
} else {
  ReactNative = require('ReactNativeRenderer-prod');
}

module.exports = (ReactNative: ReactNativeType);
複製代碼

咱們能夠發現ReactNative原來就是ReactNativeRenderer-dev或ReactNativeRenderer-prod,區別是啥程序員都懂得,至於ReactNativeType,這是相似一個簡化的接口,不作贅述了。

如今看ReactNativeRenderer-dev,看名字就知道這個類是Render的具體實現者代碼行數將近一萬五,只能截取着看了。

__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
    // Used as a mixin in many createClass-based components
    NativeMethodsMixin: NativeMethodsMixin,
    // Used by react-native-github/Libraries/ components
    ReactNativeBridgeEventPlugin: ReactNativeBridgeEventPlugin, // requireNativeComponent
    ReactNativeComponentTree: ReactNativeComponentTree, // ScrollResponder
    ReactNativePropRegistry: ReactNativePropRegistry, // flattenStyle, Stylesheet
    TouchHistoryMath: TouchHistoryMath, // PanResponder
    createReactNativeComponentClass: createReactNativeComponentClass, // RCTText, RCTView, ReactNativeART
    takeSnapshot: takeSnapshot
  }
複製代碼

先看這段,找到 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED下的createReactNativeComponentClass了,再看具體定義:

/**
 * Creates a renderable ReactNative host component.
 * Use this method for view configs that are loaded from UIManager.
 * Use createReactNativeComponentClass() for view configs defined within JavaScript.
 *
 * @param {string} config iOS View configuration.
 * @private
 */
var createReactNativeComponentClass = function(name, callback) {
  return register(name, callback);
};
複製代碼

再看register:

var viewConfigCallbacks = new Map();
var viewConfigs = new Map();
/**
 * Registers a native view/component by name.
 * A callback is provided to load the view config from UIManager.
 * The callback is deferred until the view is actually rendered.
 * This is done to avoid causing Prepack deopts.
 */
function register(name, callback) {
  invariant(
    !viewConfigCallbacks.has(name),
    "Tried to register two views with the same name %s",
    name
  );
  viewConfigCallbacks.set(name, callback);
  return name;
}
複製代碼

這樣就把這個view註冊到viewConfigCallbacks中去了,而viewConfigCallbacks是在下面函數中被使用的

/**
 * Retrieves a config for the specified view.
 * If this is the first time the view has been used,
 * This configuration will be lazy-loaded from UIManager.
 */
function get$1(name) {
  var viewConfig = void 0;
  if (!viewConfigs.has(name)) {
    var callback = viewConfigCallbacks.get(name);
    invariant(
      typeof callback === "function",
      "View config not found for name %s",
      name
    );
    viewConfigCallbacks.set(name, null);
    viewConfig = callback();
    viewConfigs.set(name, viewConfig);
  } else {
    viewConfig = viewConfigs.get(name);
  }
  invariant(viewConfig, "View config not found for name %s", name);
  return viewConfig;
}
複製代碼

get$1是在createInstance函數中被調用,也正是在這個函數中調用了UIManager.createView,UIManager.createView則是直接調用原生的接口函數來建立原生view的。

createInstance: function(
    type,
    props,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) {
    var tag = ReactNativeTagHandles.allocateTag();
    var viewConfig = get$1(type);

    {
      for (var key in viewConfig.validAttributes) {
        if (props.hasOwnProperty(key)) {
          deepFreezeAndThrowOnMutationInDev(props[key]);
        }
      }
    }

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

    UIManager.createView(
      tag, // reactTag
      viewConfig.uiViewClassName, // viewName
      rootContainerInstance, // rootTag
      updatePayload
    );

    var component = new ReactNativeFiberHostComponent(tag, viewConfig);

    precacheFiberNode(internalInstanceHandle, tag);
    updateFiberProps(tag, props);

    // Not sure how to avoid this cast. Flow is okay if the component is defined
    // in the same file but if it's external it can't see the types.
    return component;
  },
複製代碼

函數的最後,生成了component返回給調用者使用。

另外,補充一個ReactNativeRenderer的主要變量的關係圖,方便你們理解源碼:

這裏寫圖片描述

很是感謝閱讀

》》》》》》》》》》》》》》》》》》》》》從這裏斷開了,字段長度的限制《《《《《《《《《《《《《《《《《《《《《《《

一下開始都是Java的代碼了。 先給一個調用關係圖:

這裏寫圖片描述
原生的createView在UIManagerModule類中聲明,而UIManagerModule類時每一個控件都擁有的幫助管理控件的對象。createView源碼以下:

@ReactMethod
  public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
    if (DEBUG) {
      String message =
          "(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props;
      FLog.d(ReactConstants.TAG, message);
      PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
    }
    mUIImplementation.createView(tag, className, rootViewTag, props);
  }
複製代碼

註解@ReactMethod的意思就是這個函數會由JS代碼調用

咱們能夠看到個參數int tag, String className, int rootViewTag, ReadableMap props,正是JS代碼中傳入的參數 UIManager.createView( tag, // reactTag viewConfig.uiViewClassName, // viewName rootContainerInstance, // rootTag updatePayload ); 以後會調用UIImplementation的createView:

/**
   * Invoked by React to create a new node with a given tag, class name and properties.
   */
  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);
  }
複製代碼

cssNode,rootNode分別存放着,css樣式相關信息,和view自己的相關信息,此時真正咱們view的style的相關屬性經過props傳入,經過 new ReactStylesDiffMap(props);生成一個map,也就是styles,而後經過cssNode.updateProperties(styles);設置給cssNode,過程以下,具體實如今ReactShadowNodeImpl的updateProperties:

@Override
  public final void updateProperties(ReactStylesDiffMap props) {
    ViewManagerPropertyUpdater.updateProps(this, props);
    onAfterUpdateTransaction();
  }
複製代碼

再看ViewManagerPropertyUpdater.updateProps:

public static <T extends ReactShadowNode> void updateProps(T node, ReactStylesDiffMap props) {
    ShadowNodeSetter<T> setter = findNodeSetter(node.getClass());
    ReadableMap propMap = props.mBackingMap;
    ReadableMapKeySetIterator iterator = propMap.keySetIterator();
    while (iterator.hasNextKey()) {
      String key = iterator.nextKey();
      setter.setProperty(node, key, props);
    }
  }
複製代碼

while循環裏,全部樣式設置被一個一個從props取出,設置給以前的cssNode 這些執行後,會調用handleCreateView:

protected void handleCreateView(
          ReactShadowNode cssNode,
          int rootViewTag,
          @Nullable ReactStylesDiffMap styles) {
    if (!cssNode.isVirtual()) {
      mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles);
    }
  }

複製代碼

再看NativeViewHierarchyOptimizer.handleCreateView:

/**
   * Handles a createView call. May or may not actually create a native view.
   */
  public void handleCreateView(
      ReactShadowNode node,
      ThemedReactContext themedContext,
      @Nullable ReactStylesDiffMap initialProps) {
    if (!ENABLED) {
      int tag = node.getReactTag();
      mUIViewOperationQueue.enqueueCreateView(
          themedContext,
          tag,
          node.getViewClass(),
          initialProps);
      return;
    }
複製代碼

在這個會建立一個createview的任務,放入mUIViewOperationQueue隊列中等待執行, 熟悉handler都懂得,不熟悉的請百度一下安卓handler,如今只講最後的執行在CreateViewOperation的execute方法:

@Override
      public void execute() {
      Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
      mNativeViewHierarchyManager.createView(
          mThemedContext,
          mTag,
          mClassName,
          mInitialProps);
    }
複製代碼

再看mNativeViewHierarchyManager.createView:

public synchronized void createView(
      ThemedReactContext themedContext,
      int tag,
      String className,
      @Nullable ReactStylesDiffMap initialProps) {
    UiThreadUtil.assertOnUiThread();
    SystraceMessage.beginSection(
        Systrace.TRACE_TAG_REACT_VIEW,
        "NativeViewHierarchyManager_createView")
        .arg("tag", tag)
        .arg("className", className)
        .flush();
    try {
      ViewManager viewManager = mViewManagers.get(className);

      View view = viewManager.createView(themedContext, mJSResponderHandler);
      mTagsToViews.put(tag, view);
      mTagsToViewManagers.put(tag, viewManager);

      // Use android View id field to store React tag. This is possible since we don't inflate // React views from layout xmls. Thus it is easier to just reuse that field instead of // creating another (potentially much more expensive) mapping from view to React tag view.setId(tag); if (initialProps != null) { viewManager.updateProperties(view, initialProps); } } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW); } } 複製代碼

這裏最終找到了ViewManager經過createView建立view:

/**
   * Creates a view and installs event emitters on it.
   */
  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;
  }
複製代碼

這正的建立工做交給了createViewInstance,這是個虛函數,ViewManager是虛基類, 因此這正的建立工做交給了最終實現他的類來完成了,譬如ReactTextViewManager會 @Override public ReactTextView createViewInstance(ThemedReactContext context) { return new ReactTextView(context); } new一個ReactTextView實例出來,也就是說會根據JS那邊具體組件名稱(譬如View,Text,Image),來建立相應的native的View來! 這時就最終建立出原生VIew了!(IOS同理)

原文連接: tech.meicai.cn/detail/66, 也可微信搜索小程序「美菜產品技術團隊」,乾貨滿滿且每週更新,想學習技術的你不要錯過哦。

相關文章
相關標籤/搜索