mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2024-12-09 04:31:35 +08:00
refactor(select): use functional select option to significantly imporve render preformance
This commit is contained in:
parent
b7c13912f5
commit
cbfc200dc1
41
demo/debugComponents/selectDebug.vue
Normal file
41
demo/debugComponents/selectDebug.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!--template>
|
||||||
|
<n-select v-model="value" :options="options" />
|
||||||
|
</template-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- <n-select v-model="value">
|
||||||
|
<n-select-option label="label1" value="value1">
|
||||||
|
Dog
|
||||||
|
</n-select-option>
|
||||||
|
<n-select-option label="label2" value="value2">
|
||||||
|
Cat
|
||||||
|
</n-select-option>
|
||||||
|
<n-select-option label="label3" value="value3">
|
||||||
|
Money
|
||||||
|
</n-select-option>
|
||||||
|
<n-select-option label="label4" value="value4">
|
||||||
|
Tiger
|
||||||
|
</n-select-option>
|
||||||
|
<n-select-option label="label5" value="value5">
|
||||||
|
Happy
|
||||||
|
</n-select-option>
|
||||||
|
</n-select> -->
|
||||||
|
<n-select v-model="value" :options="options" />
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
const options = []
|
||||||
|
for (let i = 0; i < 2500; ++i) {
|
||||||
|
options.push({
|
||||||
|
label: String(i),
|
||||||
|
value: i
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
options,
|
||||||
|
value: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -392,6 +392,10 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'IconTransitionDebug',
|
name: 'IconTransitionDebug',
|
||||||
path: `/${this.lang}/${this.theme}` + '/n-icon-transition-debug'
|
path: `/${this.lang}/${this.theme}` + '/n-icon-transition-debug'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SelectDebug',
|
||||||
|
path: `/${this.lang}/${this.theme}` + '/n-select-debug'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,7 @@ import cancelMarkDebug from './debugComponents/cancelMarkDebug'
|
|||||||
import cascaderDebug from './debugComponents/cascaderDebug'
|
import cascaderDebug from './debugComponents/cascaderDebug'
|
||||||
import verticalAlignDebug from './debugComponents/verticalAlignDebug'
|
import verticalAlignDebug from './debugComponents/verticalAlignDebug'
|
||||||
import iconTransitionDebug from './debugComponents/iconTransitionDebug'
|
import iconTransitionDebug from './debugComponents/iconTransitionDebug'
|
||||||
|
import selectDebug from './debugComponents/selectDebug'
|
||||||
|
|
||||||
import hljs from 'highlight.js/lib/highlight'
|
import hljs from 'highlight.js/lib/highlight'
|
||||||
import javascript from 'highlight.js/lib/languages/javascript'
|
import javascript from 'highlight.js/lib/languages/javascript'
|
||||||
@ -231,7 +232,8 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/n-icon-transition-debug',
|
path: '/n-icon-transition-debug',
|
||||||
component: iconTransitionDebug
|
component: iconTransitionDebug
|
||||||
}
|
},
|
||||||
|
{ path: '/n-select-debug', component: selectDebug }
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -19,15 +19,7 @@
|
|||||||
@scroll="handleMenuScroll"
|
@scroll="handleMenuScroll"
|
||||||
>
|
>
|
||||||
<div class="n-base-select-menu-option-wrapper">
|
<div class="n-base-select-menu-option-wrapper">
|
||||||
<div class="n-base-select-menu-light-bar-wrapper">
|
<n-select-menu-light-bar ref="lightBar" />
|
||||||
<transition name="n-base-select-menu-light-bar--transition">
|
|
||||||
<div
|
|
||||||
v-if="showLightBar"
|
|
||||||
class="n-base-select-menu-light-bar"
|
|
||||||
:style="{ top: `${lightBarTop}px` }"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
<template v-if="!loading">
|
<template v-if="!loading">
|
||||||
<template v-if="!useSlot">
|
<template v-if="!useSlot">
|
||||||
<n-select-option
|
<n-select-option
|
||||||
@ -39,7 +31,9 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<slot />
|
<n-render-options :mirror="mirror">
|
||||||
|
<slot />
|
||||||
|
</n-render-options>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
@ -66,10 +60,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import withlightbar from '../../../mixins/withlightbar'
|
|
||||||
import NScrollbar from '../../../common/Scrollbar'
|
import NScrollbar from '../../../common/Scrollbar'
|
||||||
import linkedOptions from '../../../utils/data/linkedOptions'
|
import linkedOptions from '../../../utils/data/linkedOptions'
|
||||||
import NSelectOption from './SelectOption'
|
import NSelectOption from './SelectOption'
|
||||||
|
import NSelectMenuLightBar from './SelectMenuLightBar'
|
||||||
|
import NRenderOptions from './SelectRenderOptions'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NBaseSelectMenu',
|
name: 'NBaseSelectMenu',
|
||||||
@ -80,9 +75,10 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
NScrollbar,
|
NScrollbar,
|
||||||
NSelectOption
|
NSelectOption,
|
||||||
|
NSelectMenuLightBar,
|
||||||
|
NRenderOptions
|
||||||
},
|
},
|
||||||
mixins: [withlightbar],
|
|
||||||
props: {
|
props: {
|
||||||
theme: {
|
theme: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -139,6 +135,10 @@ export default {
|
|||||||
width: {
|
width: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
mirror: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
@ -206,6 +206,21 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
hideLightBar () {
|
||||||
|
if (this.$refs.lightBar) {
|
||||||
|
this.$refs.lightBar.hideLightBar()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideLightBarSync () {
|
||||||
|
if (this.$refs.lightBar) {
|
||||||
|
this.$refs.lightBar.hideLightBarSync()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateLightBarPosition (el) {
|
||||||
|
if (this.$refs.lightBar) {
|
||||||
|
this.$refs.lightBar.updateLightBarPosition(el)
|
||||||
|
}
|
||||||
|
},
|
||||||
handleMenuScroll (e, scrollContainer, scrollContent) {
|
handleMenuScroll (e, scrollContainer, scrollContent) {
|
||||||
this.$emit('menu-scroll', e, scrollContainer, scrollContent)
|
this.$emit('menu-scroll', e, scrollContainer, scrollContent)
|
||||||
},
|
},
|
||||||
@ -217,6 +232,9 @@ export default {
|
|||||||
this.updateLightBarPosition(e.target)
|
this.updateLightBarPosition(e.target)
|
||||||
this.pendingOption = option
|
this.pendingOption = option
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
handleOptionMouseLeave (e, option) {
|
||||||
|
|
||||||
},
|
},
|
||||||
handleKeyUpUp () {
|
handleKeyUpUp () {
|
||||||
this.prev()
|
this.prev()
|
||||||
|
25
packages/base/SelectMenu/src/SelectMenuLightBar.vue
Normal file
25
packages/base/SelectMenu/src/SelectMenuLightBar.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="n-base-select-menu-light-bar-wrapper">
|
||||||
|
<transition name="n-base-select-menu-light-bar--transition">
|
||||||
|
<div
|
||||||
|
v-if="showLightBar"
|
||||||
|
class="n-base-select-menu-light-bar"
|
||||||
|
:style="{ top: `${lightBarTop}px` }"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import withlightbar from '../../../mixins/withlightbar'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NBaseSelectMenuLightBar',
|
||||||
|
mixins: [ withlightbar ],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,32 +1,10 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="n-base-select-option"
|
|
||||||
:class="{
|
|
||||||
'n-base-select-option--selected':
|
|
||||||
isSelected,
|
|
||||||
'n-base-select-option--disabled':
|
|
||||||
disabled
|
|
||||||
}"
|
|
||||||
:data-id="optionId"
|
|
||||||
@click="handleClick"
|
|
||||||
@mouseenter="handleMouseEnter"
|
|
||||||
@mouseleave="handleMouseLeave"
|
|
||||||
>
|
|
||||||
<slot>
|
|
||||||
{{ label }}
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'NBaseSelectOption',
|
name: 'NBaseSelectOption',
|
||||||
|
functional: true,
|
||||||
inject: {
|
inject: {
|
||||||
NBaseSelectMenu: {
|
NBaseSelectMenu: {
|
||||||
default: null
|
default: null
|
||||||
},
|
|
||||||
NBaseSelectOptionCollector: {
|
|
||||||
default: null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@ -45,60 +23,66 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
render (h, context) {
|
||||||
isSelected () {
|
const SelectMenu = context.injections.NBaseSelectMenu
|
||||||
return this.processedOption && this.NBaseSelectMenu.isSelected(this.processedOption)
|
let optionId = null
|
||||||
},
|
if (SelectMenu && SelectMenu.value2Id) {
|
||||||
value2Id () {
|
optionId = SelectMenu.value2Id.get(context.props.value)
|
||||||
if (this.NBaseSelectMenu) {
|
|
||||||
return this.NBaseSelectMenu.value2Id
|
|
||||||
} return null
|
|
||||||
},
|
|
||||||
id2Option () {
|
|
||||||
if (this.NBaseSelectMenu) {
|
|
||||||
return this.NBaseSelectMenu.id2Option
|
|
||||||
} return null
|
|
||||||
},
|
|
||||||
optionId () {
|
|
||||||
if (this.value2Id === null) return null
|
|
||||||
return this.value2Id.get(this.value)
|
|
||||||
},
|
|
||||||
processedOption () {
|
|
||||||
if (this.id2Option === null) return null
|
|
||||||
return this.id2Option.get(this.optionId)
|
|
||||||
}
|
}
|
||||||
},
|
let id2Option = null
|
||||||
watch: {
|
if (SelectMenu && SelectMenu.id2Option) {
|
||||||
value () {
|
id2Option = SelectMenu.id2Option
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.NBaseSelectOptionCollector.collectOptions()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
let option = null
|
||||||
mounted () {
|
if (optionId !== null && id2Option !== null) {
|
||||||
if (this.NBaseSelectOptionCollector) {
|
option = id2Option.get(optionId)
|
||||||
this.NBaseSelectOptionCollector.collectOptions()
|
|
||||||
}
|
}
|
||||||
},
|
let isSelected = false
|
||||||
beforeDestroy () {
|
if (SelectMenu && SelectMenu.isSelected && option) {
|
||||||
this.$nextTick().then(() => {
|
isSelected = SelectMenu.isSelected(option)
|
||||||
if (this.NBaseSelectOptionCollector) {
|
}
|
||||||
this.NBaseSelectOptionCollector.collectOptions()
|
const listeners = context.listeners || {}
|
||||||
|
function handleClick (e) {
|
||||||
|
SelectMenu.handleOptionClick(e, option)
|
||||||
|
listeners.click && listeners.click(e)
|
||||||
|
}
|
||||||
|
function handleMouseEnter (e) {
|
||||||
|
SelectMenu.handleOptionMouseEnter(e, option)
|
||||||
|
listeners.mouseenter && listeners.mouseenter(e)
|
||||||
|
}
|
||||||
|
function handleMouseLeave (e) {
|
||||||
|
SelectMenu.handleOptionMouseLeave(e, option)
|
||||||
|
listeners.mouseleave && listeners.mouseleave(e)
|
||||||
|
}
|
||||||
|
let on = {}
|
||||||
|
if (SelectMenu) {
|
||||||
|
on = {
|
||||||
|
click: handleClick,
|
||||||
|
mouseenter: handleMouseEnter,
|
||||||
|
mouseleave: handleMouseLeave
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
},
|
on = listeners
|
||||||
methods: {
|
|
||||||
handleClick (e) {
|
|
||||||
this.NBaseSelectMenu.handleOptionClick(e, this.processedOption)
|
|
||||||
this.$emit('click', e)
|
|
||||||
},
|
|
||||||
handleMouseEnter (e) {
|
|
||||||
this.NBaseSelectMenu.handleOptionMouseEnter(e, this.processedOption)
|
|
||||||
this.$emit('mouseenter', e)
|
|
||||||
},
|
|
||||||
handleMouseLeave (e) {
|
|
||||||
this.$emit('mouseleave', e)
|
|
||||||
}
|
}
|
||||||
|
let attrs = {}
|
||||||
|
if (optionId !== null) {
|
||||||
|
attrs = {
|
||||||
|
'data-id': optionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h('div', {
|
||||||
|
staticClass: 'n-base-select-option',
|
||||||
|
class: {
|
||||||
|
'n-base-select-option--selected': isSelected,
|
||||||
|
'n-base-select-option--disabled': context.props.disabled
|
||||||
|
},
|
||||||
|
key: context.props.value,
|
||||||
|
attrs,
|
||||||
|
on,
|
||||||
|
props: {
|
||||||
|
...context.props
|
||||||
|
}
|
||||||
|
}, (context.scopedSlots.default && context.scopedSlots.default()) || [ context.props.label ])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -63,11 +63,13 @@ export default {
|
|||||||
const children = getDefaultSlotOf(this)
|
const children = getDefaultSlotOf(this)
|
||||||
children.forEach((child, index) => {
|
children.forEach((child, index) => {
|
||||||
child.key = index
|
child.key = index
|
||||||
if (child.componentOptions) {
|
/**
|
||||||
if (VALID_COMPONENT.includes(getComponentNameOf(child))) {
|
* If component name is valid,
|
||||||
const propsData = getOptionPropsDataOf(child)
|
* there must be data
|
||||||
this.options.push(propsData)
|
*/
|
||||||
}
|
if (VALID_COMPONENT.includes(getComponentNameOf(child))) {
|
||||||
|
const propsData = getOptionPropsDataOf(child)
|
||||||
|
this.options.push({ ...propsData, children: child.children })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,38 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import SelectOption from './SelectOption'
|
||||||
getComponentNameOf,
|
|
||||||
getOptionPropsDataOf
|
|
||||||
} from '../../../utils/component'
|
|
||||||
|
|
||||||
import {
|
|
||||||
VALID_COMPONENT
|
|
||||||
} from './config'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NSelectRenderOptions',
|
name: 'NSelectRenderOptions',
|
||||||
functional: true,
|
functional: true,
|
||||||
|
inject: {
|
||||||
|
NBaseSelectMenu: {
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
mirror: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
render (h, context) {
|
render (h, context) {
|
||||||
const defaultSlot = context.scopedSlots.default()
|
if (context.props.mirror) {
|
||||||
const filteredDefaultSlot = defaultSlot
|
return context.children
|
||||||
.filter(vNode => {
|
} else {
|
||||||
if (VALID_COMPONENT.includes(getComponentNameOf(vNode))) {
|
const selectMenu = context.injections.NBaseSelectMenu
|
||||||
if (vNode.componentOptions) {
|
const options = (selectMenu && selectMenu.linkedOptions) || []
|
||||||
const filter = context.props.filter
|
return options.map(option => {
|
||||||
const filterable = context.props.filterable
|
return h(SelectOption, {
|
||||||
const remote = context.props.remote
|
props: {
|
||||||
if (!remote && filterable && filter) {
|
label: option.label,
|
||||||
const pattern = context.props.pattern
|
value: option.value
|
||||||
const option = getOptionPropsDataOf(vNode)
|
},
|
||||||
return filter(pattern, option)
|
scopedSlots: {
|
||||||
|
default () {
|
||||||
|
return option.children
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
return filteredDefaultSlot
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -113,7 +113,7 @@ export default {
|
|||||||
blur: context.listeners.blur || (() => {}),
|
blur: context.listeners.blur || (() => {}),
|
||||||
select: context.listeners.select || (() => {})
|
select: context.listeners.select || (() => {})
|
||||||
},
|
},
|
||||||
scopedSlots: context.scopedSlots
|
scopedSlots: { ...context.scopedSlots }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,8 +5,7 @@ import asthemecontext from '../../../mixins/asthemecontext'
|
|||||||
import WrapWithValue from './WrapWithValue'
|
import WrapWithValue from './WrapWithValue'
|
||||||
import {
|
import {
|
||||||
NBaseSelectMenu,
|
NBaseSelectMenu,
|
||||||
NBaseSelectOptionCollector,
|
NBaseSelectOptionCollector
|
||||||
NBaseSelectRenderOptions
|
|
||||||
} from '../../../base/SelectMenu'
|
} from '../../../base/SelectMenu'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -202,15 +201,7 @@ export default {
|
|||||||
size: this.size,
|
size: this.size,
|
||||||
theme: this.synthesizedTheme
|
theme: this.synthesizedTheme
|
||||||
}
|
}
|
||||||
}, [
|
}, options)
|
||||||
h(NBaseSelectRenderOptions, {
|
|
||||||
scopedSlots: {
|
|
||||||
default () {
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
])
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,6 @@
|
|||||||
:options="filteredOptions"
|
:options="filteredOptions"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
:size="size"
|
:size="size"
|
||||||
:remote="remote"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:no-data-content="noDataContent"
|
:no-data-content="noDataContent"
|
||||||
:not-found-content="notFoundContent"
|
:not-found-content="notFoundContent"
|
||||||
@ -72,12 +71,11 @@
|
|||||||
:filterable="filterable"
|
:filterable="filterable"
|
||||||
:is-selected="isSelected"
|
:is-selected="isSelected"
|
||||||
:use-slot="useSlot"
|
:use-slot="useSlot"
|
||||||
|
:mirror="false"
|
||||||
@menu-toggle-option="handleToggleOption"
|
@menu-toggle-option="handleToggleOption"
|
||||||
@menu-scroll="handleMenuScroll"
|
@menu-scroll="handleMenuScroll"
|
||||||
>
|
>
|
||||||
<n-base-select-render-options v-if="useSlot" :filterable="filterable" :remote="remote" :filter="filter" :pattern="pattern">
|
<slot />
|
||||||
<slot />
|
|
||||||
</n-base-select-render-options>
|
|
||||||
</n-base-select-menu>
|
</n-base-select-menu>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@ -93,8 +91,7 @@ import zindexable from '../../../mixins/zindexable'
|
|||||||
import clickoutside from '../../../directives/clickoutside'
|
import clickoutside from '../../../directives/clickoutside'
|
||||||
import {
|
import {
|
||||||
NBaseSelectMenu,
|
NBaseSelectMenu,
|
||||||
NBaseSelectOptionCollector,
|
NBaseSelectOptionCollector
|
||||||
NBaseSelectRenderOptions
|
|
||||||
} from '../../../base/SelectMenu'
|
} from '../../../base/SelectMenu'
|
||||||
import NBasePicker from '../../../base/Picker'
|
import NBasePicker from '../../../base/Picker'
|
||||||
import withapp from '../../../mixins/withapp'
|
import withapp from '../../../mixins/withapp'
|
||||||
@ -114,8 +111,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
NBaseSelectMenu,
|
NBaseSelectMenu,
|
||||||
NBasePicker,
|
NBasePicker,
|
||||||
NBaseSelectOptionCollector,
|
NBaseSelectOptionCollector
|
||||||
NBaseSelectRenderOptions
|
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
clickoutside
|
clickoutside
|
||||||
|
Loading…
Reference in New Issue
Block a user