react-native
Version:
A framework for building native apps using React
429 lines (357 loc) • 11.8 kB
JavaScript
/**
* Copyright 2013-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 ReactNativeFiber
* @flow
*/
;
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 ReactNativeInjection = require('ReactNativeInjection');
const ReactNativeTagHandles = require('ReactNativeTagHandles');
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactPortal = require('ReactPortal');
const UIManager = require('UIManager');
const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
const emptyObject = require('fbjs/lib/emptyObject');
const findNodeHandle = require('findNodeHandle');
const invariant = require('fbjs/lib/invariant');
const { injectInternals } = require('ReactFiberDevToolsHook');
const {
precacheFiberNode,
uncacheFiberNode,
updateFiberProps,
} = ReactNativeComponentTree;
ReactNativeInjection.inject();
type Container = number;
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);
} else {
uncacheFiberNode((node : any)._nativeTag);
(node : any)._children.forEach(recursivelyUncacheFiberNode);
}
}
const NativeRenderer = ReactFiberReconciler({
appendChild(
parentInstance : Instance | Container,
child : Instance | TextInstance
) : void {
if (typeof parentInstance === 'number') {
// Root container
UIManager.setChildren(
parentInstance, // containerTag
[(child : any)._nativeTag] // reactTags
);
} else {
const children = parentInstance._children;
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[], // moveFromIndices
[], // moveToIndices
[(child : any)._nativeTag], // addChildReactTags
[children.length - 1], // addAtIndices
[], // removeAtIndices
);
}
},
appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
parentInstance._children.push(child);
},
commitTextUpdate(
textInstance : TextInstance,
oldText : string,
newText : string
) : void {
UIManager.updateView(
textInstance, // reactTag
'RCTRawText', // viewName
{text: newText}, // props
);
},
commitMount(
instance : Instance,
type : string,
newProps : Props,
internalInstanceHandle : Object
) : void {
// Noop
},
commitUpdate(
instance : Instance,
updatePayloadTODO : Object,
type : string,
oldProps : Props,
newProps : Props,
internalInstanceHandle : Object
) : void {
const viewConfig = instance.viewConfig;
updateFiberProps(instance._nativeTag, newProps);
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes
);
UIManager.updateView(
(instance : any)._nativeTag, // reactTag
viewConfig.uiViewClassName, // viewName
updatePayload, // props
);
},
createInstance(
type : string,
props : Props,
rootContainerInstance : Container,
hostContext : {||},
internalInstanceHandle : Object
) : Instance {
const tag = ReactNativeTagHandles.allocateTag();
const viewConfig = ReactNativeViewConfigRegistry.get(type);
if (__DEV__) {
for (let key in viewConfig.validAttributes) {
if (props.hasOwnProperty(key)) {
deepFreezeAndThrowOnMutationInDev(props[key]);
}
}
}
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes
);
UIManager.createView(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload, // props
);
const component = new NativeHostComponent(tag, viewConfig);
precacheFiberNode(internalInstanceHandle, tag);
updateFiberProps(tag, props);
return component;
},
createTextInstance(
text : string,
rootContainerInstance : Container,
hostContext : {||},
internalInstanceHandle : Object,
) : TextInstance {
const tag = ReactNativeTagHandles.allocateTag();
UIManager.createView(
tag, // reactTag
'RCTRawText', // viewName
rootContainerInstance, // rootTag
{text: text} // props
);
precacheFiberNode(internalInstanceHandle, tag);
return tag;
},
finalizeInitialChildren(
parentInstance : Instance,
type : string,
props : Props,
rootContainerInstance : Container,
) : boolean {
// Don't send a no-op message over the bridge.
if (parentInstance._children.length === 0) {
return false;
}
// Map from child objects to native tags.
// Either way we need to pass a copy of the Array to prevent it from being frozen.
const nativeTags = parentInstance._children.map(
(child) => typeof child === 'number'
? child // Leaf node (eg text)
: child._nativeTag
);
UIManager.setChildren(
parentInstance._nativeTag, // containerTag
nativeTags // reactTags
);
return false;
},
getRootHostContext() : {||} {
return emptyObject;
},
getChildHostContext() : {||} {
return emptyObject;
},
getPublicInstance(instance) {
return instance;
},
insertBefore(
parentInstance : Instance | Container,
child : Instance | TextInstance,
beforeChild : Instance | TextInstance
) : void {
// TODO (bvaughn): Remove this check when...
// We create a wrapper object for the container in ReactNative render()
// Or we refactor to remove wrapper objects entirely.
// For more info on pros/cons see PR #8560 description.
invariant(
typeof parentInstance !== 'number',
'Container does not support insertBefore operation',
);
const children = (parentInstance : any)._children;
const beforeChildIndex = children.indexOf(beforeChild);
const index = children.indexOf(child);
// Move existing child or add new child?
if (index >= 0) {
children.splice(index, 1);
children.splice(beforeChildIndex, 0, child);
UIManager.manageChildren(
(parentInstance : any)._nativeTag, // containerID
[index], // moveFromIndices
[beforeChildIndex], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
children.splice(beforeChildIndex, 0, child);
UIManager.manageChildren(
(parentInstance : any)._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[(child : any)._nativeTag], // addChildReactTags
[beforeChildIndex], // addAtIndices
[], // removeAtIndices
);
}
},
prepareForCommit() : void {
// Noop
},
prepareUpdate(
instance : Instance,
type : string,
oldProps : Props,
newProps : Props,
rootContainerInstance : Container,
hostContext : {||}
) : null | Object {
return emptyObject;
},
removeChild(
parentInstance : Instance | Container,
child : Instance | TextInstance
) : void {
recursivelyUncacheFiberNode(child);
if (typeof parentInstance === 'number') {
UIManager.manageChildren(
parentInstance, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[0], // removeAtIndices
);
} else {
const children = parentInstance._children;
const index = children.indexOf(child);
children.splice(index, 1);
UIManager.manageChildren(
parentInstance._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[index], // removeAtIndices
);
}
},
resetAfterCommit() : void {
// Noop
},
resetTextContent(instance : Instance) : void {
// Noop
},
scheduleAnimationCallback: global.requestAnimationFrame,
scheduleDeferredCallback: global.requestIdleCallback,
shouldSetTextContent(props : Props) : boolean {
// TODO (bvaughn) Revisit this decision.
// Always returning false simplifies the createInstance() implementation,
// But creates an additional child Fiber for raw text children.
// No additional native views are created though.
// It's not clear to me which is better so I'm deferring for now.
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
return false;
},
useSyncScheduling: true,
});
ReactGenericBatching.injection.injectFiberBatchedUpdates(
NativeRenderer.batchedUpdates
);
const roots = new Map();
findNodeHandle.injection.injectFindNode(
(fiber: Fiber) => {
const instance: any = NativeRenderer.findHostInstance(fiber);
return instance ? instance._nativeTag : null;
}
);
findNodeHandle.injection.injectFindRootNodeID(
(instance) => instance._nativeTag
);
const ReactNative = {
findNodeHandle,
render(element : Element<any>, containerTag : any, callback: ?Function) {
let root = roots.get(containerTag);
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = NativeRenderer.createContainer(containerTag);
roots.set(containerTag, root);
}
NativeRenderer.updateContainer(element, root, null, callback);
return NativeRenderer.getPublicRootInstance(root);
},
unmountComponentAtNode(containerTag : number) {
const root = roots.get(containerTag);
if (root) {
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
NativeRenderer.updateContainer(null, root, null, () => {
roots.delete(containerTag);
});
}
},
unmountComponentAtNodeAndRemoveContainer(containerTag: number) {
ReactNative.unmountComponentAtNode(containerTag);
// Call back into native to remove all of the subviews from this container
UIManager.removeRootView(containerTag);
},
unstable_createPortal(children: ReactNodeList, containerTag : number, key : ?string = null) {
return ReactPortal.createPortal(children, containerTag, null, key);
},
unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
};
if (typeof injectInternals === 'function') {
injectInternals({
findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
findHostInstanceByFiber: NativeRenderer.findHostInstance,
});
}
module.exports = ReactNative;