/** * 没有给图片尺寸的时候,默认从左到右依次排列 * TypeScript * https://gist.github.com/Venryx/7cff24b17867da305fff12c6f8ef6f96 * * - 1. 泛型的支持 * - 2. `tsconfig.json` 支持 [参考 ReadME.md] */ import React, { ForwardRefExoticComponent, JSXElementConstructor, ReactElement, RefAttributes, useRef, } from 'react'; import { NativeScrollEvent, NativeSyntheticEvent, RefreshControl, StyleProp, StyleSheet, View, ViewStyle, VirtualizedList, } from 'react-native'; import * as Animatable from 'react-native-animatable'; import {CommonAnimationActions} from '../../types'; type ScrollToOffset = { y: number; animated?: boolean; }; interface WaterfallProps { /** * FlastList 规范的 PropTypes,命名都一模一样,功能也一模一样。 * 请参考: https://www.react-native.cn/docs/flatlist */ data: ItemT[]; /** 是都允许下拉刷新 */ bounces?: boolean; numColumns?: number | undefined; contentContainerStyle?: StyleProp; onEndReached?: (info: {distanceFromEnd: number}) => void; onEndReachedThreshold?: number | null | undefined; onRefresh?: () => void; onScroll?: | ((event: NativeSyntheticEvent) => void) | undefined; refreshing?: boolean; removeClippedSubviews?: boolean | undefined; scrollEventThrottle?: number | undefined; showsVerticalScrollIndicator?: boolean | undefined; style?: StyleProp; ListEmptyComponent?: ReactElement>; ListHeaderComponent?: ReactElement>; ListFooterComponent?: ReactElement>; renderItem: ( item: ItemT, column: number, i: number, ) => ReactElement>; /** 动画相关 */ animation?: CommonAnimationActions; /** 自定义属性 */ pageSize?: number; } interface WaterfallRefProps { /** 滚动到指定位置 */ scrollToOffset: (params: ScrollToOffset) => void; } /** * 泛型的支持 * 参考: https://stackoverflow.com/questions/59947787/generictype-in-react-fcpropst * @param props * @returns */ export const Waterfall = React.forwardRef( ( props: WaterfallProps, ref: React.Ref, ) => { let waterfall = useRef(); const { data = [], bounces = true, numColumns = 2, showsVerticalScrollIndicator = false, removeClippedSubviews = true, onEndReachedThreshold = 0.2, scrollEventThrottle = 100, /** 自定义属性 */ pageSize = 10, animation = { type: 'fadeInDown', duration: 1000, delay: 200, }, refreshing = false, style, contentContainerStyle, onRefresh, onScroll, onEndReached, renderItem, ListHeaderComponent = null, ListFooterComponent = null, ListEmptyComponent = null, } = props; const defaultProps = { bounces, showsVerticalScrollIndicator, removeClippedSubviews, onEndReachedThreshold, scrollEventThrottle, ListHeaderComponent, ListFooterComponent, ListEmptyComponent, }; React.useImperativeHandle(ref, () => ({ scrollToOffset: (params: ScrollToOffset) => { let {animated = false, y = 0} = params; // @ts-ignore waterfall.current.scrollToOffset({animated, offset: y}); }, })); return ( (waterfall.current = ref)} getItemCount={data => 1} getItem={(data, index) => data[index]} style={[{flex: 1}, style]} onScroll={onScroll} keyExtractor={(item, index) => `react-native-miui`} onEndReached={info => { /** * 🐞 有可能刚进来的时候,`props.data` 还没进来,但是他认为已经到达底部了。 * console.log(`info.distanceFromEnd: ${info.distanceFromEnd}`); * info.distanceFromEnd > 16 && props?.onEndReached && props.onEndReached(info) */ data.length > 0 && onEndReached?.(info); }} refreshControl={ } renderItem={info => ( {Array.from({length: numColumns}, (_, i) => ( {data.map((__, _i) => { if (_i % numColumns == i) { return ( // @ts-ignore Datas[${_i}]`}> {renderItem(__, i, _i)} ); } else { return null; } })} ))} )} /> ); }, ); const styles = StyleSheet.create({}); type Handle = T1 extends ForwardRefExoticComponent> ? T2 : never; export type Waterfall = Handle;