1. 程式人生 > >React-Native-原始碼分析三-JSX如何渲染成原生頁面(下)

React-Native-原始碼分析三-JSX如何渲染成原生頁面(下)

前文中這次會反推JSX如何最終變化為原生控制元件的過程,上面這部分算是原生的繪製已經結束,下面開始到JS程式碼中找,JSX佈局如何傳達到原生的。

經驗之談:要憑藉我的半吊子js和C水平要去扒拉React-Native js部分的程式碼,也是夠吃力的,但是我找到了一個很好的工具-webStorm,之前使用sublime text,不能檢視類直接的依賴,不能全域性查詢引用類的地方,在面對幾百個類和他們直接錯綜複雜的關係的時候,著實心累。有了webStom可以直接跳轉到引用的類中,如果要查一個類在什麼地方用到,可以使用shift+command+F查詢到所有的使用到這個字串的地方,是在陌生領域探索的利器。還有就是在資料夾中全文搜尋檔名,也是常用查詢方式。

在檢視JS程式碼之前首先要找到一個突破口,因為我的腦子裡面一直有個疑問,就是React和React-Native是如何搭配工作的,我們就從這個問題入手開始分析。

先看一下一個很普通的RN頁面

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

class TestReact extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
      </View>
    );
  }
}
...

AppRegistry.registerComponent('TestReact'
, () => TestReact);

我們發現Component 是解構賦值於react,而Text 來自 react-native 那我們就到react.js中去看一下

node_modules/react/lib/React.js

'use strict';

var _assign = require('object-assign');

