Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError

[version]
^0.37.0
^0.38.0
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"eslint-plugin-react-internal": "file:eslint-rules",
"fbjs": "^0.8.9",
"fbjs-scripts": "^0.6.0",
"flow-bin": "^0.37.0",
"flow-bin": "^0.38.0",
"glob": "^6.0.1",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
Expand Down
4 changes: 4 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,10 @@ src/renderers/native/__tests__/ReactNativeMount-test.js
* should be able to create and update a native component
* returns the correct instance and calls it in the callback

src/renderers/native/__tests__/createReactNativeComponentClass-test.js
* should register viewConfigs
* should not allow viewConfigs with duplicate uiViewClassNames to be registered

src/renderers/shared/__tests__/ReactDebugTool-test.js
* should add and remove hooks
* warns once when an error is thrown in hook
Expand Down
100 changes: 33 additions & 67 deletions src/renderers/native/NativeMethodsMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,19 @@ var UIManager = require('UIManager');

var invariant = require('invariant');

type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => void

type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
) => void

type MeasureLayoutOnSuccessCallback = (
left: number,
top: number,
width: number,
height: number
) => void

function warnForStyleProps(props, validAttributes) {
for (var key in validAttributes.style) {
if (!(validAttributes[key] || props[key] === undefined)) {
console.error(
'You are setting the style `{ ' + key + ': ... }` as a prop. You ' +
'should nest it in a style object. ' +
'E.g. `{ style: { ' + key + ': ... } }`'
);
}
}
}
var {
mountSafeCallback,
throwOnStylesProp,
warnForStyleProps,
} = require('NativeMethodsMixinUtils');

import type {
MeasureInWindowOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
MeasureOnSuccessCallback,
NativeMethodsInterface,
} from 'NativeMethodsMixinUtils';
import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry';

/**
* `NativeMethodsMixin` provides methods to access the underlying native
Expand All @@ -65,7 +44,7 @@ function warnForStyleProps(props, validAttributes) {
* information, see [Direct
* Manipulation](docs/direct-manipulation.html).
*/
var NativeMethodsMixin = {
var NativeMethodsMixin : NativeMethodsInterface = {
/**
* Determines the location on screen, width, and height of the given view and
* returns the values via an async callback. If successful, the callback will
Expand Down Expand Up @@ -140,18 +119,34 @@ var NativeMethodsMixin = {
* Manipulation](docs/direct-manipulation.html)).
*/
setNativeProps: function(nativeProps: Object) {
const maybeViewConfig = (ReactNative.getViewConfig(this) : any);

// If there is no host component beneath this we should fail silently.
// This is not an error; it could mean a class component rendered null.
if (maybeViewConfig === null) {
return;
}

const viewConfig : ReactNativeBaseComponentViewConfig =
(maybeViewConfig : any);

const {
uiViewClassName,
validAttributes,
} = viewConfig;

if (__DEV__) {
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
warnForStyleProps(nativeProps, validAttributes);
}

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

UIManager.updateView(
(ReactNative.findNodeHandle(this) : any),
this.viewConfig.uiViewClassName,
uiViewClassName,
updatePayload
);
},
Expand All @@ -172,19 +167,6 @@ var NativeMethodsMixin = {
},
};

function throwOnStylesProp(component, props) {
if (props.styles !== undefined) {
var owner = component._owner || null;
var name = component.constructor.displayName;
var msg = '`styles` is not a supported property of `' + name + '`, did ' +
'you mean `style` (singular)?';
if (owner && owner.constructor && owner.constructor.displayName) {
msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' +
' component.';
}
throw new Error(msg);
}
}
if (__DEV__) {
// hide this from Flow since we can't define these properties outside of
// __DEV__ without actually implementing them (setting them to undefined
Expand All @@ -203,20 +185,4 @@ if (__DEV__) {
};
}

/**
* In the future, we should cleanup callbacks by cancelling them instead of
* using this.
*/
function mountSafeCallback(
context: ReactComponent<any, any, any>,
callback: ?Function
): any {
return function() {
if (!callback || (typeof context.isMounted === 'function' && !context.isMounted())) {
return undefined;
}
return callback.apply(context, arguments);
};
}

module.exports = NativeMethodsMixin;
100 changes: 100 additions & 0 deletions src/renderers/native/NativeMethodsMixinUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule NativeMethodsMixinUtils
* @flow
*/
'use strict';

export type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => void

export type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
) => void

export type MeasureLayoutOnSuccessCallback = (
left: number,
top: number,
width: number,
height: number
) => void

/**
* Shared between ReactNativeFiberHostComponent and NativeMethodsMixin to keep
* API in sync.
*/
export interface NativeMethodsInterface {
blur() : void,
focus() : void,
measure(callback : MeasureOnSuccessCallback) : void,
measureInWindow(callback : MeasureInWindowOnSuccessCallback) : void,
measureLayout(
relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */
) : void,
setNativeProps(nativeProps: Object) : void,
}

