首页 > 解决方案 > 在输入中键入清除文本时更新Vue组件

问题描述

我认为这是更新组件时的反应性问题。基本上,如果我在输入中快速键入并且父数据被更新,则文本有时会丢失。

最好在下面尝试一下,只需快速输入,您就会看到字母消失。

const search = Vue.component('search', {
  props: [ "suggestions", "settings", "search" ],
  template: `
  <div>
    <input type="text" placeholder="Search..." :value="search" @keyup="$emit('update:search', $event.target.value)">
    <p v-for="suggestion in suggestions">{{ suggestion }}</p>
  </div>
  `
})

new Vue({
  data: {
    settings: {},
    state: {
      autocompletedSuggestions: []
    },
    search: ""
  },
  watch: {
    search(value) {
      const dummyData = [ 'test1', 'test2', 'test3', 'test4', 'test5', 'test6', 'test7', 'test8' ]
      window.setTimeout(() => {
        let random = Math.round(Math.random() * (dummyData.length - 0) + 0);
        let suggestions = [];
        for (let i = 0; i < random; i++) {
          suggestions.push(dummyData[Math.round(Math.random() * (dummyData.length - 1) + 0)])
        }
        this.state.autocompletedSuggestions = suggestions
      }, 100)
    }
  }
}).$mount('#app')
p {
  padding: 0;
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <search :suggestions="state.autocompletedSuggestions"
          :settings="settings"
          :search.sync="search"></search>
</div>

我怎样才能阻止这种情况发生?

标签: javascriptvue.js

解决方案


这只是由以下原因引起的时间问题:

  1. keyup事件有很大的延迟(我的机器上在 50 到 80 毫秒之间 - 我猜也取决于键盘)
  2. 在您的示例中非常“方便地”选择了“超时”值setTimeout(如果您尝试明显更短的东西 - 例如 10 毫秒 - 或更长的时间(例如 1000 毫秒),所描述的不当行为的机会将会减少)

怎么了:

  1. 输入的第一个“a”字母 -> 事件
  2. keyup事件
  3. search更新为“a”+观察者设置超时
  4. 组件已更新
  5. 键入的第二个“a”字母
  6. 超时函数执行更新this.state.autocompletedSuggestions
  7. 组件更新 - “a”作为search道具推送 - 有效地将输入值从“aa”更改为“a”
  8. keyup事件(对于“aa”) - 但$event.target.value已经设置为“a”

如何修复它:

  1. 不要使用keyup事件。改为使用input。它被更快地触发(在用户输入之后)。keyup当用户使用上下文菜单粘贴并为每个不是您想要的键(包括 Shift、Alt 等)触发时不会触发
  2. 使用某种去抖动 - 仅在用户停止输入后触发搜索请求。400 毫秒对我来说很棒。setTimeout在您的示例中用于模拟 ajax 调用实际上对此很有用(请参阅我的示例)
  3. 每当您发起新的搜索请求时,请务必取消之前的搜索请求(如果有的话)。或者只是在请求期间禁用输入...

const search = Vue.component('search', {
  props: ["suggestions", "settings", "search"],
  template: `
  <div>
    <input type="text" placeholder="Search..." :value="search" @keyup="onKeyup" @input="onInput">
    <p v-for="suggestion in suggestions">{{ suggestion }}</p>
  </div>
  `,
  methods: {
    onKeyup(ev) {
      console.log(`Keyup event - Value: ${ev.target.value}`)
    },
    onInput(ev) {
      console.log(`Input event - Value: ${ev.target.value}`)
      this.$emit('update:search', ev.target.value)
    }
  },
  updated() {
    console.log("Updated")
  }
})

new Vue({
  data: {
    settings: {},
    state: {
      autocompletedSuggestions: []
    },
    search: "",
    timeout: 0,
    debounceTime: 400
  },
  watch: {
    search(value) {
      console.log("Search:", value)

      clearTimeout(this.timeout) // cancel previous timeout if any
      this.timeout = window.setTimeout(async () => {
        this.state.autocompletedSuggestions = await this.getSuggestionsAsync()
        console.log("Suggestions loaded")
      }, this.debounceTime)
    }
  },
  methods: {
    getSuggestionsAsync() {
      return new Promise((resolve) => {
        const dummyData = ['test1', 'test2', 'test3', 'test4', 'test5', 'test6', 'test7', 'test8']

        let random = Math.round(Math.random() * (dummyData.length - 0) + 0);
        let suggestions = [];
        for (let i = 0; i < random; i++) {
          suggestions.push(dummyData[Math.round(Math.random() * (dummyData.length - 1) + 0)])
        }

        // to simulate async call
        setTimeout(() => resolve(suggestions), 100)
      })
    }
  }
}).$mount('#app')
p {
  padding: 0;
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <pre>{{ search }}</pre>
  <search :suggestions="state.autocompletedSuggestions" :settings="settings" :search.sync="search"></search>
</div>


推荐阅读