Permalink
Browse files

Improve flow typing

Summary:
- Properly inherit flow types from base components, including `defaultProps`
- template-ify the `Item` type as it flows from the `data` prop into `ItemComponent`

Note that for `SectionList` is is harder to do the `Item` typing because each section in the
`sections` array can have a different `Item` type, plus all the optional overrides...not sure how to
tackle that.

Reviewed By: yungsters

Differential Revision: D4557523

fbshipit-source-id: a0c5279fcdaabe6aab5fe11743a99c3715a44032
  • Loading branch information...
sahrens authored and facebook-github-bot committed Feb 17, 2017
1 parent c529a06 commit 63d3ea17a717f032c59f0de315f4b197ededcfec
View
@@ -44,6 +44,7 @@ suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-9]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-9]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
@@ -108,7 +108,7 @@ class FlatListExample extends React.PureComponent {
key={(this.state.horizontal ? 'h' : 'v') + (this.state.fixedHeight ? 'f' : 'd')}
legacyImplementation={false}
numColumns={1}
onRefresh={() => alert('onRefresh: nothing to refresh :P')}
onRefresh={this._onRefresh}
onViewableItemsChanged={this._onViewableItemsChanged}
ref={this._captureRef}
refreshing={false}
@@ -121,6 +121,7 @@ class FlatListExample extends React.PureComponent {
_getItemLayout = (data: any, index: number) => {
return getItemLayout(data, index, this.state.horizontal);
};
_onRefresh = () => alert('onRefresh: nothing to refresh :P');
_renderItemComponent = ({item}) => {
return (
<ItemComponent
@@ -154,7 +155,7 @@ class FlatListExample extends React.PureComponent {
_pressItem = (key: number) => {
pressItem(this, key);
};
_listRef: FlatList;
_listRef: FlatList<*>;
}
@@ -41,22 +41,23 @@ const invariant = require('invariant');
import type {StyleObj} from 'StyleSheetTypes';
import type {Viewable} from 'ViewabilityHelper';
import type {Props as VirtualizedListProps} from 'VirtualizedList';
type Item = any;
type RequiredProps = {
type RequiredProps<ItemT> = {
/**
* Note this can be a normal class component, or a functional component, such as a render method
* on your main component.
*/
ItemComponent: ReactClass<{item: Item, index: number}>,
ItemComponent: ReactClass<{item: ItemT, index: number}>,
/**
* For simplicity, data is just a plain array. If you want to use something else, like an
* immutable list, use the underlying `VirtualizedList` directly.
*/
data: ?Array<Item>,
data: ?Array<ItemT>,
};
type OptionalProps = {
type OptionalProps<ItemT> = {
/**
* Rendered at the bottom of all the items.
*/
@@ -79,7 +80,7 @@ type OptionalProps = {
* Remember to include separator length (height or width) in your offset calculation if you
* specify `SeparatorComponent`.
*/
getItemLayout?: (data: ?Array<Item>, index: number) =>
getItemLayout?: (data: ?Array<ItemT>, index: number) =>
{length: number, offset: number, index: number},
/**
* If true, renders items next to each other horizontally instead of stacked vertically.
@@ -90,7 +91,7 @@ type OptionalProps = {
* and as the react key to track item re-ordering. The default extractor checks item.key, then
* falls back to using the index, like react does.
*/
keyExtractor: (item: Item, index: number) => string,
keyExtractor: (item: ItemT, index: number) => string,
/**
* Multiple columns can only be rendered with horizontal={false} and will zig-zag like a flexWrap
* layout. Items should all be the same height - masonry layouts are not supported.
@@ -111,6 +112,7 @@ type OptionalProps = {
* `viewablePercentThreshold` prop.
*/
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
legacyImplementation?: ?boolean,
/**
* Set this true while waiting for new data from a refresh.
*/
@@ -123,11 +125,19 @@ type OptionalProps = {
* Optional optimization to minimize re-rendering items.
*/
shouldItemUpdate: (
prevProps: {item: Item, index: number},
nextProps: {item: Item, index: number}
prevProps: {item: ItemT, index: number},
nextProps: {item: ItemT, index: number}
) => boolean,
};
type Props = RequiredProps & OptionalProps; // plus props from the underlying implementation
type Props<ItemT> = RequiredProps<ItemT> & OptionalProps<ItemT> & VirtualizedListProps;
const defaultProps = {
...VirtualizedList.defaultProps,
getItem: undefined,
getItemCount: undefined,
numColumns: 1,
};
type DefaultProps = typeof defaultProps;
/**
* A performant interface for rendering simple, flat lists, supporting the most handy features:
@@ -148,13 +158,9 @@ type Props = RequiredProps & OptionalProps; // plus props from the underlying im
* ItemComponent={({item}) => <Text>{item.key}</Text>}
* />
*/
class FlatList extends React.PureComponent {
static defaultProps = {
keyExtractor: VirtualizedList.defaultProps.keyExtractor,
numColumns: 1,
shouldItemUpdate: VirtualizedList.defaultProps.shouldItemUpdate,
};
props: Props;
class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, void> {
static defaultProps: DefaultProps = defaultProps;
props: Props<ItemT>;
/**
* Scrolls to the end of the content. May be janky without getItemLayout prop.
*/
@@ -191,7 +197,7 @@ class FlatList extends React.PureComponent {
this._checkProps(this.props);
}
componentWillReceiveProps(nextProps: Props) {
componentWillReceiveProps(nextProps: Props<ItemT>) {
this._checkProps(nextProps);
}
@@ -200,7 +206,7 @@ class FlatList extends React.PureComponent {
_captureRef = (ref) => { this._listRef = ref; };
_checkProps(props: Props) {
_checkProps(props: Props<ItemT>) {
const {
getItem,
getItemCount,
@@ -229,7 +235,7 @@ class FlatList extends React.PureComponent {
}
}
_getItem = (data: Array<Item>, index: number): Item | Array<Item> => {
_getItem = (data: Array<ItemT>, index: number): ItemT | Array<ItemT> => {
const {numColumns} = this.props;
if (numColumns > 1) {
const ret = [];
@@ -243,13 +249,19 @@ class FlatList extends React.PureComponent {
}
};
_getItemCount = (data: Array<Item>): number => {
_getItemCount = (data: Array<ItemT>): number => {
return Math.floor(data.length / this.props.numColumns);
};
_keyExtractor = (items: Item | Array<Item>, index: number): string => {
_keyExtractor = (items: ItemT | Array<ItemT>, index: number): string => {
const {keyExtractor, numColumns} = this.props;
if (numColumns > 1) {
invariant(
Array.isArray(items),
'FlatList: Encountered internal consistency error, expected each item to consist of an ' +
'array with 1-%s columns; instead, received a single item.',
numColumns,
);
return items.map((it, kk) => keyExtractor(it, index * numColumns + kk)).join(':');
} else {
return keyExtractor(items, index);
@@ -49,7 +49,7 @@ type NormalProps = {
// Provide either `items` or `sections`
items?: ?Array<Item>, // By default, an Item is assumed to be {key: string}
sections?: ?Array<{key: string, items: Array<Item>}>,
sections?: ?Array<{key: string, data: Array<Item>}>,
/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
@@ -146,7 +146,7 @@ class MetroListView extends React.Component {
const sections = {};
props.sections.forEach((sectionIn, ii) => {
const sectionID = 's' + ii;
sections[sectionID] = sectionIn.itemData;
sections[sectionID] = sectionIn.data;
sectionHeaderData[sectionID] = sectionIn;
});
return {
@@ -37,43 +37,43 @@ const React = require('React');
const VirtualizedSectionList = require('VirtualizedSectionList');
import type {Viewable} from 'ViewabilityHelper';
import type {Props as VirtualizedSectionListProps} from 'VirtualizedSectionList';
type Item = any;
type SectionItem = any;
type SectionBase = {
type SectionBase<SectionItemT> = {
// Must be provided directly on each section.
data: Array<SectionItem>,
data: Array<SectionItemT>,
key: string,
// Optional props will override list-wide props just for this section.
ItemComponent?: ?ReactClass<{item: SectionItem, index: number}>,
ItemComponent?: ?ReactClass<{item: SectionItemT, index: number}>,
SeparatorComponent?: ?ReactClass<*>,
keyExtractor?: (item: SectionItem) => string,
keyExtractor?: (item: SectionItemT) => string,
// TODO: support more optional/override props
// FooterComponent?: ?ReactClass<*>,
// HeaderComponent?: ?ReactClass<*>,
// onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
// TODO: support recursive sections
// SectionHeaderComponent?: ?ReactClass<{section: SectionBase}>,
// SectionHeaderComponent?: ?ReactClass<{section: SectionBase<*>}>,
// sections?: ?Array<Section>;
};
type RequiredProps<SectionT: SectionBase> = {
type RequiredProps<SectionT: SectionBase<*>> = {
sections: Array<SectionT>,
};
type OptionalProps<SectionT: SectionBase> = {
type OptionalProps<SectionT: SectionBase<*>> = {
/**
* Rendered after the last item in the last section.
*/
FooterComponent?: ?ReactClass<*>,
/**
* Default renderer for every item in every section.
*/
ItemComponent?: ?ReactClass<{item: Item, index: number}>,
ItemComponent: ReactClass<{item: Item, index: number}>,
/**
* Rendered at the top of each section. In the future, a sticky option will be added.
*/
@@ -93,8 +93,8 @@ type OptionalProps<SectionT: SectionBase> = {
* stored outside of the recursive `ItemComponent` instance tree.
*/
enableVirtualization?: ?boolean,
keyExtractor?: (item: Item) => string,
onEndReached?: ({distanceFromEnd: number}) => void,
keyExtractor: (item: Item, index: number) => string,
onEndReached?: ?({distanceFromEnd: number}) => void,
/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
* sure to also set the `refreshing` prop correctly.
@@ -104,21 +104,25 @@ type OptionalProps<SectionT: SectionBase> = {
* Called when the viewability of rows changes, as defined by the
* `viewablePercentThreshold` prop.
*/
onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
/**
* Set this true while waiting for new data from a refresh.
*/
refreshing?: boolean,
refreshing?: ?boolean,
/**
* This is an optional optimization to minimize re-rendering items.
*/
shouldItemUpdate?: (
shouldItemUpdate: (
prevProps: {item: Item, index: number},
nextProps: {item: Item, index: number}
) => boolean,
};
type Props<SectionT> = RequiredProps<SectionT> & OptionalProps<SectionT>;
type Props<SectionT> = RequiredProps<SectionT>
& OptionalProps<SectionT>
& VirtualizedSectionListProps<SectionT>;
type DefaultProps = typeof VirtualizedSectionList.defaultProps;
/**
* A performant interface for rendering sectioned lists, supporting the most handy features:
@@ -132,8 +136,11 @@ type Props<SectionT> = RequiredProps<SectionT> & OptionalProps<SectionT>;
*
* If you don't need section support and want a simpler interface, use FlatList.
*/
class SectionList<SectionT: SectionBase> extends React.Component<void, Props<SectionT>, void> {
class SectionList<SectionT: SectionBase<*>>
extends React.PureComponent<DefaultProps, Props<SectionT>, *>
{
props: Props<SectionT>;
static defaultProps: DefaultProps = VirtualizedSectionList.defaultProps;
render() {
if (this.props.legacyImplementation) {
@@ -68,7 +68,7 @@ type RequiredProps = {
* The default accessor functions assume this is an Array<{key: string}> but you can override
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
*/
data: any,
data?: any,
};
type OptionalProps = {
FooterComponent?: ?ReactClass<*>,
@@ -89,12 +89,12 @@ type OptionalProps = {
getItemCount: (items: any) => number,
getItemLayout?: (items: any, index: number) =>
{length: number, offset: number, index: number}, // e.g. height, y
horizontal: boolean,
horizontal?: ?boolean,
initialNumToRender: number,
keyExtractor: (item: Item, index: number) => string,
maxToRenderPerBatch: number,
onEndReached: ({distanceFromEnd: number}) => void,
onEndReachedThreshold: number, // units of visible length
onEndReached?: ?({distanceFromEnd: number}) => void,
onEndReachedThreshold?: ?number, // units of visible length
onLayout?: ?Function,
/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
@@ -105,11 +105,11 @@ type OptionalProps = {
* Called when the viewability of rows changes, as defined by the
* `viewablePercentThreshold` prop.
*/
onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
/**
* Set this true while waiting for new data from a refresh.
*/
refreshing?: boolean,
refreshing?: ?boolean,
removeClippedSubviews?: boolean,
renderScrollComponent: (props: Object) => React.Element<*>,
shouldItemUpdate: (
@@ -126,11 +126,11 @@ type OptionalProps = {
viewablePercentThreshold: number,
windowSize: number, // units of visible length
};
type Props = RequiredProps & OptionalProps;
export type Props = RequiredProps & OptionalProps;
let _usedIndexForKey = false;
class VirtualizedList extends React.PureComponent {
class VirtualizedList extends React.PureComponent<OptionalProps, Props, *> {
props: Props;
// scrollToEnd may be janky without getItemLayout prop
@@ -182,7 +182,7 @@ class VirtualizedList extends React.PureComponent {
);
}
static defaultProps: OptionalProps = {
static defaultProps = {
disableVirtualization: false,
getItem: (data: any, index: number) => data[index],
getItemCount: (data: any) => data ? data.length : 0,
@@ -390,7 +390,12 @@ class VirtualizedList extends React.PureComponent {
_onCellLayout = (e, cellKey, index) => {
const layout = e.nativeEvent.layout;
const next = {offset: this._selectOffset(layout), length: this._selectLength(layout), index, inLayout: true};
const next = {
offset: this._selectOffset(layout),
length: this._selectLength(layout),
index,
inLayout: true,
};
const curr = this._frames[cellKey];
if (!curr ||
next.offset !== curr.offset ||
Oops, something went wrong.

1 comment on commit 63d3ea1

@hramos

This comment has been minimized.

Show comment
Hide comment
@hramos

hramos Feb 22, 2017

Contributor

This commit is breaking open source tests in Circle and will be reverted in #12526.

Contributor

hramos commented on 63d3ea1 Feb 22, 2017

This commit is breaking open source tests in Circle and will be reverted in #12526.

Please sign in to comment.