自定义自动完成输入清除所选选项。 `更新:modelValue`问题

发布于 2025-01-11 02:18:00 字数 5162 浏览 0 评论 0原文

我正在尝试使用 debo 创建自动完成输入,但由于某种原因我无法让它正常工作。 我可以从下拉菜单中选择一个建议,并将其短暂显示在屏幕上,但随后它会与输入本身一起重置。

我很确定错误来自 selectSuggestion 函数的最后一行清除 valueInner,但在选择值后清除输入正是我想要的。但是,我不希望在发生这种情况后清除选定的值。

它可以是 selectSuggestion 函数和 valueInner 上的 watch 的组合,但我还需要 watch 来使去抖工作。

关于如何解决这个问题有什么想法吗?

CodeSandbox 链接: 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>

I'm tyring to create a autocomplete input with deboand for some reason I am not able to get it to work properly.
I am able to select a suggestion from the drop down and show it briefly on the screen, but then it resets alongside the input itself.

I'm pretty sure the error comes from the selectSuggestion function's last line to clear the valueInner, but clearing the input once the value is selected is what I want. I don't want, however, the selected value to clear after that has happened.

It could be a combination of the selectSuggestion function and the watch on valueInner, but I also need that watch to make the debounce work.

Any ideas on how I can fix this?

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>

Autocomplete

<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>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

人间不值得 2025-01-18 02:18:00

该错误确实来自于清除 valueInner.value,这会触发其上的监视,从而将空白值作为 update:modelValue 事件数据发出,从而覆盖模型值。

快速解决方法是在重置 valueInner.value 时使用 null 而不是空字符串(以区分内部清除与用户输入):

const selectSuggestion = (suggestion) => {
selectedSuggestion.value = suggestion
emitInput(suggestion)
valueInner.value = null

The error is indeed from clearing valueInner.value, which triggers the watch on it, which emits the blank value as the update:modelValue event data, which overwrites the model value.

A quick fix is to use null instead of an empty string (to distinguish between an internal clear versus user input) when resetting valueInner.value:

const selectSuggestion = (suggestion) => {
  selectedSuggestion.value = suggestion
  emitInput(suggestion)
  valueInner.value = null ????
}

Then in the watch, return immediately if the new value is null because we don't need to emit that:

watch(valueInner, (newVal, oldVal) => {
  if (newVal === null) return
  ⋮
  /* handle new value */
})

Also, in filterSuggestions, return an empty array when the value is falsy (includes null or empty string):

const filterSuggestions = computed(() => {
     ????
  if (!valueInner.value) {
    return []
  }
  ⋮
})

demo

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文