From acc1edd188c16769a6709a8e6feb428203d6235f Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 27 Mar 2017 19:51:40 -0700 Subject: [PATCH] expand example with `PureComponent` usage, explicit `extraData` prop, `initialNumToRender` Summary: Should help with some common pitfalls, e.g. https://github.com/facebook/react-native/issues/12512#issuecomment-289521758. Reviewed By: yungsters Differential Revision: D4781833 fbshipit-source-id: 3dec2f0c444645ad710e9ed81390636da4581f0f --- Libraries/Lists/FlatList.js | 100 ++++++++++++++++++++++++++--- Libraries/Lists/SectionList.js | 25 ++++++-- Libraries/Lists/VirtualizedList.js | 9 ++- 3 files changed, 121 insertions(+), 13 deletions(-) diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index 70c21670c..15cbcc437 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -57,6 +57,16 @@ type OptionalProps = { * Rendered at the top of all the items. */ ListHeaderComponent?: ?ReactClass, + /** + * Optional custom style for multi-item rows generated when numColumns > 1. + */ + columnWrapperStyle?: StyleObj, + /** + * A marker property for telling the list to re-render (since it implements `PureComponent`). If + * your `renderItem` function depends on anything outside of the `data` prop, stick it here and + * treat it immutably. + */ + extraData?: any, /** * `getItemLayout` is an optional optimizations that let us skip measurement of dynamic content if * you know the height of items a priori. `getItemLayout` is the most efficient, and is easy to @@ -75,6 +85,12 @@ type OptionalProps = { * If true, renders items next to each other horizontally instead of stacked vertically. */ horizontal?: ?boolean, + /** + * How many items to render in the initial batch. This should be enough to fill the screen but not + * much more. Note these items will never be unmounted as part of the windowed rendering in order + * to improve perceived performance of scroll-to-top actions. + */ + initialNumToRender: number, /** * Used to extract a unique key for a given item at the specified index. Key is used for caching * and as the react key to track item re-ordering. The default extractor checks `item.key`, then @@ -91,6 +107,12 @@ type OptionalProps = { * content. */ onEndReached?: ?(info: {distanceFromEnd: number}) => void, + /** + * How far from the end (in units of visible length of the list) the bottom edge of the + * list must be from the end of the content to trigger the `onEndReached` callback. + * Thus a value of 0.5 will trigger `onEndReached` when the end of the content is + * within half the visible length of the list. + */ onEndReachedThreshold?: ?number, /** * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make @@ -98,19 +120,17 @@ type OptionalProps = { */ onRefresh?: ?() => void, /** - * Called when the viewability of rows changes, as defined by the - * `viewablePercentThreshold` prop. + * Called when the viewability of rows changes, as defined by the `viewabilityConfig` prop. */ - onViewableItemsChanged?: ?(info: {viewableItems: Array, changed: Array}) => void, + onViewableItemsChanged?: ?(info: { + viewableItems: Array, + changed: Array, + }) => void, legacyImplementation?: ?boolean, /** * Set this true while waiting for new data from a refresh. */ refreshing?: ?boolean, - /** - * Optional custom style for multi-item rows generated when numColumns > 1 - */ - columnWrapperStyle?: StyleObj, /** * See `ViewabilityHelper` for flow type and further documentation. */ @@ -148,8 +168,71 @@ type DefaultProps = typeof defaultProps; * renderItem={({item}) => {item.key}} * /> * + * More complex example demonstrating `PureComponent` usage for perf optimization and avoiding bugs. + * + * - By binding the `onPressItem` handler, the props will remain `===` and `PureComponent` will + * prevent wasteful re-renders unless the actual `id`, `selected`, or `title` props change, even + * if the inner `SomeOtherWidget` has no such optimizations. + * - By passing `extraData={this.state}` to `FlatList` we make sure `FlatList` itself will re-render + * when the `state.selected` changes. Without setting this prop, `FlatList` would not know it + * needs to re-render any items because it is also a `PureComponent` and the prop comparison will + * not show any changes. + * - `keyExtractor` tells the list to use the `id`s for the react keys. + * + * + * class MyListItem extends React.PureComponent { + * _onPress = () => { + * this.props.onPressItem(this.props.id); + * }; + * + * render() { + * return ( + * + * ) + * } + * } + * + * class MyList extends React.PureComponent { + * state = {selected: (new Map(): Map)}; + * + * _keyExtractor = (item, index) => item.id; + * + * _onPressItem = (id: string) => { + * // updater functions are preferred for transactional updates + * this.setState((state) => { + * // copy the map rather than modifying state. + * const selected = new Map(state.selected); + * selected.set(id, !state.get(id)); // toggle + * return {selected}; + * }); + * }; + * + * _renderItem = ({item}) => ( + * + * ); + * + * render() { + * return ( + * + * ); + * } + * } + * * This is a convenience wrapper around [``](docs/virtualizedlist.html), - * and thus inherits the following caveats: + * and thus inherits it's props that aren't explicitly listed here along with the following caveats: * * - Internal state is not preserved when content scrolls out of the render window. Make sure all * your data is captured in the item data or external stores like Flux, Redux, or Relay. @@ -305,6 +388,7 @@ class FlatList extends React.PureComponent, vo arr.push({...v, item, key: keyExtractor(item, index), index}); }); } + _onViewableItemsChanged = (info) => { const {numColumns, onViewableItemsChanged} = this.props; if (!onViewableItemsChanged) { diff --git a/Libraries/Lists/SectionList.js b/Libraries/Lists/SectionList.js index 86d0f65a5..cb875b3dd 100644 --- a/Libraries/Lists/SectionList.js +++ b/Libraries/Lists/SectionList.js @@ -56,21 +56,34 @@ type OptionalProps> = { * Rendered at the very end of the list. */ ListFooterComponent?: ?ReactClass, - /** - * Rendered at the top of each section. Sticky headers are not yet supported. - */ - renderSectionHeader?: ?(info: {section: SectionT}) => ?React.Element, /** * Rendered in between each section. */ SectionSeparatorComponent?: ?ReactClass, + /** + * How many items to render in the initial batch. This should be enough to fill the screen but not + * much more. Note these items will never be unmounted as part of the windowed rendering in order + * to improve perceived performance of scroll-to-top actions. + */ + initialNumToRender: number, /** * Used to extract a unique key for a given item at the specified index. Key is used for caching * 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, + /** + * Called once when the scroll position gets within `onEndReachedThreshold` of the rendered + * content. + */ onEndReached?: ?(info: {distanceFromEnd: number}) => void, + /** + * How far from the end (in units of visible length of the list) the bottom edge of the + * list must be from the end of the content to trigger the `onEndReached` callback. + * Thus a value of 0.5 will trigger `onEndReached` when the end of the content is + * within half the visible length of the list. + */ + onEndReachedThreshold?: ?number, /** * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make * sure to also set the `refreshing` prop correctly. @@ -88,6 +101,10 @@ type OptionalProps> = { * Set this true while waiting for new data from a refresh. */ refreshing?: ?boolean, + /** + * Rendered at the top of each section. Sticky headers are not yet supported. + */ + renderSectionHeader?: ?(info: {section: SectionT}) => ?React.Element, /** * Makes section headers stick to the top of the screen until the next one pushes it off. Only * enabled by default on iOS because that is the platform standard there. diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index fc64a70c8..509971609 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -49,6 +49,12 @@ type OptionalProps = { * this for debugging purposes. */ disableVirtualization: boolean, + /** + * A marker property for telling the list to re-render (since it implements `PureComponent`). If + * your `renderItem` function depends on anything outside of the `data` prop, stick it here and + * treat it immutably. + */ + extraData?: any, /** * A generic accessor for extracting an item from any sort of data blob. */ @@ -62,7 +68,8 @@ type OptionalProps = { horizontal?: ?boolean, /** * How many items to render in the initial batch. This should be enough to fill the screen but not - * much more. + * much more. Note these items will never be unmounted as part of the windowed rendering in order + * to improve perceived performance of scroll-to-top actions. */ initialNumToRender: number, keyExtractor: (item: Item, index: number) => string,