vuejs / eslint-plugin-vue

Official ESLint plugin for Vue.js
https://eslint.vuejs.org/
MIT License
4.45k stars 663 forks source link

[Proposal] Allow single-element exception for `vue/require-v-for-key` #2512

Open matthew-dean opened 2 months ago

matthew-dean commented 2 months ago

What rule do you want to change? vue/require-v-for-key

Context It's a useful pattern to bind an object within a larger object to the scope of Vue markup to reduce code. There really is no mechanism for this in Vue except for v-for. v-for (+ Vue Language Services) scopes a variable based on how it interprets the result of the v-for statement. Because of this, you can essentially reduce boilerplate and, in some cases, slightly speed up performance by using v-for.

Consider this example where getTyped is something like Lodash's get, but providing this specific path in the following example returns a strongly-typed object with the shape of:

Reactive<{
  value: string
  error: string | undefined
}>
    <input v-model="getTyped(model, 'a.deeply.bound.path.name').value" />
    <div v-if="getTyped(model, 'a.deeply.bound.path.name').error">
      {{ getTyped(model, 'a.deeply.bound.path.name').error }}
    </div>

This is, of course, ugly and verbose. A nifty way to clean it up for a template binding would be to do this:

  <template v-for="name in [getTyped(model, 'a.deeply.bound.path.name')]">
    <input v-model="name.value" />
    <div v-if="name.error">
      {{ name.error }}
    </div>
  </template>

TypeScript / Volar respects this immediately, and we get a strong typing for name as expected. However, eslint-plugin-vue (with this rule) immediately complains. In this case, though, it doesn't need to. While the Vue docs recommend a key for v-for, the above example is easily parseable to be understood as a tuple constant with one element, and there are no key issues when only ever iterating from one element. There will only ever be one element, so the key is not needed.

Does this change cause the rule to produce more or fewer warnings? Fewer

How will the change be implemented? (New option, new default behavior, etc.)? New option:

'vue/require-v-for-key': ['error', {
  allowSingleElement: true
}]

Please provide some example code that this change will affect:

<div v-for="name in [get(model, 'a.deeply.bound.model')]">
    <input v-model="name.value" />
    <div test-id="name">
      {{ name.error }}
    </div>
  </div>

What does the rule currently do for this code? It throws an error if key is absent.

What will the rule do after it's changed? The rule will behave the same by default. If allowSingleElement is set to true, it throws an error if key is absent, except when v-for refers only to a tuple with a single element / item.

Thanks for considering!

matthew-dean commented 2 months ago

Just a little bit more context. I took a look at Vue's documentation, and it has this to say (emphasis mine):

It is recommended to provide a key attribute with v-for whenever possible, unless the iterated DOM content is simple (i.e. contains no components or stateful DOM elements), or you are intentionally relying on the default behavior for performance gains.

I believe that this change would conform entirely to the documentation, as this use-case is, by definition, simple.

FloEdelmann commented 2 weeks ago

the iterated DOM content is simple (i.e. contains no components or stateful DOM elements)

Your examples are not "simple" in the mentioned sense though, as you have an <input> in the iterated content, which is a stateful DOM element.

However, regarding your proposal: I'm not sure whether we should endorse this behavior by adding an option for it. Using v-for with only a single array item is a hacky workaround for a template variable. Why not use a computed variable for it? And if it's inside of a ("real") v-for loop, then maybe it would rather make sense to extract that loop content into a separate component, where you could use a computed variable again.