如何使用可覆盖列表类型实现自定义 class?

How can I implement custom class with overridable list type?

我有一个使用 next v10.1.2 和 Typescript v4.4.4 的 Nextjs 项目 我想要一个封装自定义类型列表的 class SortableList。唯一的要求是列表扩展了我的摘要 class OrderedItem.

目前,当我尝试访问从新的 SortableList 对象扩展 OrderedItem 的 class 上的 属性 时出现错误。

这在 Typescript 中可行吗?正确的设置方法是什么?

代码:

OrderedItem.ts

// The purpose of this class is to ensure that items in a list can be ordered.
abstract class OrderedItem {
  order: number

  constructor(order: number) {
    this.order = order
  }
}

export default OrderedItem

CustomListItem.ts

import OrderedItem from './OrderedItem'

// This is an example of a class that I would like to be orderable.
class CustomListItem extends OrderedItem {
  name: string
  jsx: JSX.Element

  constructor(order: number, name: string, jsx: JSX.Element) {
    super(order)
    this.name = name
    this.jsx = jsx
  }
}

export default CustomListItem

SortableList.ts

import OrderedItem from './OrderedItem'

class SortableList {
  // I want this array to be overridden by a custom class like CustomListItem
  list: OrderedItem[]

  constructor(list: OrderedItem[]) {
    this.list = list
  }

  sortByOrder = (): void => {
    this.list = this.list.sort((a, b) => a.order - b.order)
  }

  sortByReverseOrder = (): void => {
    this.list = this.list.sort((a, b) => b.order - a.order)
  }
}

export default SortableList

example.tsx

import CustomListItem from './CustomListItem'
import SortableList from './SortableList'

export default function Example() {
  const customList: CustomListItem[] = [
    new CustomListItem(3, 'name3', <div></div>),
    new CustomListItem(1, 'name1', <div></div>),
    new CustomListItem(2, 'name2', <div></div>),
  ]
  const sortableList: SortableList = new SortableList(customList)
  sortableList.sortByOrder()

  return sortableList.list.map(item => item.jsx)
}

我从这个例子中得到的错误是在 example.tsx 的 return 语句中尝试访问 item 上的 jsx 属性 因为项目输入为 OrderedItem 而不是 CustomListItem.

错误:属性 'jsx' 在类型 'OrderedItem' 上不存在。

正确的做法是什么?我试着用 <T> 声明 SortableList 并像

一样初始化它

const sortableList = new SortableList<CustomListItem>(customList),但我不熟悉它的工作原理。

问题是当您访问 SortableList 的元素时,编译器只知道它是一个 OrderedItem,没有 属性 jsx.

主要有两种解决方法:

  1. 您知道您的项目将始终属于某种类型(就像您的情况一样)并将其转换为该类型。易于实现,但我真的警告你这不是一个好主意,因为你将打破对象编程原则,而且这可能导致很难调试的运行时错误,例如你放置了一个不是列表中的 CustomListItem,没有 属性 jsx。这是如何完成的:
sortableList.list.map(item => (item as CustomListItem).jsx)
  1. 正式的方式是使用泛型。泛型只是代码中类型的占位符,当您使用它时,它将被指定的类型替换。在您的情况下,您还需要对该泛型进行约束,强制实现指定的类型扩展您的 OrderedItem class。通过这种方式,您不会遇到意外错误的实际情况,因为 ts 编译器通过检查您的类型来阻止它们。这就是你的情况:

SortableList.ts

import OrderedItem from './OrderedItem'

// T is the generic
// T extends OrderedItem is the constraint
class SortableList<T extends OrderedItem> {
  // this array can contains only elements
  // that are descendant of T
  // T is always a descendant of OrderedItem
  list: T[]

  constructor(list: T[]) {
    this.list = list
  }

  sortByOrder = (): void => {
    this.list = this.list.sort((a, b) => a.order - b.order)
  }

  sortByReverseOrder = (): void => {
    this.list = this.list.sort((a, b) => b.order - a.order)
  }
}

export default SortableList

example.tsx

import CustomListItem from './CustomListItem'
import SortableList from './SortableList'

export default function Example() {
  const customList: CustomListItem[] = [
    new CustomListItem(3, 'name3', <div></div>),
    new CustomListItem(1, 'name1', <div></div>),
    new CustomListItem(2, 'name2', <div></div>),
  ]
  const sortableList: SortableList<CustomListItem> =
         new SortableList<CustomListItem>(customList)
  sortableList.sortByOrder()

  return sortableList.list.map(item => item.jsx)
}