var ReactChildren = require('./ReactChildren');
var ReactComponent = require('./ReactComponent');
var ReactPureComponent = require
('./ReactPureComponent'); var ReactClass = require('./ReactClass'); var ReactDOMFactories = require('./ReactDOMFactories'); var ReactElement = require('./ReactElement'); var ReactPropTypes = require('./ReactPropTypes'); var ReactVersion = require('./ReactVersion'); ... var React = { // Modern Children: { map: ReactChildren.map, forEach: ReactChildren.forEach, count: ReactChildren.count, toArray: ReactChildren.toArray, only: onlyChild }, Component: ReactComponent, PureComponent: ReactPureComponent, createElement: createElement, cloneElement: cloneElement, isValidElement: ReactElement.isValidElement, // Classic PropTypes: ReactPropTypes, createClass: ReactClass.createClass, createFactory: createFactory, module.exports = React;

發現React只是引用了ReactComponent,ReactClass等類然後賦值給了自己的變數,想到老的寫法中有React.createClass這樣來建立元件的,那就到ReactClass中看下

var ReactClassInterface = {

  mixins: SpecPolicy.DEFINE_MANY,

  statics: SpecPolicy.DEFINE_MANY,

  propTypes: SpecPolicy.DEFINE_MANY,

  contextTypes: SpecPolicy.DEFINE_MANY,

  childContextTypes: SpecPolicy.DEFINE_MANY,

  getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED,

  getInitialState: SpecPolicy.DEFINE_MANY_MERGED,

  getChildContext: SpecPolicy.DEFINE_MANY_MERGED,

  render: SpecPolicy.DEFINE_ONCE,

  componentWillMount: SpecPolicy.DEFINE_MANY,

  componentDidMount: SpecPolicy.DEFINE_MANY,

  componentWillReceiveProps: SpecPolicy.DEFINE_MANY,

  shouldComponentUpdate: SpecPolicy.DEFINE_ONCE,

  componentWillUpdate: SpecPolicy.DEFINE_MANY,

  componentDidUpdate: SpecPolicy.DEFINE_MANY,

  componentWillUnmount: SpecPolicy.DEFINE_MANY,

  updateComponent: SpecPolicy.OVERRIDE_BASE

};

很熟悉是不是,這不就是RN的生命週期嘛,找到了生命週期,那麼我就看也沒有地方實現了這個介面並且呼叫render方法的,因為Rn是通過render方法來把資料傳遞到native,控制native渲染UI的。
在ReactClass.js中全域性搜尋render,並沒有發現render的實現,再到ReactComponent.js中搜索也沒有發現render的實現,這個時候感覺這樣來查詢好像大海撈針,我們還沒有找到突破口,那我們換個思路,由大到小走不通,那我們就由小到大,由具體到抽象,從一個UI控制元件的實現來看看也沒有什麼收穫。

隨便找個控制元件RefreshControl

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

'use strict';

const ColorPropType = require('ColorPropType');
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const Platform = require('Platform');
const React = require('React');
const View = require('View');

const requireNativeComponent = require('requireNativeComponent');

if (Platform.OS === 'android') {
  var RefreshLayoutConsts = require('UIManager').AndroidSwipeRefreshLayout.Constants;
} else {
  var RefreshLayoutConsts = {SIZE: {}};
}

const RefreshControl = React.createClass({
  statics: {
    SIZE: RefreshLayoutConsts.SIZE,
  },

  mixins: [NativeMethodsMixin],

  propTypes: {
    ...View.propTypes,

    onRefresh: React.PropTypes.func,

    refreshing: React.PropTypes.bool.isRequired,

    tintColor: ColorPropType,

    titleColor: ColorPropType,

    title: React.PropTypes.string,

    enabled: React.PropTypes.bool,

    colors: React.PropTypes.arrayOf(ColorPropType),

    progressBackgroundColor: ColorPropType,

    size: React.PropTypes.oneOf([RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE]),

    progressViewOffset: React.PropTypes.number,
  },

  _nativeRef: (null: any),
  _lastNativeRefreshing: false,

  componentDidMount() {
    this._lastNativeRefreshing = this.props.refreshing;
  },

  componentDidUpdate(prevProps: {refreshing: boolean}) {

    if (this.props.refreshing !== prevProps.refreshing) {
      this._lastNativeRefreshing = this.props.refreshing;
    } else if (this.props.refreshing !== this._lastNativeRefreshing) {
      this._nativeRef.setNativeProps({refreshing: this.props.refreshing});
      this._lastNativeRefreshing = this.props.refreshing;
    }
  },

  render() {
    return (
      <NativeRefreshControl
        {...this.props}
        ref={ref => this._nativeRef = ref}
        onRefresh={this._onRefresh}
      />
    );
  },

  _onRefresh() {
    this._lastNativeRefreshing = true;

    this.props.onRefresh && this.props.onRefresh();

    this.forceUpdate();
  },
});

if (Platform.OS === 'ios') {
  var NativeRefreshControl = requireNativeComponent(
    'RCTRefreshControl',
    RefreshControl
  );
} else if (Platform.OS === 'android') {
  var NativeRefreshControl = requireNativeComponent(
    'AndroidSwipeRefreshLayout',
    RefreshControl
  );
}

module.exports = RefreshControl;

跳過前面的屬性定義,直接來看render是如何渲染控制元件的

render() {
    return (
      <NativeRefreshControl
        {...this.props}
        ref={ref => this._nativeRef = ref}
        onRefresh={this._onRefresh}
      />
    );
  },

NativeRefreshControl是ios和Android平臺通用的控制元件,所以有了下面區分平臺的相容程式碼

if (Platform.OS === 'ios') {
  var NativeRefreshControl = requireNativeComponent(
    'RCTRefreshControl',
    RefreshControl
  );
} else if (Platform.OS === 'android') {
  var NativeRefreshControl = requireNativeComponent(
    'AndroidSwipeRefreshLayout',
    RefreshControl
  );
}

我們的目光停在了requireNativeComponent這個方法上,在ios平臺使用RCTRefreshControl,在Android平臺使用AndroidSwipeRefreshLayout,看來就是他來相容各平臺的api。在資料夾內全域性搜尋
requireNativeComponent.js(這個類不在同級目錄,所以不方便找,這個時候就全域性搜尋)

'use strict';

var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
var UIManager = require('UIManager');
var UnimplementedView = require('UnimplementedView');

var createReactNativeComponentClass = require('react/lib/createReactNativeComponentClass');
...
import type { ComponentInterface } from 'verifyPropTypes';

function requireNativeComponent(
  viewName: string,
  componentInterface?: ?ComponentInterface,
  extraConfig?: ?{nativeOnly?: Object},
): Function {
  var viewConfig = UIManager[viewName];
  if (!viewConfig || !viewConfig.NativeProps) {
    warning(false, 'Native component for "%s" does not exist', viewName);
    return UnimplementedView;
  }
  var nativeProps = {
    ...UIManager.RCTView.NativeProps,
    ...viewConfig.NativeProps,
  };
  viewConfig.uiViewClassName = viewName;
  viewConfig.validAttributes = {};
  viewConfig.propTypes = componentInterface && componentInterface.propTypes;
  ...
  viewConfig.validAttributes.style = ReactNativeStyleAttributes;

  return createReactNativeComponentClass(viewConfig);
}

requireNativeComponent根據前面傳過來的viewname,extraConfig,生成了配置變數viewConfig,最後呼叫createReactNativeComponentClass(viewConfig)

var createReactNativeComponentClass = require('react/lib/createReactNativeComponentClass');

createReactNativeComponentClass來自react的lib目錄下,看到了react有點欣喜,感覺這條路走對了,不廢話,繼續跟入

/node_modules/react/lib/createReactNativeComponentClass.js

'use strict';

var ReactNativeBaseComponent = require('./ReactNativeBaseComponent');

var createReactNativeComponentClass = function (viewConfig) {
  var Constructor = function (element) {
    this._currentElement = element;
    this._topLevelWrapper = null;
    this._hostParent = null;
    this._hostContainerInfo = null;
    this._rootNodeID = 0;
    this._renderedChildren = null;
  };
  Constructor.displayName = viewConfig.uiViewClassName;
  Constructor.viewConfig = viewConfig;
  Constructor.propTypes = viewConfig.propTypes;
  Constructor.prototype = new ReactNativeBaseComponent(viewConfig);
  Constructor.prototype.constructor = Constructor;

  return Constructor;
};

module.exports = createReactNativeComponentClass;

createReactNativeComponentClass方法很簡單,返回了一個建構函式,但是我們傳入的viewConfig被new 了一個new ReactNativeBaseComponent(viewConfig)

'use strict';

var _assign = require('object-assign');

var NativeMethodsMixin = require('./NativeMethodsMixin');
var ReactNativeAttributePayload = require('./ReactNativeAttributePayload');
var ReactNativeComponentTree = require('./ReactNativeComponentTree');
var ReactNativeEventEmitter = require('./ReactNativeEventEmitter');
var ReactNativeTagHandles = require('./ReactNativeTagHandles');
var ReactMultiChild = require('./ReactMultiChild');
var UIManager = require('react-native/lib/UIManager');

var ReactNativeBaseComponent = function (viewConfig) {
  this.viewConfig = viewConfig;
};


ReactNativeBaseComponent.Mixin = {
  getPublicInstance: function () {
    // TODO: This should probably use a composite wrapper
    return this;
  },

  unmountComponent: function () {
    ReactNativeComponentTree.uncacheNode(this);
    deleteAllListeners(this);
    this.unmountChildren();
    this._rootNodeID = 0;
  },

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

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

    if (process.env.NODE_ENV !== 'production') {
      for (var key in this.viewConfig.validAttributes) {
        if (this._currentElement.props.hasOwnProperty(key)) {
          deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
        }
      }
    }

    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._registerListenersUponCreation(this._currentElement.props);
    this.initializeChildren(this._currentElement.props.children, tag, transaction, context);
    return tag;
  }
};

