vuejs / vue-loader

📦 Webpack loader for Vue.js components
MIT License
4.99k stars 915 forks source link

Array index in v-for is not treated correctly by ts-loader #1882

Open SniperJoe opened 2 years ago

SniperJoe commented 2 years ago

Version

16.8.1

Reproduction link

github.com

Steps to reproduce

Given I have an array in my component:

private bookmakersVote: DataTypes.BookmakerVoteData[] = [];

and I'm using it in the template:

<div v-show="appState == 2" class="bookmaker-vote-wrapper" v-for="(bk, bkIndex) in bookmakersVote" :key="bk.id" :class="bkIndex % 2 == 0 ? 'even' : 'odd'"></div>

What is expected?

Successful compilation

What is actually happening?

vue-loader compiles it into:

_createElementBlock(_Fragment, null, _renderList(_ctx.bookmakersVote, (bk, bkIndex) => {
            return _withDirectives((_openBlock(), _createElementBlock("div", {
              class: _normalizeClass(["bookmaker-vote-wrapper", bkIndex % 2 == 0 ? 'even' : 'odd']),
              key: bk.id
            }, [
              _createVNode(_component_bookmaker_vote, {
                data: _ctx.bookmakersVote[bkIndex],
                "onUpdate:data": ($event: any) => ((_ctx.bookmakersVote[bkIndex]) = $event),
                lock: _ctx.bkVotesCount > 2,
                onError: _ctx.showVoteError,
                onVote: _ctx.countVote
              }, null, 8 /* PROPS */, ["data", "onUpdate:data", "lock", "onError", "onVote"])
            ], 2 /* CLASS */)), [
              [_vShow, _ctx.appState == 2]
            ])
          }), 128 /* KEYED_FRAGMENT */)

ts-loader (v 9.2.6) refuses to compile that. It throws error:

ERROR in src/popup/popup.vue.ts
321:64-71
[tsl] ERROR in src/popup/popup.vue.ts(321,65)
      TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
ts-loader-default_0c5a263502dc9404

Temporary fix is to wrap the index:

parseInt(`${bkIndex}`)
kkuegler commented 2 years ago

Looking into this, it seems the (type) definition of renderList helper function (here aliased as _renderList) seems to be the reason for this behaviour.

There are multiple type overloads for renderList (selectively quoting from <vue-next>/packages/runtime-core/src/helpers/renderList.ts):

/**
 * v-for array
 */
export function renderList<T>(
  source: T[],
  renderItem: (value: T, index: number) => VNodeChild
): VNodeChild[]

/**
 * v-for object
 */
export function renderList<T>(
  source: T,
  renderItem: <K extends keyof T>(
    value: T[K],
    key: K,
    index: number
  ) => VNodeChild
): VNodeChild[]

The second parameter of the renderItem function can be a an index (with type number, when source is an array) or a key (with type string | number | symbol, when source is an object).

It seems compiling the template happens without any type knowledge about the Vue component instance, i.e. we don't know the type of bookmakersVote. As a result, the TS compiler does not know it should use the "array" definition of renderList. Instead the "object" definition applies, as it is a more generic overload.

Because of this, bkIndex in your example to the TS compiler has the type string | number | symbol, not all of which the modulo operator can be applied to.

I'm new to the Vue template compiler and vue-loader, but to me there seem to be some possible solutions for this:

ForesightImaging commented 6 months ago

for me this was happening only in production, we're using webpack. I was able to quell the error by adding as number to the usage of the index.