我看到getMenuProps
的自述文件,但它没有解释refs
的用途。当我移动它时,它似乎有效果。我不确定它是否必须位于列表元素直接位于子项元素之上?或者它可以位于父树中的任何位置?
我用这个方法实现了一个带有downshift + react-virtualized的自动完成组件:
import React, { useCallback, useMemo, useRef } from 'react'
import Downshift, { StateChangeOptions } from 'downshift'
import Input from './Input'
import { matchSorter } from 'match-sorter'
import { CloseIcon, IconButton, useSize } from './base'
import TriangleDownIcon from './icon/TriangleDown'
import List, {
ListRowProps,
} from 'react-virtualized/dist/commonjs/List'
export type ItemViewCast = {
isActive?: boolean
isSelected?: boolean
item: ItemCast
list: Array<ItemCast>
}
export type ItemCast = {
height?: number
value: string
search: string
}
export default function Autocomplete({
value,
onChange,
placeholder,
items,
itemRenderer,
listHeight,
overscanRowCount = 10,
useDynamicItemHeight,
itemHeight = 128,
showScrollingPlaceholder,
clearable,
id,
renderItemToString,
}: {
renderItemToString: (item: ItemCast | null) => string
id?: string
value?: string
placeholder?: string
onChange: (val?: string) => void
items: Array<ItemCast>
itemRenderer: React.ComponentType<ItemViewCast>
listHeight?: number | string
overscanRowCount?: number
useDynamicItemHeight?: boolean
itemHeight?: number
showScrollingPlaceholder?: boolean
clearable?: boolean
}) {
const map = useMemo(() => {
return items.reduce<Record<string, number>>((m, x, i) => {
if (x.value) {
m[x.value] = i
}
return m
}, {})
}, [items])
const handleStateChange = (changes: StateChangeOptions<ItemCast>) => {
if (changes.hasOwnProperty('selectedItem')) {
onChange(changes.selectedItem?.value ?? undefined)
} else if (changes.hasOwnProperty('inputValue')) {
// onChange(changes.inputValue ?? undefined)
}
}
const clearSelection = () => {
if (clearable) {
onChange(undefined)
}
}
const getItemHeight = useCallback(
({ index }: { index: number }) => {
return items[index]?.height ?? itemHeight
},
[items, itemHeight],
)
// const handleScrollToRowChange = (event) => {
// const {rowCount} = this.state;
// let scrollToIndex = Math.min(
// rowCount - 1,
// parseInt(event.target.value, 10),
// );
// if (isNaN(scrollToIndex)) {
// scrollToIndex = undefined;
// }
// // this.setState({scrollToIndex});
// }
const selectedIndex = value ? map[value] : undefined
const selectedItem =
selectedIndex != null ? items[selectedIndex] : undefined
return (
<Downshift
id={id ? `autocomplete-${id}` : undefined}
inputValue={value}
onStateChange={handleStateChange}
itemToString={renderItemToString}
>
{({
getLabelProps,
getInputProps,
getToggleButtonProps,
getMenuProps,
getItemProps,
getRootProps,
isOpen,
selectedItem,
inputValue,
highlightedIndex,
}) => {
return (
<div className="w-full relative">
<div className="w-full relative">
<Input
{...getInputProps({
isOpen,
// inputValue,
placeholder,
})}
/>
<div className="absolute right-0 top-0 h-full">
<div className="flex items-center h-full">
{selectedItem && clearable ? (
<IconButton
onClick={clearSelection}
aria-label="clear selection"
className="w-32 h-32"
>
<CloseIcon />
</IconButton>
) : (
<IconButton
className="w-32 h-32"
{...getToggleButtonProps()}
>
<TriangleDownIcon />
</IconButton>
)}
</div>
</div>
</div>
{isOpen && (
<Menu
id={id}
itemRenderer={itemRenderer}
overscanRowCount={overscanRowCount}
listHeight={listHeight}
getMenuProps={getMenuProps}
items={items}
inputValue={inputValue ?? undefined}
value={value}
highlightedIndex={highlightedIndex ?? undefined}
useDynamicItemHeight={useDynamicItemHeight}
getItemHeight={getItemHeight}
itemHeight={itemHeight}
showScrollingPlaceholder={showScrollingPlaceholder}
getItemProps={getItemProps}
/>
)}
</div>
)
}}
</Downshift>
)
}
function Menu({
getMenuProps,
listHeight,
overscanRowCount,
items,
inputValue,
value,
highlightedIndex,
useDynamicItemHeight,
getItemHeight,
itemHeight,
showScrollingPlaceholder,
itemRenderer,
getItemProps,
id,
}: {
id?: string
items: Array<ItemCast>
listHeight?: number | string
value?: string
overscanRowCount: number
highlightedIndex?: number
useDynamicItemHeight?: boolean
itemHeight: number
getItemHeight: ({ index }: { index: number }) => number
inputValue?: string
getMenuProps: () => Record<string, any>
getItemProps: (opts: any) => Record<string, any>
showScrollingPlaceholder?: boolean
itemRenderer: React.ComponentType<ItemViewCast>
}) {
const listRef = useRef(null)
const filteredItems = useMemo(() => {
if (!inputValue) {
return items
}
return matchSorter(items, inputValue, { keys: ['search'] })
}, [items, inputValue])
const filteredMap = useMemo(() => {
return filteredItems.reduce<Record<string, number>>((m, x, i) => {
if (x.value) {
m[x.value] = i
}
return m
}, {})
}, [filteredItems])
const rowCount = filteredItems.length
const selectedIndex = value ? filteredMap[value] : undefined
const Item = itemRenderer
// const selectedItem =
// selectedIndex != null ? filteredItems[selectedIndex] : undefined
const rowRenderer = ({
index,
isScrolling,
key,
style,
getItemProps,
highlightedIndex,
}: ListRowProps & {
getItemProps: (opts: any) => Record<string, any>
highlightedIndex?: number
}) => {
if (showScrollingPlaceholder && isScrolling) {
return (
<div
// className={cx(styles.row, styles.isScrollingPlaceholder)}
key={key}
style={style}
>
Scrolling...
</div>
)
}
const item = filteredItems[index]
if (useDynamicItemHeight) {
// switch (item.size) {
// case 75:
// additionalContent = <div>It is medium-sized.</div>
// break
// case 100:
// additionalContent = (
// <div>
// It is large-sized.
// <br />
// It has a 3rd row.
// </div>
// )
// break
// }
}
return (
<Item
key={item.value}
item={item}
list={filteredItems}
{...getItemProps({
item,
index,
isActive: highlightedIndex === index,
isSelected: selectedIndex === index,
})}
/>
)
}
const ref = useRef(null)
const size = useSize(ref)
return (
<div
className="w-full relative"
style={{ height: listHeight }}
ref={ref}
>
<div className="w-full absolute h-full bg-zinc-50 z-3000">
<List
id={id ? `autocomplete-list-${id}` : undefined}
className="relative w-full"
ref={listRef}
height={size?.height ?? 0}
overscanRowCount={overscanRowCount}
// noRowsRenderer={this._noRowsRenderer}
rowCount={rowCount}
rowHeight={useDynamicItemHeight ? getItemHeight : itemHeight}
containerProps={getMenuProps()}
rowRenderer={(props: ListRowProps) => {
return rowRenderer({
...props,
getItemProps,
highlightedIndex: highlightedIndex ?? undefined,
})
}}
// scrollToIndex={scrollToIndex}
width={size?.width ?? 128}
/>
{/* // </AutoSizer> */}
</div>
</div>
)
}
字符串
我认为问题是List
内部的react-virtualized
具有这样的DOM结构:
<div class="grid">
<div class="scroller">
<div class="item">item from `Item` component 1</div>
<div class="item">item from `Item` component 2</div>
</div>
</div>
型
(don不记得确切的类是什么,但这是要点)。
你可以设置<List containerProps={getMenuItems()}>
,但这是在上面的<div class="grid">
元素上设置的,而不是在滚动条上。没有办法指定滚动条属性。有什么方法可以让它工作吗?
到目前为止,我已经在上面的自动完成代码中的这三个地方进行了尝试,每个地方都会导致不同的行为,没有一个是正确的:
<div
className="w-full relative"
style={{ height: listHeight }}
ref={ref}
{...getMenuProps()}
>
<div {...getMenuProps()} className="w-full absolute h-full bg-zinc-50 z-3000">
<List containerProps={getMenuProps()}>
型
到目前为止,完全关闭getMenuProps
似乎是最好的,但不是100%确定。所以我想知道是否有必要使用它。或者我如何才能让它与react-virtualized一起工作?
完全关闭getMenuProps
实际上,它似乎不工作,滚动是混乱的,不知道这是哪个库。
x1c 0d1x的数据
使用<List containerProps={getMenuProps()}>
它显示额外的空间在年底滚动它看起来像,这也是错误的。
的
在absolute
节点上使用<div {...getMenuProps()}>
滚动是所有颠簸和事情重叠。
的
1条答案
按热度按时间bnl4lu3b1#
我现在基本上都在工作了:
我主要做了两件事:
List
添加一个简单的scrollerProps
属性,这样它就可以直接在子节点上方指定getMenuProps()
。不确定这是否完全必要。1.在
Downshift
元素上实现了stateReducer
,似乎默认的stateReducer
不是你想要的,所以我让它做我想要的。代码如下:
字符串