_assign(ReactNativeBaseComponent.prototype, ReactMultiChild.Mixin, ReactNativeBaseComponent.Mixin, NativeMethodsMixin);

module.exports = ReactNativeBaseComponent;

進到ReactNativeBaseComponent 裡面我們發現了倆個很重要的地方:

  1. var UIManager = require(‘react-native/lib/UIManager’);UIManager是JS管理原生UI的的控制類,它的出現代表著這裡有人要直接控制原生UI

  2. mountComponent: function (transaction, hostParent, hostContainerInfo, context) 基本上就是render的意思,仔細研究一下這個方法

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

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

    if (process.env.NODE_ENV !== 'production') {
      for (var key in this.viewConfig.validAttributes) {
        if (this._currentElement.props.hasOwnProperty(key)) {
          deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
        }
      }
    }

    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._registerListenersUponCreation(this._currentElement.props);
    this.initializeChildren(this._currentElement.props.children, tag, transaction, context);
    return tag;
  }
};

UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload) 找到了這個方法,就是找到了突破口,剛剛一路跟過來,我們在RefreshControl render方法中發現是new 了一個ReactNativeBaseComponent(),現在發現ReactNativeBaseComponent的mountComponent方法直接就呼叫了UIManager.createView,這和我們上一篇中講到的com/facebook/react/uimanager/UIManagerModule.java中的createView方法難道不謀而合?我們直接點UIManager.createView進去看看,發現跳轉到了不是UIManager.js 而是react-native/Libraries/ReactNative/UIManagerStatTracker.js這個不知道又是JS什麼奇葩的技能導致的。不管了,不懂的東西已經那麼多了,不在乎再多一個,直接看

