You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt/Vue计算属性报`slice` is not a function错误求助

Nuxt/Vue 用户列表分页过滤报错问题解决

问题描述

开发Nuxt/Vue应用时,从后端获取用户列表可正常展示,但实现分页与过滤功能时,出现filteredList.value的slice或length不是函数的错误。即使调试时看到计算属性是数组,错误仍存在。简化版组件代码如下:

<script setup lang="ts">
interface UserDTO {
  id: Number,
  name: String,
  email: String,
  roles: String,
  created_at: String,
  updated_at: String
}

// const data = shallowRef<UserDTO[]>([
//   {
//     "id": 1,
//     "email": "admin@example.com",
//     "name": "Admin",
//     "roles": "admin",
//     "created_at": "2024-11-01T04:44:41.000Z",
//     "updated_at": "2024-11-01T04:44:41.251Z"
//   }
// ])
const { data } = await useFetch<UserDTO[]>('/api/users')

const search = ref('')
const page = ref(1)
const itemsPerPage = ref(10)

const filteredList = computed(() => {
  const users = data.value
  if (!users) { return [] }
  const query = search.value.toLowerCase()
  if (query.trim() === "") {
    return data
  }
  return users.filter((user) => {
    return (
      user.name.toLowerCase().includes(query) ||
      user.email.toLowerCase().includes(query) ||
      user.roles.toLowerCase().includes(query)
    )
  })
})
const displayList = computed(() => {
  return filteredList.value.slice(startIndex.value, endIndex.value)
})
const displayStartIndex = computed(() => {
  if (filteredList.value.length === 0) {
    return 0
  }
  return startIndex.value + 1
})
const startIndex = computed(() => {
  return (page.value - 1) * itemsPerPage.value
})
const endIndex = computed(() => {
  return Math.min(
    filteredList.value.length,
    startIndex.value + itemsPerPage.value
  )
})
const pageTotal = computed(() => {
  return Math.max(
    1,
    Math.ceil(filteredList.value.length / itemsPerPage.value)
  )
})
</script>

<template>
  <div>
    <input
      type="text"
      class="form-control"
      placeholder="Search…"
      v-model="search"
      autocomplete="off"
    />
    <select class="form-select" v-model="itemsPerPage">
      <option>10</option>
      <option>20</option>
      <option>50</option>
      <option>100</option>
    </select>
    &nbsp;entries
    <table>
      <tbody>
        <tr v-for="(user, index) in displayList" :key="index">
          <td>
            {{ user.name }}
          </td>
          <td class="text-secondary">
            {{ user.email }}
          </td>
          <td class="text-secondary">
            {{ user.roles }}
          </td>
          <td class="text-secondary">
            {{ user.created_at }}
          </td>
          <td class="text-secondary">
            {{ user.updated_at }}
          </td>
        </tr>
        <tr v-if="filteredList.length === 0">
          <td colspan="5">
            No users to show
          </td>
        </tr>
      </tbody>
    </table>
    <p>
      Showing {{ displayStartIndex }} to {{ endIndex }} of
      {{ filteredList.length }} entries
    </p>
  </div>
</template>

问题原因

  • 核心错误:filteredList计算属性中,当搜索框为空时返回的是data(Nuxt useFetch返回的Ref响应式对象),而非实际的数组data.value。后续依赖filteredList的计算属性调用数组方法时,实际操作的是Ref对象,自然会报错。
  • 次要问题:UserDTO接口中使用了大写的Number/String(构造函数类型),正确的TypeScript基本类型应该是小写的number/string。

修复方案

1. 修正filteredList的返回值

将搜索为空时的返回值从data改为users(即data.value),确保始终返回数组类型:

const filteredList = computed(() => {
  const users = data.value
  if (!users) { return [] }
  const query = search.value.toLowerCase()
  if (query.trim() === "") {
    return users // 替换原返回的data(Ref对象)为实际数组
  }
  return users.filter((user) => {
    return (
      user.name.toLowerCase().includes(query) ||
      user.email.toLowerCase().includes(query) ||
      user.roles.toLowerCase().includes(query)
    )
  })
})

2. 修正接口类型定义

把接口中的大写类型改为小写基本类型:

interface UserDTO {
  id: number,
  name: string,
  email: string,
  roles: string,
  created_at: string,
  updated_at: string
}

完整修复后的代码

<script setup lang="ts">
interface UserDTO {
  id: number,
  name: string,
  email: string,
  roles: string,
  created_at: string,
  updated_at: string
}

const { data } = await useFetch<UserDTO[]>('/api/users')

const search = ref('')
const page = ref(1)
const itemsPerPage = ref(10)

const filteredList = computed(() => {
  const users = data.value
  if (!users) { return [] }
  const query = search.value.toLowerCase()
  if (query.trim() === "") {
    return users
  }
  return users.filter((user) => {
    return (
      user.name.toLowerCase().includes(query) ||
      user.email.toLowerCase().includes(query) ||
      user.roles.toLowerCase().includes(query)
    )
  })
})
const displayList = computed(() => {
  return filteredList.value.slice(startIndex.value, endIndex.value)
})
const displayStartIndex = computed(() => {
  if (filteredList.value.length === 0) {
    return 0
  }
  return startIndex.value + 1
})
const startIndex = computed(() => {
  return (page.value - 1) * itemsPerPage.value
})
const endIndex = computed(() => {
  return Math.min(
    filteredList.value.length,
    startIndex.value + itemsPerPage.value
  )
})
const pageTotal = computed(() => {
  return Math.max(
    1,
    Math.ceil(filteredList.value.length / itemsPerPage.value)
  )
})
</script>

<template>
  <div>
    <input
      type="text"
      class="form-control"
      placeholder="Search…"
      v-model="search"
      autocomplete="off"
    />
    <select class="form-select" v-model="itemsPerPage">
      <option>10</option>
      <option>20</option>
      <option>50</option>
      <option>100</option>
    </select>
    &nbsp;entries
    <table>
      <tbody>
        <tr v-for="(user, index) in displayList" :key="index">
          <td>
            {{ user.name }}
          </td>
          <td class="text-secondary">
            {{ user.email }}
          </td>
          <td class="text-secondary">
            {{ user.roles }}
          </td>
          <td class="text-secondary">
            {{ user.created_at }}
          </td>
          <td class="text-secondary">
            {{ user.updated_at }}
          </td>
        </tr>
        <tr v-if="filteredList.length === 0">
          <td colspan="5">
            No users to show
          </td>
        </tr>
      </tbody>
    </table>
    <p>
      Showing {{ displayStartIndex }} to {{ endIndex }} of
      {{ filteredList.length }} entries
    </p>
  </div>
</template>

补充说明

调试时看似filteredList是数组,可能是因为Vue DevTools会自动展开Ref对象的value属性,导致你误以为返回的是数组,但实际代码中操作的是未解包的Ref对象,从而引发错误。

内容的提问来源于stack exchange,提问作者Yonic Surny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 19:52:32