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',
|
||||
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 verticalAlignDebug from './debugComponents/verticalAlignDebug'
|
||||
import iconTransitionDebug from './debugComponents/iconTransitionDebug'
|
||||
import selectDebug from './debugComponents/selectDebug'
|
||||
|
||||
import hljs from 'highlight.js/lib/highlight'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
@ -231,7 +232,8 @@ const routes = [
|
||||
{
|
||||
path: '/n-icon-transition-debug',
|
||||
component: iconTransitionDebug
|
||||
}
|
||||
},
|
||||
{ path: '/n-select-debug', component: selectDebug }
|
||||
])
|
||||
},
|
||||
{
|
||||
|
@ -19,15 +19,7 @@
|
||||
@scroll="handleMenuScroll"
|
||||
>
|
||||
<div class="n-base-select-menu-option-wrapper">
|
||||
<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>
|
||||
<n-select-menu-light-bar ref="lightBar" />
|
||||
<template v-if="!loading">
|
||||
<template v-if="!useSlot">
|
||||
<n-select-option
|
||||
@ -39,7 +31,9 @@
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot />
|
||||
<n-render-options :mirror="mirror">
|
||||
<slot />
|
||||
</n-render-options>
|
||||
</template>
|
||||
</template>
|
||||
<div
|
||||
@ -66,10 +60,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import withlightbar from '../../../mixins/withlightbar'
|
||||
import NScrollbar from '../../../common/Scrollbar'
|
||||
import linkedOptions from '../../../utils/data/linkedOptions'
|
||||
import NSelectOption from './SelectOption'
|
||||
import NSelectMenuLightBar from './SelectMenuLightBar'
|
||||
import NRenderOptions from './SelectRenderOptions'
|
||||
|
||||
export default {
|
||||
name: 'NBaseSelectMenu',
|
||||
@ -80,9 +75,10 @@ export default {
|
||||
},
|
||||
components: {
|
||||
NScrollbar,
|
||||
NSelectOption
|
||||
NSelectOption,
|
||||
NSelectMenuLightBar,
|
||||
NRenderOptions
|
||||
},
|
||||
mixins: [withlightbar],
|
||||
props: {
|
||||
theme: {
|
||||
type: String,
|
||||
@ -139,6 +135,10 @@ export default {
|
||||
width: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
mirror: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
@ -206,6 +206,21 @@ export default {
|
||||
}
|
||||
},
|
||||
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) {
|
||||
this.$emit('menu-scroll', e, scrollContainer, scrollContent)
|
||||
},
|
||||
@ -217,6 +232,9 @@ export default {
|
||||
this.updateLightBarPosition(e.target)
|
||||
this.pendingOption = option
|
||||
}
|
||||
},
|
||||
handleOptionMouseLeave (e, option) {
|
||||
|
||||
},
|
||||
handleKeyUpUp () {
|
||||
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>
|
||||
export default {
|
||||
name: 'NBaseSelectOption',
|
||||
functional: true,
|
||||
inject: {
|
||||
NBaseSelectMenu: {
|
||||
default: null
|
||||
},
|
||||
NBaseSelectOptionCollector: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@ -45,60 +23,66 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected () {
|
||||
return this.processedOption && this.NBaseSelectMenu.isSelected(this.processedOption)
|
||||
},
|
||||
value2Id () {
|
||||
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)
|
||||
render (h, context) {
|
||||
const SelectMenu = context.injections.NBaseSelectMenu
|
||||
let optionId = null
|
||||
if (SelectMenu && SelectMenu.value2Id) {
|
||||
optionId = SelectMenu.value2Id.get(context.props.value)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.$nextTick().then(() => {
|
||||
this.NBaseSelectOptionCollector.collectOptions()
|
||||
})
|
||||
let id2Option = null
|
||||
if (SelectMenu && SelectMenu.id2Option) {
|
||||
id2Option = SelectMenu.id2Option
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.NBaseSelectOptionCollector) {
|
||||
this.NBaseSelectOptionCollector.collectOptions()
|
||||
let option = null
|
||||
if (optionId !== null && id2Option !== null) {
|
||||
option = id2Option.get(optionId)
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$nextTick().then(() => {
|
||||
if (this.NBaseSelectOptionCollector) {
|
||||
this.NBaseSelectOptionCollector.collectOptions()
|
||||
let isSelected = false
|
||||
if (SelectMenu && SelectMenu.isSelected && option) {
|
||||
isSelected = SelectMenu.isSelected(option)
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
},
|
||||
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)
|
||||
} else {
|
||||
on = listeners
|
||||
}
|
||||
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>
|
||||
|
@ -63,11 +63,13 @@ export default {
|
||||
const children = getDefaultSlotOf(this)
|
||||
children.forEach((child, index) => {
|
||||
child.key = index
|
||||
if (child.componentOptions) {
|
||||
if (VALID_COMPONENT.includes(getComponentNameOf(child))) {
|
||||
const propsData = getOptionPropsDataOf(child)
|
||||
this.options.push(propsData)
|
||||
}
|
||||
/**
|
||||
* If component name is valid,
|
||||
* there must be data
|
||||
*/
|
||||
if (VALID_COMPONENT.includes(getComponentNameOf(child))) {
|
||||
const propsData = getOptionPropsDataOf(child)
|
||||
this.options.push({ ...propsData, children: child.children })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,37 +1,38 @@
|
||||
<script>
|
||||
import {
|
||||
getComponentNameOf,
|
||||
getOptionPropsDataOf
|
||||
} from '../../../utils/component'
|
||||
|
||||
import {
|
||||
VALID_COMPONENT
|
||||
} from './config'
|
||||
import SelectOption from './SelectOption'
|
||||
|
||||
export default {
|
||||
name: 'NSelectRenderOptions',
|
||||
functional: true,
|
||||
inject: {
|
||||
NBaseSelectMenu: {
|
||||
default: null
|
||||
},
|
||||
mirror: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
render (h, context) {
|
||||
const defaultSlot = context.scopedSlots.default()
|
||||
const filteredDefaultSlot = defaultSlot
|
||||
.filter(vNode => {
|
||||
if (VALID_COMPONENT.includes(getComponentNameOf(vNode))) {
|
||||
if (vNode.componentOptions) {
|
||||
const filter = context.props.filter
|
||||
const filterable = context.props.filterable
|
||||
const remote = context.props.remote
|
||||
if (!remote && filterable && filter) {
|
||||
const pattern = context.props.pattern
|
||||
const option = getOptionPropsDataOf(vNode)
|
||||
return filter(pattern, option)
|
||||
if (context.props.mirror) {
|
||||
return context.children
|
||||
} else {
|
||||
const selectMenu = context.injections.NBaseSelectMenu
|
||||
const options = (selectMenu && selectMenu.linkedOptions) || []
|
||||
return options.map(option => {
|
||||
return h(SelectOption, {
|
||||
props: {
|
||||
label: option.label,
|
||||
value: option.value
|
||||
},
|
||||
scopedSlots: {
|
||||
default () {
|
||||
return option.children
|
||||
}
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
return filteredDefaultSlot
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -113,7 +113,7 @@ export default {
|
||||
blur: context.listeners.blur || (() => {}),
|
||||
select: context.listeners.select || (() => {})
|
||||
},
|
||||
scopedSlots: context.scopedSlots
|
||||
scopedSlots: { ...context.scopedSlots }
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -5,8 +5,7 @@ import asthemecontext from '../../../mixins/asthemecontext'
|
||||
import WrapWithValue from './WrapWithValue'
|
||||
import {
|
||||
NBaseSelectMenu,
|
||||
NBaseSelectOptionCollector,
|
||||
NBaseSelectRenderOptions
|
||||
NBaseSelectOptionCollector
|
||||
} from '../../../base/SelectMenu'
|
||||
|
||||
export default {
|
||||
@ -202,15 +201,7 @@ export default {
|
||||
size: this.size,
|
||||
theme: this.synthesizedTheme
|
||||
}
|
||||
}, [
|
||||
h(NBaseSelectRenderOptions, {
|
||||
scopedSlots: {
|
||||
default () {
|
||||
return options
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
}, options)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,6 @@
|
||||
:options="filteredOptions"
|
||||
:multiple="multiple"
|
||||
:size="size"
|
||||
:remote="remote"
|
||||
:loading="loading"
|
||||
:no-data-content="noDataContent"
|
||||
:not-found-content="notFoundContent"
|
||||
@ -72,12 +71,11 @@
|
||||
:filterable="filterable"
|
||||
:is-selected="isSelected"
|
||||
:use-slot="useSlot"
|
||||
:mirror="false"
|
||||
@menu-toggle-option="handleToggleOption"
|
||||
@menu-scroll="handleMenuScroll"
|
||||
>
|
||||
<n-base-select-render-options v-if="useSlot" :filterable="filterable" :remote="remote" :filter="filter" :pattern="pattern">
|
||||
<slot />
|
||||
</n-base-select-render-options>
|
||||
<slot />
|
||||
</n-base-select-menu>
|
||||
</transition>
|
||||
</div>
|
||||
@ -93,8 +91,7 @@ import zindexable from '../../../mixins/zindexable'
|
||||
import clickoutside from '../../../directives/clickoutside'
|
||||
import {
|
||||
NBaseSelectMenu,
|
||||
NBaseSelectOptionCollector,
|
||||
NBaseSelectRenderOptions
|
||||
NBaseSelectOptionCollector
|
||||
} from '../../../base/SelectMenu'
|
||||
import NBasePicker from '../../../base/Picker'
|
||||
import withapp from '../../../mixins/withapp'
|
||||
@ -114,8 +111,7 @@ export default {
|
||||
components: {
|
||||
NBaseSelectMenu,
|
||||
NBasePicker,
|
||||
NBaseSelectOptionCollector,
|
||||
NBaseSelectRenderOptions
|
||||
NBaseSelectOptionCollector
|
||||
},
|
||||
directives: {
|
||||
clickoutside
|
||||
|
Loading…
Reference in New Issue
Block a user