自定义自动完成输入清除选择的选项。 `update:modelValue` 问题
Custom autocomplete input clearing selected option. `update:modelValue` issue
我很想用 deboand 创建一个自动完成输入,但出于某种原因我无法让它正常工作。
我可以 select 从下拉菜单中选择一个建议并在屏幕上简短地显示它,但随后它会与输入本身一起重置。
我很确定错误来自 selectSuggestion
函数的最后一行以清除 valueInner
,但是一旦值是 selected 就清除输入是我想要的.但是,我不希望 selected 值在此之后被清除。
它可能是 selectSuggestion
函数和 valueInner
上的 watch
的组合,但我还需要 watch
来使去抖动工作。
关于如何解决这个问题的任何想法?
CodeSandbox Link: https://codesandbox.io/s/stoic-forest-7w19oj?file=/src/components/Autocomplete.vue
App.vue
<template>
<Autocomplete v-model="text" :suggestions="['something','hairstyle','cheese']"/>
<p>Selected Suggestion:{{text}}</p>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import Autocomplete from '../src/components/Autocomplete'
export default defineComponent({
name: "App",
setup(){
const text = ref('')
return { text }
},
components: {
Autocomplete,
Autocomplete2,
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
自动完成
<template>
<input
ref="inputRef"
v-model="valueInner"
:disabled="disabled || undefined"
:placeholder="placeholder"
:readonly="readonly || undefined"
:required="required || undefined"
:autofocus="autofocus"
:autocomplete="autocomplete"
:suggestions="suggestions"
/>
<ul v-if="filterSuggestions.length">
<li>
Showing {{ filterSuggestions.length }} of {{ suggestions.length }} results
</li>
<li
v-for="suggestion in filterSuggestions"
:key="suggestion"
@click="selectSuggestion(suggestion)"
>
{{ suggestion}}
</li>
</ul>
</template>
<script lang="ts">
import {
defineComponent,
ref,
onMounted,
nextTick,
watch,
PropType,
computed,
} from 'vue'
export default defineComponent({
name: 'Autocomplete',
inheritAttrs: false,
props: {
label: {
type: String,
},
disabled: { type: Boolean, default: false },
placeholder: { type: String },
readonly: { type: Boolean },
required: { type: Boolean },
modelValue: { type: [String, Number], default: '' },
autocomplete: { type: Boolean, default: false },
suggestions: {
type: Array as PropType<string[]>,
default: ['Hamburger', 'Fries', 'Peanuts'],
},
debounce: { type: Number, default: undefined },
},
emits: ['update:modelValue'],
setup(props, { emit }) {
let debounceInner = 0
if (typeof props.debounce === 'number') {
debounceInner = props.debounce
} debounceInner = 1000
const inputRef = ref<HTMLElement | null>(null)
const isFocused = ref(false)
watch(isFocused, (oldVal, newVal) => {
console.log(`isFocused → `, isFocused)
})
let timeout: any = null
const valueInner = ref<any>(props.modelValue)
console.log(valueInner.value)
watch(valueInner, (newVal, oldVal) => {
const debounceMs = debounceInner
if (debounceMs > 0) {
clearTimeout(timeout)
timeout = setTimeout(() => emitInput(newVal), debounceMs)
} else {
emitInput(newVal)
}
})
function emitInput(newVal: any) {
let payload = newVal
emit('update:modelValue', payload)
}
let selectedSuggestion = ref('')
const suggestions = ['United States', 'Japan', 'Italy', 'Australia', 'Germany', 'Poland', 'Ukraine']
const filterSuggestions = computed(() => {
if (valueInner.value === '') {
return []
}
let matches = 0
return suggestions.filter((suggestion) =>{
if (
suggestion.toLowerCase().includes(valueInner.value.toLowerCase())
&& matches < 10
) {
matches++
return suggestion
}
})
})
const selectSuggestion = (suggestion) => {
selectedSuggestion.value = suggestion
emitInput(suggestion)
valueInner.value = ''
}
function emitInput(newVal: any) {
let payload = newVal
emit('update:modelValue', payload)
}
watch(valueInner, (newVal, oldVal) => {
const debounceMs = debounceInner
if (debounceMs > 0) {
clearTimeout(timeout)
timeout = setTimeout(() => emitInput(newVal), debounceMs)
} else {
emitInput(newVal)
}
})
return {
inputRef,
valueInner,
suggestions,
filterSuggestions,
selectSuggestion,
selectedSuggestion,
}
},
})
</script>
<style lang="sass" scoped>
li
list-style: none
</style>
错误确实来自清除 valueInner.value
,这会触发其上的监视,它会发出空白值作为 update:modelValue
事件数据,从而覆盖模型值。
一个快速修复方法是在重置 valueInner.value
时使用 null
而不是空字符串(以区分内部清除与用户输入):
const selectSuggestion = (suggestion) => {
selectedSuggestion.value = suggestion
emitInput(suggestion)
valueInner.value = null
}
然后在手表中,如果新值是 null
,则立即 return 因为我们不需要发出那个:
watch(valueInner, (newVal, oldVal) => {
if (newVal === null) return
⋮
/* handle new value */
})
此外,在 filterSuggestions
中,return 值为假值时的空数组(包括 null
或空字符串):
const filterSuggestions = computed(() => {
if (!valueInner.value) {
return []
}
⋮
})
我很想用 deboand 创建一个自动完成输入,但出于某种原因我无法让它正常工作。 我可以 select 从下拉菜单中选择一个建议并在屏幕上简短地显示它,但随后它会与输入本身一起重置。
我很确定错误来自 selectSuggestion
函数的最后一行以清除 valueInner
,但是一旦值是 selected 就清除输入是我想要的.但是,我不希望 selected 值在此之后被清除。
它可能是 selectSuggestion
函数和 valueInner
上的 watch
的组合,但我还需要 watch
来使去抖动工作。
关于如何解决这个问题的任何想法?
CodeSandbox Link: https://codesandbox.io/s/stoic-forest-7w19oj?file=/src/components/Autocomplete.vue
App.vue
<template>
<Autocomplete v-model="text" :suggestions="['something','hairstyle','cheese']"/>
<p>Selected Suggestion:{{text}}</p>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import Autocomplete from '../src/components/Autocomplete'
export default defineComponent({
name: "App",
setup(){
const text = ref('')
return { text }
},
components: {
Autocomplete,
Autocomplete2,
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
自动完成
<template>
<input
ref="inputRef"
v-model="valueInner"
:disabled="disabled || undefined"
:placeholder="placeholder"
:readonly="readonly || undefined"
:required="required || undefined"
:autofocus="autofocus"
:autocomplete="autocomplete"
:suggestions="suggestions"
/>
<ul v-if="filterSuggestions.length">
<li>
Showing {{ filterSuggestions.length }} of {{ suggestions.length }} results
</li>
<li
v-for="suggestion in filterSuggestions"
:key="suggestion"
@click="selectSuggestion(suggestion)"
>
{{ suggestion}}
</li>
</ul>
</template>
<script lang="ts">
import {
defineComponent,
ref,
onMounted,
nextTick,
watch,
PropType,
computed,
} from 'vue'
export default defineComponent({
name: 'Autocomplete',
inheritAttrs: false,
props: {
label: {
type: String,
},
disabled: { type: Boolean, default: false },
placeholder: { type: String },
readonly: { type: Boolean },
required: { type: Boolean },
modelValue: { type: [String, Number], default: '' },
autocomplete: { type: Boolean, default: false },
suggestions: {
type: Array as PropType<string[]>,
default: ['Hamburger', 'Fries', 'Peanuts'],
},
debounce: { type: Number, default: undefined },
},
emits: ['update:modelValue'],
setup(props, { emit }) {
let debounceInner = 0
if (typeof props.debounce === 'number') {
debounceInner = props.debounce
} debounceInner = 1000
const inputRef = ref<HTMLElement | null>(null)
const isFocused = ref(false)
watch(isFocused, (oldVal, newVal) => {
console.log(`isFocused → `, isFocused)
})
let timeout: any = null
const valueInner = ref<any>(props.modelValue)
console.log(valueInner.value)
watch(valueInner, (newVal, oldVal) => {
const debounceMs = debounceInner
if (debounceMs > 0) {
clearTimeout(timeout)
timeout = setTimeout(() => emitInput(newVal), debounceMs)
} else {
emitInput(newVal)
}
})
function emitInput(newVal: any) {
let payload = newVal
emit('update:modelValue', payload)
}
let selectedSuggestion = ref('')
const suggestions = ['United States', 'Japan', 'Italy', 'Australia', 'Germany', 'Poland', 'Ukraine']
const filterSuggestions = computed(() => {
if (valueInner.value === '') {
return []
}
let matches = 0
return suggestions.filter((suggestion) =>{
if (
suggestion.toLowerCase().includes(valueInner.value.toLowerCase())
&& matches < 10
) {
matches++
return suggestion
}
})
})
const selectSuggestion = (suggestion) => {
selectedSuggestion.value = suggestion
emitInput(suggestion)
valueInner.value = ''
}
function emitInput(newVal: any) {
let payload = newVal
emit('update:modelValue', payload)
}
watch(valueInner, (newVal, oldVal) => {
const debounceMs = debounceInner
if (debounceMs > 0) {
clearTimeout(timeout)
timeout = setTimeout(() => emitInput(newVal), debounceMs)
} else {
emitInput(newVal)
}
})
return {
inputRef,
valueInner,
suggestions,
filterSuggestions,
selectSuggestion,
selectedSuggestion,
}
},
})
</script>
<style lang="sass" scoped>
li
list-style: none
</style>
错误确实来自清除 valueInner.value
,这会触发其上的监视,它会发出空白值作为 update:modelValue
事件数据,从而覆盖模型值。
一个快速修复方法是在重置 valueInner.value
时使用 null
而不是空字符串(以区分内部清除与用户输入):
const selectSuggestion = (suggestion) => {
selectedSuggestion.value = suggestion
emitInput(suggestion)
valueInner.value = null
}
然后在手表中,如果新值是 null
,则立即 return 因为我们不需要发出那个:
watch(valueInner, (newVal, oldVal) => {
if (newVal === null) return
⋮
/* handle new value */
})
此外,在 filterSuggestions
中,return 值为假值时的空数组(包括 null
或空字符串):
const filterSuggestions = computed(() => {
if (!valueInner.value) {
return []
}
⋮
})