3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ReactNativeソースコードリーディング <View>

Posted at

View

画面を表示する機能
https://github.com/facebook/react-native/tree/master/Libraries/Components/View

Reading

基本的にES5かつReact0.14以前のコードスタイルですね。

View
const View = React.createClass({
  mixins: [NativeMethodsMixin],

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

  statics: {
    ...statics,
  },

  ...,
});

property定義。
次に一番重要であろうpropTypesを見ていきます。

propTypes
  propTypes: {
    accessible: PropTypes.bool,

    accessibilityLabel: PropTypes.string,

    accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentType),
    /*
     * const AccessibilityComponentType = [
     *   'none',
     *   'button',
     *   'radiobutton_checked',
     *   'radiobutton_unchecked',
     * ];
     */

    accessibilityLiveRegion: PropTypes.oneOf([
      'none',
      'polite',
      'assertive',
    ]),

    // @platform android
    importantForAccessibility: PropTypes.oneOf([
      'auto',
      'yes',
      'no',
      'no-hide-descendants',
    ]),

    /*
     * - `'none'` - The element has no traits.
     * - `'button'` - The element should be treated as a button.
     * - `'link'` - The element should be treated as a link.
     * - `'header'` - The element is a header that divides content into sections.
     * - `'search'` - The element should be treated as a search field.
     * - `'image'` - The element should be treated as an image.
     * - `'selected'` - The element is selected.
     * - `'plays'` - The element plays sound.
     * - `'key'` - The element should be treated like a keyboard key.
     * - `'text'` - The element should be treated as text.
     * - `'summary'` - The element provides app summary information.
     * - `'disabled'` - The element is disabled.
     * - `'frequentUpdates'` - The element frequently changes its value.
     * - `'startsMedia'` - The element starts a media session.
     * - `'adjustable'` - The element allows adjustment over a range of values.
     * - `'allowsDirectInteraction'` - The element allows direct touch interaction for VoiceOver users.
     * - `'pageTurn'` - Informs VoiceOver that it should scroll to the next page when it finishes reading the contents of the element.
     *
     * @platform ios
     */
    accessibilityTraits: PropTypes.oneOfType([
      PropTypes.oneOf(AccessibilityTraits),
      PropTypes.arrayOf(PropTypes.oneOf(AccessibilityTraits)),
    ]),

    onAccessibilityTap: PropTypes.func,

    onMagicTap: PropTypes.func,

    testID: PropTypes.string,

    /*
     * touch interactions
     * func: (nativeEvent) => {}
     *
     * - `nativeEvent`
     *   - `changedTouches` - Array of all touch events that have changed since the last event.
     *   - `identifier` - The ID of the touch.
     *   - `locationX` - The X position of the touch, relative to the element.
     *   - `locationY` - The Y position of the touch, relative to the element.
     *   - `pageX` - The X position of the touch, relative to the root element.
     *   - `pageY` - The Y position of the touch, relative to the root element.
     *   - `target` - The node id of the element receiving the touch event.
     *   - `timestamp` - A time identifier for the touch, useful for velocity calculation.
     *   - `touches` - Array of all current touches on the screen.
     */

    // The View is now responding for touch events
    // reponderがactiveになる時に発火
    onResponderGrant: PropTypes.func,

    // The user is moving their finger.
    // ユーザの指が動いた時に発火
    onResponderMove: PropTypes.func,

    // Another responder is already active and will not release it to that `View` asking to be the responder.
    // 他のresponderがactiveで、responderがreleaseされずにこのresponderが発火できない時に発火
    onResponderReject: PropTypes.func,

    // Fired at the end of the touch.
    // responderがreleaseされる時に発火
    onResponderRelease: PropTypes.func,

    // The responder has been taken from the `View`
    // responderが終了された時に発火
    onResponderTerminate: PropTypes.func,

    // Some other `View` wants to become responder and is asking this `View` to release its responder. 
    // Returning `true` allows its release.
    // 他のViewがresponderを扱いたいというリクエストを受信した時に発火
    // trueを返せばresponderをrelease
    onResponderTerminationRequest: PropTypes.func,

    // Does this view want to become responder on the start of a touch?
    // touch開始時にresponderになるかどうか、trueかfalseを返す必要あり
    onStartShouldSetResponder: PropTypes.func,

    // If a parent `View` wants to prevent a child `View` from becoming responder on a touch start,
    // it should have this handler which returns `true`.
    // childrenのViewがtouch開始時にresponderを扱うことを防ぐ場合はtrueを返す
    onStartShouldSetResponderCapture: PropTypes.func,

    // Does this view want to "claim" touch responsiveness?
    // touchに主張した時はtrueを返す
    onMoveShouldSetResponder: PropTypes.func,

    // If a parent `View` wants to prevent a child `View` from becoming responder on a move,
    // it should have this handler which returns `true`.
    // childrenのViewがmove時にresponderを扱うことを防ぐ場合はtrueを返す
    onMoveShouldSetResponderCapture: PropTypes.func,

    /**
     * This defines how far a touch event can start away from the view.
     * Typical interface guidelines recommend touch targets that are at least
     * 30 - 40 points/density-independent pixels.
     *
     * For example, if a touchable view has a height of 20 the touchable height can be extended to
     * 40 with `hitSlop={{top: 10, bottom: 10, left: 0, right: 0}}`
     *
     * touchできる範囲を指定する
     */
    hitSlop: EdgeInsetsPropType,

    /**
     * Invoked on mount and layout changes with:
     *
     * `{nativeEvent: { layout: {x, y, width, height}}}`
     *
     * This event is fired immediately once the layout has been calculated, but
     * the new layout may not yet be reflected on the screen at the time the
     * event is received, especially if a layout animation is in progress.
     *
     * layoutが変化した時に一度呼ばれる
     */
    onLayout: PropTypes.func,

    // touch eventのtagetになるかを指定する
    pointerEvents: PropTypes.oneOf([
      'box-none',
      'none',
      'box-only',
      'auto',
    ]),

    style: stylePropType,

    // subviewをたくさん持っている場合に不必要に表示しないようにする?
    removeClippedSubviews: PropTypes.bool,

    // Whether this `View` should render itself (and all of its children) into a
    // single hardware texture on the GPU.
    // @platform android
    renderToHardwareTextureAndroid: PropTypes.bool,

    // Whether this `View` should be rendered as a bitmap before compositing.
    // @platform ios
    shouldRasterizeIOS: PropTypes.bool,

    // childrenを描画するためだけの場合に表示を最適化する
    // @platform android
    collapsable: PropTypes.bool,

    // @platform android
    needsOffscreenAlphaCompositing: PropTypes.bool,
  },

