TypeScript编写排序函数时方括号取值如何用typeof实现类型守卫
TypeScript 通用对象数组排序函数实现方案
问题说明
在编写基于传入键排序对象数组的函数时,现有硬编码字段判断的写法可正常运行,但扩展性差:后续扩展Person接口的字段时,必须为每个新增字段添加对应的条件判断分支,期望能根据排序键对应值的类型自动适配排序逻辑,无需手动新增分支。
尝试了以下三种运行时可正常生效的类型判断方式,但TypeScript会持续抛出类型错误:条件分支内调用的属性/方法不合法。原因是TS会将取值识别为Person接口所有键对应值的类型联合,不会自动收窄到符合该类型判断的对应类型:
if(typeof arr[prop] === "string") if(arr[prop] instanceof String) if(arr[prop].constructor.name === "String")
原有问题代码
interface Person{ name: string, age: number, isAlive: boolean } const myArr = [ {name: 'Tomás', age: 24, isAlive: true}, {name: 'Kate', age: 12, isAlive: true}, {name: 'Brad', age: 54, isAlive: false}, {name: 'Angelica', age: 64, isAlive: true}, ] function myFn( arr: Person[], orderBy: keyof Person): Person[] { if(orderBy === 'name'){ return arr.sort((a,b) => a[orderBy].localeCompare(b[orderBy])); } else if (orderBy === "age") { return arr.sort((a, b) => a[orderBy] - b[orderBy]); } else if (orderBy === "isAlive") { return arr.sort(value => value[orderBy] ? -1 : 1) } else { return []; } } const byAlive = myFn(myArr, "isAlive"); console.log(byAlive) const byAge = myFn(myArr, "age"); console.log(byName) // 此处存在笔误:byName未声明就调用 const byName = myFn(myArr, "name"); console.log(byName)
报错原因
TypeScript的控制流类型收窄不会通过「对象属性值的类型」反向收窄「索引键的类型」:当判断typeof a[orderBy] === 'string'时,TS仅能确定当前a[orderBy]这个值是string类型,不会把orderBy的类型从整体的keyof Person收窄到对应string类型的键'name',因此后续访问b[orderBy]时,TS仍会将其识别为string | number | boolean联合类型,调用string专属的localeCompare方法就会报类型错误。
可扩展实现方案
通过泛型+通用比较器的写法实现,和具体业务接口解耦,后续新增同类型字段无需修改排序逻辑:
// 通用类型比较器:根据传入值的类型返回对应排序规则,后续需要支持新类型(如Date)只需在此处新增判断 function getComparator<T>(value: T): (a: T, b: T) => number { switch (typeof value) { case 'string': return (a, b) => (a as string).localeCompare(b as string) case 'number': return (a, b) => (a as number) - (b as number) case 'boolean': return (a, b) => a === b ? 0 : a ? -1 : 1 default: // 未覆盖的类型默认保持原顺序 return () => 0 } } /** * 通用对象数组排序函数 * @param arr 待排序数组 * @param sortKey 排序依据的键名 * @returns 排序后的新数组(不修改原数组) */ function sortByKey<T, K extends keyof T>(arr: T[], sortKey: K): T[] { if (arr.length === 0) return [] const comparator = getComparator(arr[0][sortKey]) // 浅拷贝后排序,避免Array.sort原地修改原数组 return [...arr].sort((prev, curr) => comparator(prev[sortKey], curr[sortKey])) } // 业务使用示例 interface Person{ name: string, age: number, isAlive: boolean // 后续新增同类型字段无需修改排序函数,比如新增id: number、address: string都可直接使用 } const myArr: Person[] = [ {name: 'Tomás', age: 24, isAlive: true}, {name: 'Kate', age: 12, isAlive: true}, {name: 'Brad', age: 54, isAlive: false}, {name: 'Angelica', age: 64, isAlive: true}, ] const byAlive = sortByKey(myArr, "isAlive"); console.log(byAlive) const byAge = sortByKey(myArr, "age"); console.log(byAge) const byName = sortByKey(myArr, "name"); console.log(byName)
方案优势
- 无硬编码字段判断,扩展性强:给接口新增string/number/boolean类型的字段时,直接传入键名即可使用,不需要修改排序函数逻辑
- 类型安全:TS会自动校验传入的键名是否属于数组元素的合法属性,传错键名会直接抛类型错误
- 通用性强:不与
Person接口强绑定,任意结构的对象数组都可复用该排序方法 - 无副作用:排序前先做浅拷贝,不会修改原数组
内容的提问来源于stack exchange,提问作者user2444383
相关产品推荐
相关产品推荐

