mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: This diff is part of an overall stack, meant to fix incorrect usage of `setState()` in `VirtualizedList`, which triggers new invariant checks added in `VirtualizedList_EXPERIMENTAL`. See the stack summary below for more information on the broader change. ## Diff Summary This moves public VirtualizedList types to a new file, shared between both `VirtualizedList`, and `VirtualizedList_EXPERIMENTAL`. This allows us to make public interface changes in a single place. ## Stack Summary `VirtualizedList`'s component state is a set of cells to render. This state is set via the `setState()` class component API. The main "tick" function `VirtualizedList._updateCellsToRender()` calculates this new state using a combination of the current component state, and instance-local state like maps, measurement caches, etc. From: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous --- > React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. For example, this code may fail to update the counter: ``` // Wrong this.setState({ counter: this.state.counter + this.props.increment, }); ``` > To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument: ``` // Correct this.setState((state, props) => ({ counter: state.counter + props.increment })); ``` --- `_updateCellsToRender()` transitively calls many functions which will read directly from `this.props` or `this.state` instead of the value passed by the state updater. This intermittently fires invariant violations, when there is a mismatch. This diff migrates all usages of `props` and `state` during state update to the values provied in `setState()`. To prevent future mismatch, and to provide better clarity on when it is safe to use `this.props`, `this.state`, I overrode `setState` to fire an invariant violation if it is accessed when it is unsafe to: {F756963772} Changelog: [Internal][Changed] - Move Props to VirtualizedListProps Reviewed By: genkikondo Differential Revision: D38293585 fbshipit-source-id: 344d94466a2741ee67eb5754de5506eb3e265c5b
274 lines
9.9 KiB
JavaScript
274 lines
9.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
* @format
|
|
*/
|
|
|
|
import * as React from 'react';
|
|
import type {
|
|
ViewabilityConfig,
|
|
ViewabilityConfigCallbackPair,
|
|
ViewToken,
|
|
} from './ViewabilityHelper';
|
|
|
|
import type {ViewStyleProp} from '../StyleSheet/StyleSheet';
|
|
import typeof ScrollView from '../Components/ScrollView/ScrollView';
|
|
|
|
export type Item = any;
|
|
|
|
export type Separators = {
|
|
highlight: () => void,
|
|
unhighlight: () => void,
|
|
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
|
...
|
|
};
|
|
|
|
export type RenderItemProps<ItemT> = {
|
|
item: ItemT,
|
|
index: number,
|
|
separators: Separators,
|
|
...
|
|
};
|
|
|
|
export type RenderItemType<ItemT> = (
|
|
info: RenderItemProps<ItemT>,
|
|
) => React.Node;
|
|
|
|
type RequiredProps = {|
|
|
/**
|
|
* The default accessor functions assume this is an Array<{key: string} | {id: string}> but you can override
|
|
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
|
|
*/
|
|
data?: any,
|
|
/**
|
|
* A generic accessor for extracting an item from any sort of data blob.
|
|
*/
|
|
getItem: (data: any, index: number) => ?Item,
|
|
/**
|
|
* Determines how many items are in the data blob.
|
|
*/
|
|
getItemCount: (data: any) => number,
|
|
|};
|
|
type OptionalProps = {|
|
|
renderItem?: ?RenderItemType<Item>,
|
|
/**
|
|
* `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
|
|
* implementation, but with a significant perf hit.
|
|
*/
|
|
debug?: ?boolean,
|
|
/**
|
|
* DEPRECATED: Virtualization provides significant performance and memory optimizations, but fully
|
|
* unmounts react instances that are outside of the render window. You should only need to disable
|
|
* this for debugging purposes. Defaults to false.
|
|
*/
|
|
disableVirtualization?: ?boolean,
|
|
/**
|
|
* A marker property for telling the list to re-render (since it implements `PureComponent`). If
|
|
* any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
|
|
* `data` prop, stick it here and treat it immutably.
|
|
*/
|
|
extraData?: any,
|
|
// e.g. height, y
|
|
getItemLayout?: (
|
|
data: any,
|
|
index: number,
|
|
) => {
|
|
length: number,
|
|
offset: number,
|
|
index: number,
|
|
...
|
|
},
|
|
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,
|
|
/**
|
|
* Instead of starting at the top with the first item, start at `initialScrollIndex`. This
|
|
* disables the "scroll to top" optimization that keeps the first `initialNumToRender` items
|
|
* always rendered and immediately renders the items starting at this initial index. Requires
|
|
* `getItemLayout` to be implemented.
|
|
*/
|
|
initialScrollIndex?: ?number,
|
|
/**
|
|
* Reverses the direction of scroll. Uses scale transforms of -1.
|
|
*/
|
|
inverted?: ?boolean,
|
|
keyExtractor?: ?(item: Item, index: number) => string,
|
|
/**
|
|
* Each cell is rendered using this element. Can be a React Component Class,
|
|
* or a render function. Defaults to using View.
|
|
*/
|
|
CellRendererComponent?: ?React.ComponentType<any>,
|
|
/**
|
|
* Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
|
|
* `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
|
|
* which will update the `highlighted` prop, but you can also add custom props with
|
|
* `separators.updateProps`.
|
|
*/
|
|
ItemSeparatorComponent?: ?React.ComponentType<any>,
|
|
/**
|
|
* Takes an item from `data` and renders it into the list. Example usage:
|
|
*
|
|
* <FlatList
|
|
* ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => (
|
|
* <View style={[style.separator, highlighted && {marginLeft: 0}]} />
|
|
* )}
|
|
* data={[{title: 'Title Text', key: 'item1'}]}
|
|
* ListItemComponent={({item, separators}) => (
|
|
* <TouchableHighlight
|
|
* onPress={() => this._onPress(item)}
|
|
* onShowUnderlay={separators.highlight}
|
|
* onHideUnderlay={separators.unhighlight}>
|
|
* <View style={{backgroundColor: 'white'}}>
|
|
* <Text>{item.title}</Text>
|
|
* </View>
|
|
* </TouchableHighlight>
|
|
* )}
|
|
* />
|
|
*
|
|
* Provides additional metadata like `index` if you need it, as well as a more generic
|
|
* `separators.updateProps` function which let's you set whatever props you want to change the
|
|
* rendering of either the leading separator or trailing separator in case the more common
|
|
* `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
|
|
* your use-case.
|
|
*/
|
|
ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
|
/**
|
|
* Rendered when the list is empty. Can be a React Component Class, a render function, or
|
|
* a rendered element.
|
|
*/
|
|
ListEmptyComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
|
/**
|
|
* Rendered at the bottom of all the items. Can be a React Component Class, a render function, or
|
|
* a rendered element.
|
|
*/
|
|
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
|
/**
|
|
* Styling for internal View for ListFooterComponent
|
|
*/
|
|
ListFooterComponentStyle?: ViewStyleProp,
|
|
/**
|
|
* Rendered at the top of all the items. Can be a React Component Class, a render function, or
|
|
* a rendered element.
|
|
*/
|
|
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
|
/**
|
|
* Styling for internal View for ListHeaderComponent
|
|
*/
|
|
ListHeaderComponentStyle?: ViewStyleProp,
|
|
/**
|
|
* A unique identifier for this list. If there are multiple VirtualizedLists at the same level of
|
|
* nesting within another VirtualizedList, this key is necessary for virtualization to
|
|
* work properly.
|
|
*/
|
|
listKey?: string,
|
|
/**
|
|
* The maximum number of items to render in each incremental render batch. The more rendered at
|
|
* once, the better the fill rate, but responsiveness may suffer because rendering content may
|
|
* interfere with responding to button taps or other interactions.
|
|
*/
|
|
maxToRenderPerBatch?: ?number,
|
|
/**
|
|
* 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. A value of 0 will not trigger until scrolling
|
|
* to the very end 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.
|
|
*/
|
|
onRefresh?: ?() => void,
|
|
/**
|
|
* Used to handle failures when scrolling to an index that has not been measured yet. Recommended
|
|
* action is to either compute your own offset and `scrollTo` it, or scroll as far as possible and
|
|
* then try again after more items have been rendered.
|
|
*/
|
|
onScrollToIndexFailed?: ?(info: {
|
|
index: number,
|
|
highestMeasuredFrameIndex: number,
|
|
averageItemLength: number,
|
|
...
|
|
}) => void,
|
|
/**
|
|
* Called when the viewability of rows changes, as defined by the
|
|
* `viewabilityConfig` prop.
|
|
*/
|
|
onViewableItemsChanged?: ?(info: {
|
|
viewableItems: Array<ViewToken>,
|
|
changed: Array<ViewToken>,
|
|
...
|
|
}) => void,
|
|
persistentScrollbar?: ?boolean,
|
|
/**
|
|
* Set this when offset is needed for the loading indicator to show correctly.
|
|
*/
|
|
progressViewOffset?: number,
|
|
/**
|
|
* A custom refresh control element. When set, it overrides the default
|
|
* <RefreshControl> component built internally. The onRefresh and refreshing
|
|
* props are also ignored. Only works for vertical VirtualizedList.
|
|
*/
|
|
refreshControl?: ?React.Element<any>,
|
|
/**
|
|
* Set this true while waiting for new data from a refresh.
|
|
*/
|
|
refreshing?: ?boolean,
|
|
/**
|
|
* Note: may have bugs (missing content) in some circumstances - use at your own risk.
|
|
*
|
|
* This may improve scroll performance for large lists.
|
|
*/
|
|
removeClippedSubviews?: boolean,
|
|
/**
|
|
* Render a custom scroll component, e.g. with a differently styled `RefreshControl`.
|
|
*/
|
|
renderScrollComponent?: (props: Object) => React.Element<any>,
|
|
/**
|
|
* Amount of time between low-pri item render batches, e.g. for rendering items quite a ways off
|
|
* screen. Similar fill rate/responsiveness tradeoff as `maxToRenderPerBatch`.
|
|
*/
|
|
updateCellsBatchingPeriod?: ?number,
|
|
/**
|
|
* See `ViewabilityHelper` for flow type and further documentation.
|
|
*/
|
|
viewabilityConfig?: ViewabilityConfig,
|
|
/**
|
|
* List of ViewabilityConfig/onViewableItemsChanged pairs. A specific onViewableItemsChanged
|
|
* will be called when its corresponding ViewabilityConfig's conditions are met.
|
|
*/
|
|
viewabilityConfigCallbackPairs?: Array<ViewabilityConfigCallbackPair>,
|
|
/**
|
|
* Determines the maximum number of items rendered outside of the visible area, in units of
|
|
* visible lengths. So if your list fills the screen, then `windowSize={21}` (the default) will
|
|
* render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing
|
|
* this number will reduce memory consumption and may improve performance, but will increase the
|
|
* chance that fast scrolling may reveal momentary blank areas of unrendered content.
|
|
*/
|
|
windowSize?: ?number,
|
|
/**
|
|
* The legacy implementation is no longer supported.
|
|
*/
|
|
legacyImplementation?: empty,
|
|
|};
|
|
|
|
export type Props = {|
|
|
...React.ElementConfig<ScrollView>,
|
|
...RequiredProps,
|
|
...OptionalProps,
|
|
|};
|