-
Notifications
You must be signed in to change notification settings - Fork 60
feat: Add virtual list component #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
alice52hz
wants to merge
2
commits into
ksc-fe:master
Choose a base branch
from
alice52hz:virtualList
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| --- | ||
| title: 虚拟列表 | ||
| order: 12 | ||
| --- | ||
|
|
||
| 给`Select`添加`virtualList`属性可以在`Option`过多时保证页面的性能,`virtualList`的值为true时开启,为false时关闭,也可以设置为`"auto"`,在`Option`数量超过200时则自动开启虚拟列表功能。 | ||
|
|
||
| ```vdt | ||
| import {Select, Option} from 'kpc'; | ||
|
|
||
| <div style="margin-bottom: 16px;"> | ||
| <Select v-model="day" virtualList> | ||
| <Option value={$value.id} v-for={this.get('list')}>{$value.title}</Option> | ||
| </Select> | ||
| You selected: {this.get('day')} | ||
| </div> | ||
| ``` | ||
|
|
||
| ```ts | ||
| interface Props { | ||
| day?: string | null | ||
| } | ||
|
|
||
| const list = []; | ||
| for(let i = 0;i < 2000; i++) { | ||
| list.push({ | ||
| title: `item - ${i}`, | ||
| id: i | ||
| }) | ||
| } | ||
|
|
||
| export default class extends Component<Props> { | ||
| static template = template; | ||
| static defaults() { | ||
| return { | ||
| day: 100, | ||
| list: list | ||
| }; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| ```styl | ||
| .k-select-option | ||
| width: 280px; | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| import { | ||
| Component, | ||
| TypeDefs, | ||
| VNode, | ||
| Children, | ||
| createVNode as h, | ||
| className as getClassName, | ||
| normalizeChildren | ||
| } from 'intact'; | ||
| import {useSize} from './useSize'; | ||
| import {useRange} from './useRange'; | ||
| import {useScroll} from './useScroll'; | ||
|
|
||
| export interface VirtualProps { | ||
| keeps?: number | ||
| buffer?: number | ||
| estimateSize?: number | ||
| total: number | ||
| isFixedType?: boolean | ||
| nativeProps?: any | ||
| start: number | ||
| end: number | ||
| enableVirtualList?: boolean | string | ||
| } | ||
|
|
||
| export type RangeInfo = { | ||
| start: number | ||
| end: number | ||
| paddingTop: number | ||
| paddingBottom: number | ||
| } | ||
|
|
||
| const typeDefs: Required<TypeDefs<VirtualProps>> = { | ||
| keeps: Number, | ||
| buffer: Number, | ||
| estimateSize: Number, | ||
| total: Number, | ||
| isFixedType: Boolean, | ||
| nativeProps: Object, | ||
| start: Number, | ||
| end: Number, | ||
| enableVirtualList: [Boolean, String] | ||
| }; | ||
|
|
||
| const defaults = (): Partial<VirtualProps> => ({ | ||
| keeps: 30, | ||
| buffer: 10 | ||
| }); | ||
|
|
||
| export class Virtual extends Component<VirtualProps> { | ||
| static typeDefs = typeDefs; | ||
| static defaults = defaults; | ||
| static template = function(this: Virtual) { | ||
| const children: Children = this.get('children'); | ||
| const nativeProps = this.$vNode.props?.nativeProps; | ||
| const enableVirtualList = this.get('enableVirtualList'); | ||
|
|
||
| if(nativeProps.class) { | ||
| nativeProps.class = getClassName(nativeProps.class); | ||
| } | ||
|
|
||
| const auto = enableVirtualList === 'auto' && this.normalizedVnodes.length < 200; | ||
| if(!enableVirtualList || auto) { | ||
| return h('div', nativeProps, children); | ||
| } | ||
|
|
||
| const outerAttrs = { | ||
| ...nativeProps, | ||
| 'ev-scroll': (e: WheelEvent) => this.scroll.handleScroll((e.target as HTMLElement).scrollTop) | ||
| } | ||
| const {start, end} = this.get(); | ||
| const {paddingTop, paddingBottom} = this.range.getRange(); | ||
| const innerAttrs = {style: `padding-top: ${paddingTop}px; padding-bottom: ${paddingBottom}px`}; | ||
| const croppedChildren = this.normalizedVnodes.slice(start, end + 1); | ||
| const vnode = this.vnode = h('div', outerAttrs, h('div', innerAttrs, croppedChildren)); | ||
|
|
||
| return vnode; | ||
| }; | ||
|
|
||
| vnode: VNode | null = null; | ||
| offset: number = 0; | ||
|
|
||
| private normalizedVnodes: VNode[] = []; | ||
|
|
||
| rangeInfo: RangeInfo = { | ||
| start: 0, | ||
| end: 0, | ||
| paddingTop: 0, | ||
| paddingBottom: 0 | ||
| }; | ||
|
|
||
| private size = useSize(); | ||
| private range = useRange(this.size); | ||
| private scroll = useScroll(this.size, this.range); | ||
|
|
||
| init() { | ||
| if(!this.get('enableVirtualList')) return; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 有没有考虑这个属性,可能会动态改变的情况 |
||
|
|
||
| const keeps = this.get('keeps')!; | ||
| const children = this.get('children') as VNode; | ||
|
|
||
| this.set('start', 0); | ||
| this.set('end', keeps - 1); | ||
|
|
||
| if(Array.isArray(children) && children.length == 2) { | ||
| this.set('total', children[1].length); | ||
| } | ||
|
|
||
| this.watch('children', (newValue) => { | ||
| const fakeVNode = {} as VNode; | ||
| normalizeChildren(fakeVNode, newValue); | ||
| this.normalizedVnodes = fakeVNode.children as VNode[]; | ||
| }) | ||
| } | ||
|
|
||
| mounted() { | ||
| if(!this.get('enableVirtualList')) return; | ||
| this.size.setItemSize(); | ||
| } | ||
|
|
||
| initItems(): void { | ||
| this.range.checkRange(0, this.size.getEndByStart(0)); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import {useInstance, nextTick} from 'intact'; | ||
| import {Virtual} from './index'; | ||
| import {Size} from './useSize'; | ||
|
|
||
| type RangeInfo = { | ||
| start: number | ||
| end: number | ||
| paddingTop: number | ||
| paddingBottom: number | ||
| } | ||
|
|
||
| export type Range = { | ||
| checkRange: (start: number, end: number) => void | ||
| getRange: () => RangeInfo | ||
| } | ||
|
|
||
| export function useRange(size: Size): Range { | ||
| const instance = useInstance() as Virtual; | ||
|
|
||
| function getRange(): RangeInfo { | ||
| const range: RangeInfo = Object.create(null); | ||
| const tmpRange = instance.rangeInfo; | ||
| range.start = tmpRange.start; | ||
| range.end = tmpRange.end; | ||
| range.paddingTop = tmpRange.paddingTop; | ||
| range.paddingBottom = tmpRange.paddingBottom; | ||
|
|
||
| return range; | ||
| } | ||
|
|
||
| function checkRange(start: number, end: number): void { | ||
| const keeps = instance.get('keeps')!; | ||
| const total = instance.get('total'); | ||
|
|
||
| // Render all if total <= keeps | ||
| if(total <= keeps) { | ||
| start = 0; | ||
| end = size.getLastIndex(); | ||
| } else if(end - start < keeps - 1) { | ||
| start = end - keeps + 1; | ||
| } | ||
|
|
||
| updateRange(start, end); | ||
| } | ||
|
|
||
| function setRange(): void { | ||
| const {start, end} = getRange(); | ||
| instance.set('start', start); | ||
| instance.set('end', end); | ||
|
|
||
| nextTick(size.setItemSize.bind(instance)); | ||
| } | ||
|
|
||
| function updateRange(start: number, end: number): void { | ||
| const tmp = instance.rangeInfo; | ||
| tmp.start = start; | ||
| tmp.end = end; | ||
| tmp.paddingTop = size.getPadFront(); | ||
| tmp.paddingBottom = size.getPadBehind(); | ||
|
|
||
| setRange(); | ||
| } | ||
|
|
||
| return { | ||
| checkRange, | ||
| getRange | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import {useInstance} from 'intact'; | ||
| import {Virtual} from './index'; | ||
| import {Size} from './useSize'; | ||
| import {Range} from './useRange'; | ||
|
|
||
| type ScrollObj = { | ||
| handleScroll: (offset: number) => void | ||
| } | ||
|
|
||
| export function useScroll(size: Size, range: Range): ScrollObj { | ||
| const instance = useInstance() as Virtual; | ||
|
|
||
| function handleScroll(offset: number): void { | ||
| const isDown = offset > instance.offset; | ||
| instance.offset = offset; | ||
|
|
||
| if(isDown) { | ||
| handleBehind(); | ||
| } else { | ||
| handleFront(); | ||
| } | ||
| } | ||
|
|
||
| function handleBehind(): void { | ||
| const overs = size.getPassedItems(); | ||
|
|
||
| // Range should not change if scroll overs within buffer | ||
| if(overs < instance.rangeInfo.start + instance.get('buffer')!) { | ||
| return; | ||
| } | ||
|
|
||
| range.checkRange(overs, size.getEndByStart(overs)); | ||
| } | ||
|
|
||
| function handleFront(): void { | ||
| const overs = size.getPassedItems(); | ||
|
|
||
| if(overs > instance.rangeInfo.start) { | ||
| return; | ||
| } | ||
|
|
||
| const start = Math.max(overs - instance.get('buffer')!, 0); | ||
| range.checkRange(start, size.getEndByStart(start)); | ||
| } | ||
|
|
||
| return { | ||
| handleScroll | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
建议改为
disable属性名,跟其他组件的设计保持一致,不过你这里可以为string,另外string还可以具体到auto字符串