feat(runtime-vapor): createSelector (#279)
This commit is contained in:
parent
884c190f08
commit
e07eac9ba3
|
@ -3,9 +3,8 @@ import {
|
|||
ref,
|
||||
shallowRef,
|
||||
triggerRef,
|
||||
watch,
|
||||
type ShallowRef,
|
||||
type WatchSource,
|
||||
createSelector,
|
||||
} from '@vue/vapor'
|
||||
import { buildData } from './data'
|
||||
import { defer, wrap } from './profiling'
|
||||
|
@ -79,16 +78,6 @@ async function bench() {
|
|||
}
|
||||
}
|
||||
|
||||
// Reduce the complexity of `selected` from O(n) to O(1).
|
||||
function createSelector(source: WatchSource) {
|
||||
const cache: Record<keyof any, ShallowRef<boolean>> = {}
|
||||
watch(source, (val, old) => {
|
||||
if (old != undefined) cache[old]!.value = false
|
||||
if (val != undefined) cache[val]!.value = true
|
||||
})
|
||||
return (id: keyof any) => (cache[id] ??= shallowRef(false)).value
|
||||
}
|
||||
|
||||
const isSelected = createSelector(selected)
|
||||
</script>
|
||||
|
||||
|
@ -113,7 +102,6 @@ const isSelected = createSelector(selected)
|
|||
v-for="row of rows"
|
||||
:key="row.id"
|
||||
:class="{ danger: isSelected(row.id) }"
|
||||
v-memo="[row.label, row.id === selected]"
|
||||
>
|
||||
<td>{{ row.id }}</td>
|
||||
<td>
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
import { ref } from '@vue/reactivity'
|
||||
import { makeRender } from './_utils'
|
||||
import { createFor, createSelector, nextTick, renderEffect } from '../src'
|
||||
|
||||
const define = makeRender()
|
||||
|
||||
describe('api: createSelector', () => {
|
||||
test('basic', async () => {
|
||||
let calledTimes = 0
|
||||
let expectedCalledTimes = 0
|
||||
|
||||
const list = ref([{ id: 0 }, { id: 1 }, { id: 2 }])
|
||||
const index = ref(0)
|
||||
|
||||
const { host } = define(() => {
|
||||
const isSleected = createSelector(index)
|
||||
return createFor(
|
||||
() => list.value,
|
||||
([item]) => {
|
||||
const span = document.createElement('li')
|
||||
renderEffect(() => {
|
||||
calledTimes += 1
|
||||
const { id } = item.value
|
||||
span.textContent = `${id}.${isSleected(id) ? 't' : 'f'}`
|
||||
})
|
||||
return span
|
||||
},
|
||||
item => item.id,
|
||||
)
|
||||
}).render()
|
||||
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>0.t</li><li>1.f</li><li>2.f</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 3))
|
||||
|
||||
index.value = 1
|
||||
await nextTick()
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>0.f</li><li>1.t</li><li>2.f</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 2))
|
||||
|
||||
index.value = 2
|
||||
await nextTick()
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>0.f</li><li>1.f</li><li>2.t</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 2))
|
||||
|
||||
list.value[2].id = 3
|
||||
await nextTick()
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>0.f</li><li>1.f</li><li>3.f</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 1))
|
||||
})
|
||||
|
||||
test('custom compare', async () => {
|
||||
let calledTimes = 0
|
||||
let expectedCalledTimes = 0
|
||||
|
||||
const list = ref([{ id: 1 }, { id: 2 }, { id: 3 }])
|
||||
const index = ref(0)
|
||||
|
||||
const { host } = define(() => {
|
||||
const isSleected = createSelector(
|
||||
index,
|
||||
(key, value) => key === value + 1,
|
||||
)
|
||||
return createFor(
|
||||
() => list.value,
|
||||
([item]) => {
|
||||
const span = document.createElement('li')
|
||||
renderEffect(() => {
|
||||
calledTimes += 1
|
||||
const { id } = item.value
|
||||
span.textContent = `${id}.${isSleected(id) ? 't' : 'f'}`
|
||||
})
|
||||
return span
|
||||
},
|
||||
item => item.id,
|
||||
)
|
||||
}).render()
|
||||
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>1.t</li><li>2.f</li><li>3.f</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 3))
|
||||
|
||||
index.value = 1
|
||||
await nextTick()
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>1.f</li><li>2.t</li><li>3.f</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 2))
|
||||
|
||||
index.value = 2
|
||||
await nextTick()
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>1.f</li><li>2.f</li><li>3.t</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 2))
|
||||
|
||||
list.value[2].id = 4
|
||||
await nextTick()
|
||||
expect(host.innerHTML).toBe(
|
||||
'<li>1.f</li><li>2.f</li><li>4.f</li><!--for-->',
|
||||
)
|
||||
expect(calledTimes).toBe((expectedCalledTimes += 1))
|
||||
})
|
||||
})
|
|
@ -7,7 +7,6 @@ import {
|
|||
renderEffect,
|
||||
shallowRef,
|
||||
template,
|
||||
withDestructure,
|
||||
withDirectives,
|
||||
} from '../src'
|
||||
import { makeRender } from './_utils'
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import {
|
||||
type MaybeRefOrGetter,
|
||||
type ShallowRef,
|
||||
onScopeDispose,
|
||||
shallowRef,
|
||||
toValue,
|
||||
} from '@vue/reactivity'
|
||||
import { watchEffect } from './apiWatch'
|
||||
|
||||
export function createSelector<T, U extends T>(
|
||||
source: MaybeRefOrGetter<T>,
|
||||
fn: (key: U, value: T) => boolean = (key, value) => key === value,
|
||||
): (key: U) => boolean {
|
||||
let subs = new Map()
|
||||
let val: T
|
||||
let oldVal: U
|
||||
|
||||
watchEffect(() => {
|
||||
val = toValue(source)
|
||||
const keys = [...subs.keys()]
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
const key = keys[i]
|
||||
if (fn(key, val)) {
|
||||
const o = subs.get(key)
|
||||
o.value = true
|
||||
} else if (oldVal !== undefined && fn(key, oldVal)) {
|
||||
const o = subs.get(key)
|
||||
o.value = false
|
||||
}
|
||||
}
|
||||
oldVal = val as U
|
||||
})
|
||||
|
||||
return key => {
|
||||
let l: ShallowRef<boolean | undefined> & { _count?: number }
|
||||
if (!(l = subs.get(key))) subs.set(key, (l = shallowRef()))
|
||||
l.value
|
||||
l._count ? l._count++ : (l._count = 1)
|
||||
onScopeDispose(() => (l._count! > 1 ? l._count!-- : subs.delete(key)))
|
||||
return l.value !== undefined ? l.value : fn(key, val)
|
||||
}
|
||||
}
|
|
@ -132,6 +132,7 @@ export {
|
|||
export { createIf } from './apiCreateIf'
|
||||
export { createFor, createForSlots } from './apiCreateFor'
|
||||
export { createComponent } from './apiCreateComponent'
|
||||
export { createSelector } from './apiCreateSelector'
|
||||
|
||||
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
||||
export { toHandlers } from './helpers/toHandlers'
|
||||
|
|
Loading…
Reference in New Issue