Skip to content

Commit 6a554fe

Browse files
authored
perf(runtime-dom/vModel): remove looseHas if model is Set (#2236)
1 parent 6b8cf99 commit 6a554fe

File tree

3 files changed

+208
-18
lines changed

3 files changed

+208
-18
lines changed

packages/runtime-dom/__tests__/directives/vModel.spec.ts

+201-1
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ describe('vModel', () => {
619619
expect(bar.selected).toEqual(true)
620620
})
621621

622-
it('should work with multiple select', async () => {
622+
it('multiple select (model is Array)', async () => {
623623
const component = defineComponent({
624624
data() {
625625
return { value: [] }
@@ -783,6 +783,206 @@ describe('vModel', () => {
783783
expect(two.selected).toEqual(true)
784784
})
785785

786+
it('multiple select (model is Array, option value is object)', async () => {
787+
const fooValue = { foo: 1 }
788+
const barValue = { bar: 1 }
789+
790+
const component = defineComponent({
791+
data() {
792+
return { value: [] }
793+
},
794+
render() {
795+
return [
796+
withVModel(
797+
h(
798+
'select',
799+
{
800+
value: null,
801+
multiple: true,
802+
'onUpdate:modelValue': setValue.bind(this)
803+
},
804+
[
805+
h('option', { value: fooValue }),
806+
h('option', { value: barValue })
807+
]
808+
),
809+
this.value
810+
)
811+
]
812+
}
813+
})
814+
render(h(component), root)
815+
816+
await nextTick()
817+
818+
const input = root.querySelector('select')
819+
const [foo, bar] = root.querySelectorAll('option')
820+
const data = root._vnode.component.data
821+
822+
foo.selected = true
823+
triggerEvent('change', input)
824+
await nextTick()
825+
expect(data.value).toMatchObject([fooValue])
826+
827+
foo.selected = false
828+
bar.selected = true
829+
triggerEvent('change', input)
830+
await nextTick()
831+
expect(data.value).toMatchObject([barValue])
832+
833+
foo.selected = true
834+
bar.selected = true
835+
triggerEvent('change', input)
836+
await nextTick()
837+
expect(data.value).toMatchObject([fooValue, barValue])
838+
839+
foo.selected = false
840+
bar.selected = false
841+
data.value = [fooValue, barValue]
842+
await nextTick()
843+
expect(foo.selected).toEqual(true)
844+
expect(bar.selected).toEqual(true)
845+
846+
foo.selected = false
847+
bar.selected = false
848+
data.value = [{ foo: 1 }, { bar: 1 }]
849+
await nextTick()
850+
// looseEqual
851+
expect(foo.selected).toEqual(true)
852+
expect(bar.selected).toEqual(true)
853+
})
854+
855+
it('multiple select (model is Set)', async () => {
856+
const component = defineComponent({
857+
data() {
858+
return { value: new Set() }
859+
},
860+
render() {
861+
return [
862+
withVModel(
863+
h(
864+
'select',
865+
{
866+
value: null,
867+
multiple: true,
868+
'onUpdate:modelValue': setValue.bind(this)
869+
},
870+
[h('option', { value: 'foo' }), h('option', { value: 'bar' })]
871+
),
872+
this.value
873+
)
874+
]
875+
}
876+
})
877+
render(h(component), root)
878+
879+
const input = root.querySelector('select')
880+
const foo = root.querySelector('option[value=foo]')
881+
const bar = root.querySelector('option[value=bar]')
882+
const data = root._vnode.component.data
883+
884+
foo.selected = true
885+
triggerEvent('change', input)
886+
await nextTick()
887+
expect(data.value).toMatchObject(new Set(['foo']))
888+
889+
foo.selected = false
890+
bar.selected = true
891+
triggerEvent('change', input)
892+
await nextTick()
893+
expect(data.value).toMatchObject(new Set(['bar']))
894+
895+
foo.selected = true
896+
bar.selected = true
897+
triggerEvent('change', input)
898+
await nextTick()
899+
expect(data.value).toMatchObject(new Set(['foo', 'bar']))
900+
901+
foo.selected = false
902+
bar.selected = false
903+
data.value = new Set(['foo'])
904+
await nextTick()
905+
expect(input.value).toEqual('foo')
906+
expect(foo.selected).toEqual(true)
907+
expect(bar.selected).toEqual(false)
908+
909+
foo.selected = false
910+
bar.selected = false
911+
data.value = new Set(['foo', 'bar'])
912+
await nextTick()
913+
expect(foo.selected).toEqual(true)
914+
expect(bar.selected).toEqual(true)
915+
})
916+
917+
it('multiple select (model is Set, option value is object)', async () => {
918+
const fooValue = { foo: 1 }
919+
const barValue = { bar: 1 }
920+
921+
const component = defineComponent({
922+
data() {
923+
return { value: new Set() }
924+
},
925+
render() {
926+
return [
927+
withVModel(
928+
h(
929+
'select',
930+
{
931+
value: null,
932+
multiple: true,
933+
'onUpdate:modelValue': setValue.bind(this)
934+
},
935+
[
936+
h('option', { value: fooValue }),
937+
h('option', { value: barValue })
938+
]
939+
),
940+
this.value
941+
)
942+
]
943+
}
944+
})
945+
render(h(component), root)
946+
947+
await nextTick()
948+
949+
const input = root.querySelector('select')
950+
const [foo, bar] = root.querySelectorAll('option')
951+
const data = root._vnode.component.data
952+
953+
foo.selected = true
954+
triggerEvent('change', input)
955+
await nextTick()
956+
expect(data.value).toMatchObject(new Set([fooValue]))
957+
958+
foo.selected = false
959+
bar.selected = true
960+
triggerEvent('change', input)
961+
await nextTick()
962+
expect(data.value).toMatchObject(new Set([barValue]))
963+
964+
foo.selected = true
965+
bar.selected = true
966+
triggerEvent('change', input)
967+
await nextTick()
968+
expect(data.value).toMatchObject(new Set([fooValue, barValue]))
969+
970+
foo.selected = false
971+
bar.selected = false
972+
data.value = new Set([fooValue, barValue])
973+
await nextTick()
974+
expect(foo.selected).toEqual(true)
975+
expect(bar.selected).toEqual(true)
976+
977+
foo.selected = false
978+
bar.selected = false
979+
data.value = new Set([{ foo: 1 }, { bar: 1 }])
980+
await nextTick()
981+
// whithout looseEqual, here is different from Array
982+
expect(foo.selected).toEqual(false)
983+
expect(bar.selected).toEqual(false)
984+
})
985+
786986
it('should work with composition session', async () => {
787987
const component = defineComponent({
788988
data() {

packages/runtime-dom/src/directives/vModel.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import {
1212
looseIndexOf,
1313
invokeArrayFns,
1414
toNumber,
15-
isSet,
16-
looseHas
15+
isSet
1716
} from '@vue/shared'
1817

1918
type AssignerFn = (value: any) => void
@@ -119,12 +118,10 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
119118
assign(filtered)
120119
}
121120
} else if (isSet(modelValue)) {
122-
const found = modelValue.has(elementValue)
123-
if (checked && !found) {
124-
assign(modelValue.add(elementValue))
125-
} else if (!checked && found) {
121+
if (checked) {
122+
modelValue.add(elementValue)
123+
} else {
126124
modelValue.delete(elementValue)
127-
assign(modelValue)
128125
}
129126
} else {
130127
assign(getCheckboxValue(el, checked))
@@ -148,7 +145,7 @@ function setChecked(
148145
if (isArray(value)) {
149146
el.checked = looseIndexOf(value, vnode.props!.value) > -1
150147
} else if (isSet(value)) {
151-
el.checked = looseHas(value, vnode.props!.value)
148+
el.checked = value.has(vnode.props!.value)
152149
} else if (value !== oldValue) {
153150
el.checked = looseEqual(value, getCheckboxValue(el, true))
154151
}
@@ -213,7 +210,7 @@ function setSelected(el: HTMLSelectElement, value: any) {
213210
if (isArray(value)) {
214211
option.selected = looseIndexOf(value, optionValue) > -1
215212
} else {
216-
option.selected = looseHas(value, optionValue)
213+
option.selected = value.has(optionValue)
217214
}
218215
} else {
219216
if (looseEqual(getValue(option), value)) {
@@ -305,7 +302,7 @@ if (__NODE_JS__) {
305302
return { checked: true }
306303
}
307304
} else if (isSet(value)) {
308-
if (vnode.props && looseHas(value, vnode.props.value)) {
305+
if (vnode.props && value.has(vnode.props.value)) {
309306
return { checked: true }
310307
}
311308
} else if (value) {

packages/shared/src/looseEqual.ts

-7
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,3 @@ export function looseEqual(a: any, b: any): boolean {
5151
export function looseIndexOf(arr: any[], val: any): number {
5252
return arr.findIndex(item => looseEqual(item, val))
5353
}
54-
55-
export function looseHas(set: Set<any>, val: any): boolean {
56-
for (let item of set) {
57-
if (looseEqual(item, val)) return true
58-
}
59-
return false
60-
}

0 commit comments

Comments
 (0)