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

TypeScript中sort函数第二个参数报对象可能为undefined错误

问题现象

在Vue3 + TypeScript项目中使用数组sort方法编写排序比较逻辑时,TS对回调函数的第二个参数b抛出类型报错:Object is possibly 'undefined',但参数a与b属于完全相同的类型,a却未触发同类报错,现象不符合预期。
出错代码行:

stories.value.sort((a, b) => a.story_score? - b.story_score?)

完整业务代码上下文如下,报错位置位于getStoryWithAuthor方法内的数组排序逻辑处:

import { ref, Ref } from "vue"
import useApi from "./useApi";

export interface Story {
  by: string
  descendants?: number
  id: number
  kids?: number[]
  score?: number
  time: number
  title?: string
  type: string
  url?: string
}

export interface Author {
  created: number
  id: string,
  karma: number,
  submitted: number[]
}

export interface StoryWithAuthor {
  author_id: string
  author_karma_score: number
  story_title?: string
  story_url ?: string
  timestamp: number
  story_score ?: number
}

const error: Ref<string>= ref('')
const stories: Ref<StoryWithAuthor[]> = ref([])

const getStories = async () :Promise<void> => {
  error.value = ''
  stories.value = []
 
  try {
    const { response: stories, request } = useApi<number[]>(
      'topstories.json'
    );
    await request()

    if (stories.value) {
      const storyIds: number[] = stories.value.sort(() => Math.random() - Math.random()).slice(0, 10)
      await getStoryWithAuthor(storyIds)
    }
  } catch (err: unknown) {
    error.value = 'Error fetching stories'
  }
}

const getStoryWithAuthor = async(storyIds: number[]): Promise<void> => {
  error.value = ''
  stories.value = []
  try {
    for (const id of storyIds) {
      const { response: story, request } = useApi<Story>(
        `item/${id}.json`
      );
      await request()
      if (story.value) {
        const author = await getAuthor(story.value.by)
        if (author) {
          stories.value.push(
            {
              author_id: author.id,
              author_karma_score: author.karma,
              story_title: story.value.title,
              story_url: story.value.url,
              timestamp: story.value.time,
              story_score: story.value.score,
            }
          )
        }
        // Typescript complains about second argument
        stories.value.sort((a, b) => a.story_score? - b.story_score?)
      }
  
    }
  } catch (err: unknown) {
    error.value = 'Error fetching story'
  }
}

const getAuthor = async (userId: string):Promise<Author | undefined> => {
  error.value = ''
  try {

    const { response: author, request } = useApi<Author>(
        `user/${userId}.json`
      );
    await request()
    return author.value
  } catch (err: unknown) {
    error.value = 'Error fetching author'
  }
}

type useStories = { getStories: () => Promise<void>, getStoryWithAuthor: (storyIds: number[]) => Promise<void>, stories: Ref<StoryWithAuthor[]>, error: Ref<unknown> }

const useStories = () :useStories => ({
  getStories, getStoryWithAuthor, stories, error,
})

export default useStories
报错原因

出现仅参数b报错、a不报错的现象,核心是两个问题:

  • 语法错误导致类型收窄逻辑异常:你写的b.story_score?是错误语法,TypeScript会把属性名后的后置问号解析为三元运算符的起始标记,而非可选链操作符(访问嵌套可选属性的正确可选链写法是obj?.prop)。在这个解析逻辑下,a.story_score?被识别为三元的条件判断部分,TS自动对a的属性做了truthy类型收窄,因此不报错;而后面的-b.story_score?属于三元的真值分支部分,TS没有对这部分做自动类型收窄,就抛出了Object is possibly 'undefined'的错误。
  • 排序逻辑位置不合理:你把sort调用写在了for循环内部,每push一个新元素就对整个数组做全量排序,此时TS对迭代过程中的数组类型收窄本身就不完整,会放大类型校验的异常;同时这个写法本身性能很差,完全没有必要。

除此之外你当前写的排序逻辑本身也是无效的:sort的比较回调需要返回大于0、小于0、等于0的数值来决定排序顺序,你现在写的不完整三元表达式,在a.story_score为0(falsy值)时会直接返回undefined,最终排序结果完全不可预期。

修复方案

1. 调整排序逻辑位置

把sort调用移到for循环外面,等所有元素都push进数组之后再统一排序,既解决TS类型收窄不完整的问题,也能减少不必要的排序计算、提升性能。

2. 正确处理可选值的比较逻辑

因为story_score是可选属性,排序时要给undefined值设置默认兜底值(比如按0计算,可根据业务需求调整),返回合法的比较数值。

修复后的getStoryWithAuthor方法核心代码如下:

const getStoryWithAuthor = async(storyIds: number[]): Promise<void> => {
  error.value = ''
  stories.value = []
  try {
    for (const id of storyIds) {
      const { response: story, request } = useApi<Story>(
        `item/${id}.json`
      );
      await request()
      if (story.value) {
        const author = await getAuthor(story.value.by)
        if (author) {
          stories.value.push(
            {
              author_id: author.id,
              author_karma_score: author.karma,
              story_title: story.value.title,
              story_url: story.value.url,
              timestamp: story.value.time,
              story_score: story.value.score,
            }
          )
        }
      }
    }
    // 所有元素插入完成后再统一排序
    stories.value.sort((a, b) => {
      // 给可选score设置默认值0,按分数降序排列
      const scoreA = a.story_score ?? 0
      const scoreB = b.story_score ?? 0
      return scoreB - scoreA
    })
  } catch (err: unknown) {
    error.value = 'Error fetching story'
  }
}

注意区分TS两种问号的使用场景:类型定义时属性名后加?标记属性可选;访问属性时用?.做安全的可选链访问,不要把问号写在属性名后面当作可选标记,会被解析为三元运算符起始符导致语法和类型错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 17:42:25