/**
* In the future, we should cleanup callbacks by cancelling them instead of
* using this.
*/
function mountSafeCallback(
context: any,
callback: ?Function
): any {
return function() {
if (!callback || (typeof context.isMounted === 'function' && !context.isMounted())) {
return undefined;
}
return callback.apply(context, arguments);
};
}

function throwOnStylesProp(component : any, props : any) {
if (props.styles !== undefined) {
var owner = component._owner || null;
var name = component.constructor.displayName;
var msg = '`styles` is not a supported property of `' + name + '`, did ' +
'you mean `style` (singular)?';
if (owner && owner.constructor && owner.constructor.displayName) {
msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' +
' component.';
}
throw new Error(msg);
}
}

function warnForStyleProps(props : any, validAttributes : any) {
for (var key in validAttributes.style) {
if (!(validAttributes[key] || props[key] === undefined)) {
console.error(
'You are setting the style `{ ' + key + ': ... }` as a prop. You ' +
'should nest it in a style object. ' +
'E.g. `{ style: { ' + key + ': ... } }`'
);
}
}
}

module.exports = {
mountSafeCallback,
throwOnStylesProp,
warnForStyleProps,
};
44 changes: 22 additions & 22 deletions src/renderers/native/ReactNativeFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,11 @@

'use strict';

import type { Element } from 'React';
import type { Fiber } from 'ReactFiber';
import type { ReactNodeList } from 'ReactTypes';
import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry';

const NativeMethodsMixin = require('NativeMethodsMixin');
const ReactFiberReconciler = require('ReactFiberReconciler');
const ReactGenericBatching = require('ReactGenericBatching');
const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
const ReactNativeComponentTree = require('ReactNativeComponentTree');
const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent');
const ReactNativeInjection = require('ReactNativeInjection');
const ReactNativeTagHandles = require('ReactNativeTagHandles');
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
Expand All @@ -34,6 +29,11 @@ const findNodeHandle = require('findNodeHandle');
const invariant = require('invariant');

const { injectInternals } = require('ReactFiberDevToolsHook');

import type { Element } from 'React';
import type { Fiber } from 'ReactFiber';
import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry';
import type { ReactNodeList } from 'ReactTypes';
const {
precacheFiberNode,
uncacheFiberNode,
Expand All @@ -43,21 +43,14 @@ const {
ReactNativeInjection.inject();

type Container = number;
type Instance = {
export type Instance = {
_children: Array<Instance | number>,
_nativeTag: number,
viewConfig: ReactNativeBaseComponentViewConfig,
};
type Props = Object;
type TextInstance = number;

function NativeHostComponent(tag, viewConfig) {
this._nativeTag = tag;
this._children = [];
this.viewConfig = viewConfig;
}
Object.assign(NativeHostComponent.prototype, NativeMethodsMixin);

function recursivelyUncacheFiberNode(node : Instance | TextInstance) {
if (typeof node === 'number') { // Leaf node (eg text)
uncacheFiberNode(node);
Expand Down Expand Up @@ -156,7 +149,7 @@ const NativeRenderer = ReactFiberReconciler({
const viewConfig = ReactNativeViewConfigRegistry.get(type);

if (__DEV__) {
for (let key in viewConfig.validAttributes) {
for (const key in viewConfig.validAttributes) {
if (props.hasOwnProperty(key)) {
deepFreezeAndThrowOnMutationInDev(props[key]);
}
Expand All @@ -175,12 +168,14 @@ const NativeRenderer = ReactFiberReconciler({
updatePayload, // props
);

const component = new NativeHostComponent(tag, viewConfig);
const component = new ReactNativeFiberHostComponent(tag, viewConfig);

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

return component;
// 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 : any) : Instance);
},

createTextInstance(
Expand Down Expand Up @@ -367,17 +362,22 @@ ReactGenericBatching.injection.injectFiberBatchedUpdates(
const roots = new Map();

findNodeHandle.injection.injectFindNode(
(fiber: Fiber) => {
const instance: any = NativeRenderer.findHostInstance(fiber);
return instance ? instance._nativeTag : null;
}
(fiber: Fiber) => NativeRenderer.findHostInstance(fiber)
);
findNodeHandle.injection.injectFindRootNodeID(
(instance) => instance._nativeTag
);

const ReactNative = {
findNodeHandle,
getViewConfig(componentOrHandle : any) : ?ReactNativeBaseComponentViewConfig {
const instance: any = findNodeHandle(componentOrHandle);
return instance ? instance.viewConfig : null;
},

findNodeHandle(componentOrHandle : any) : ?number {
const instance: any = findNodeHandle(componentOrHandle);
return instance ? instance._nativeTag : null;
},

render(element : Element<any>, containerTag : any, callback: ?Function) {
let root = roots.get(containerTag);
Expand Down
Loading