“`
var UIManager = require(‘UIManager’);

var installed = false;
var UIManagerStatTracker = {
install: function() {
if (installed) {
return;
}
installed = true;
var statLogHandle;
var stats = {};
function printStats() {
console.log({UIManagerStatTracker: stats});
statLogHandle = null;
}
function incStat(key: string, increment: number) {
stats[key] = (stats[key] || 0) + increment;
if (!statLogHandle) {
statLogHandle = setImmediate(printStats);
}
}
var createViewOrig = UIManager.createView;
UIManager.createView = function(tag, className, rootTag, props) {
incStat(‘createView’, 1);
incStat(‘setProp’, Object.keys(props || []).length);
createViewOrig(tag, className, rootTag, props);
};
var updateViewOrig = UIManager.updateView;
UIManager.updateView = function(tag, className, props) {
incStat(‘updateView’, 1);
incStat(‘setProp’, Object.keys(props || []).length);
updateViewOrig(tag, className, props);
};
var manageChildrenOrig = UIManager.manageChildren;
UIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) {
incStat(‘manageChildren’, 1);
incStat(‘move’, Object.keys(moveFrom || []).length);
incStat(‘remove’, Object.keys(remove || []).length);
manageChildrenOrig(tag, moveFrom, moveTo, addTags, addIndices, remove);
};
},
};

module.exports = UIManagerStatTracker;
“`
有意思的東西出現了:

  • UIManager.createView
  • UIManager.updateView
  • UIManager.manageChildren

這三個方法在UIManagerModule中也出現過

com/facebook/react/uimanager/UIManagerModule.java

public class UIManagerModule extends ReactContextBaseJavaModule implements
    OnBatchCompleteListener, LifecycleEventListener {

...

  @ReactMethod
  public void removeRootView(int rootViewTag) {
    mUIImplementation.removeRootView(rootViewTag);
  }


  @ReactMethod
  public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
    if (DEBUG) {
      FLog.d(
          ReactConstants.TAG,
          "(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props);
    }
    mUIImplementation.createView(tag, className, rootViewTag, props);
  }

  @ReactMethod
  public void updateView(int tag, String className, ReadableMap props) {
    if (DEBUG) {
      FLog.d(
          ReactConstants.TAG,
          "(UIManager.updateView) tag: " + tag + ", class: " + className + ", props: " + props);
    }
    mUIImplementation.updateView(tag, className, props);
  }

  @ReactMethod
  public void manageChildren(
      int viewTag,
      @Nullable ReadableArray moveFrom,
      @Nullable ReadableArray moveTo,
      @Nullable ReadableArray addChildTags,
      @Nullable ReadableArray addAtIndices,
      @Nullable ReadableArray removeFrom) {
    if (DEBUG) {
      FLog.d(
          ReactConstants.TAG,
          "(UIManager.manageChildren) tag: " + viewTag +
          ", moveFrom: " + moveFrom +
          ", moveTo: " + moveTo +
          ", addTags: " + addChildTags +
          ", atIndices: " + addAtIndices +
          ", removeFrom: " + removeFrom);
    }
    mUIImplementation.manageChildren(
        viewTag,
        moveFrom,
        moveTo,
        addChildTags,
        addAtIndices,
        removeFrom);
  }
    ...
}

這時候我們可以認為這個地方就是在呼叫原生的方法在createView或者是建立了createView的配置資訊。

分析到這裡我們已經有點眉目了,原來Rn和原生一樣,也是先渲染內部子控制元件,然後再渲染外部控制元件。所以Component來自React的,但是UI控制元件是React-Native的,在render生命週期執行的時候會執行子控制元件的render方法,子控制元件會呼叫UIManager來把資訊傳遞到原始的UIManagerModule,UIManagerModule根據傳過來的Tag找到對應的UIManager,最後生成一個Operation新增到UI處理佇列中,當mDispatchUIRunnables執行runable的時候呼叫Operation.execute抽象方法,其實就是呼叫UIManager.createViewInstance來真正生成View,然後呼叫viewManager.updateProperties 設定View的屬性。這樣一個控制元件就創建出來了。

最後附上The Life-Cycle of a Composite Component

react/lib/ReactCompositeComponent.js

/**
 * ------------------ The Life-Cycle of a Composite Component ------------------
 *
 * - constructor: Initialization of state. The instance is now retained.
 *   - componentWillMount
 *   - render
 *   - [children's constructors]
 *     - [children's componentWillMount and render]
 *     - [children's componentDidMount]
 *     - componentDidMount
 *
 *       Update Phases:
 *       - componentWillReceiveProps (only called if parent updated)
 *       - shouldComponentUpdate
 *         - componentWillUpdate
 *           - render
 *           - [children's constructors or receive props phases]
 *         - componentDidUpdate
 *
 *     - componentWillUnmount
 *     - [children's componentWillUnmount]
 *   - [children destroyed]
 * - (destroyed): The instance is now blank, released by React and ready for GC.
 *
 * -----------------------------------------------------------------------------
 */