主にtouch eventを制御するpropsですね。
最後にrenderを見てみます。

render
  render: function() {
    // 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} />;
  },

何やらproductionではこのView自体がrenderingされるわけではないらしい・・・?
もう少し後の処理を見てみます。

View.js
const RCTView = requireNativeComponent('RCTView', View, {
  nativeOnly: {
    nativeBackgroundAndroid: true,
  }
});

let ViewToExport = RCTView;
if (__DEV__) {
  ViewToExport = View;
} else {
  Object.assign(RCTView, statics);
}

module.exports = ViewToExport;

ここですね。__DEV__=trueの場合にView Componentがexportされて、productionではRCTViewがexportされているようです。
productionを見ないと意味がないので、RCTViewを生成しているrequireNativeComponentを見ていきます。

requireNativeComponent.js
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;
  for (var key in nativeProps) {
    var useAttribute = false;
    var attribute = {};

    var differ = TypeToDifferMap[nativeProps[key]];
    if (differ) {
      attribute.diff = differ;
      useAttribute = true;
    }

    var processor = TypeToProcessorMap[nativeProps[key]];
    if (processor) {
      attribute.process = processor;
      useAttribute = true;
    }

    viewConfig.validAttributes[key] = useAttribute ? attribute : true;
  }

  viewConfig.validAttributes.style = ReactNativeStyleAttributes;

  return createReactNativeComponentClass(viewConfig);
}

createReactNativeComponentClassを返しているので見てみます。
https://github.com/facebook/react/blob/master/src/renderers/native/createReactNativeComponentClass.js
(なぜかReact本体の方にある・・・)

createReactNativeComponentClass
type ReactNativeBaseComponentViewConfig = {
  validAttributes: Object;
  uiViewClassName: string;
  propTypes?: Object,
}

var createReactNativeComponentClass = function(
  viewConfig: ReactNativeBaseComponentViewConfig
): ReactClass<any> {
  var Constructor = function(element) {
    this._currentElement = element;
    this._topLevelWrapper = null;
    this._hostParent = null;
    this._hostContainerInfo = null;
    this._rootNodeID = null;
    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: any): ReactClass<any>);
};

ReactClassのprototype chainしたConstructorが返されるようですね。
prototypeのReactNativeBaseComponentを見ていきましょう。

ReactNativeBaseComponent
type ReactNativeBaseComponentViewConfig = {
  validAttributes: Object;
  uiViewClassName: string;
}

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

Object.assign(
  ReactNativeBaseComponent.prototype,
  ReactMultiChild.Mixin,
  ReactNativeBaseComponent.Mixin,
  NativeMethodsMixin
);

色々なmethodがmixinされているようです。

これでproductionでは、ReactNativeBaseComponentの各methodとViewのpropsを持つReact.Componentが出来上がることがわかりました。

次回は何かしらのtouch eventの仕組みを見ていこうと思います。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?