Почему typescript не может поддерживать типы в Array.prototype.filter()?

#typescript

Вопрос:

Следующий код подходит, но если вы раскомментируете последнюю строку, ts выдаст ошибку. Похоже на то .filter() отбрасывает уже выведенный тип из предыдущего шага, который для меня не имеет смысла. Пожалуйста, объясните, как здесь работает ts, и предложите решение.

 type ItemIds =
    | 'foo'
    | 'bar';

interface Item {
    id: ItemIds;
};

const items: Item[] = [
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
]
//.filter((item) => true)
 

https://www.typescriptlang.org/play?#code/C4TwDgpgBAksEFsYBMDOUC8AoKuoB8oByAMwHsyic9CiAjAQwCciBuLLASwDt4mSGAY2hxEUAN7VcnZAC5Y8JGnYBfdlkFluqYFE6LU80QgDaAXUxQTUiTbx65xcpQA0NlW-uT79mfPrMRJ54KlhmWAD0EVAAdCScADZ8ABTJ ogAlJgAfFDATACuEBlAA

Ответ №1:

Когда вы назначаете массив литералов items , даже если тип литерала {id: string;}[] (не [id: "foo" | "bar";]{} aka Item[] ), TypeScript может видеть, что все id значения вписываются в "foo" | "bar" него, поэтому он позволяет выполнить назначение.

Однако, если вы вызовете .filter этот массив, он больше не сможет этого делать. Тип массива таков {id: string;}[] , так что это результат filter . Он не может знать, что filter это не изменяет id значения, он просто знает, что типы, для filter которых он говорит, вернут массив того же типа. То есть {id: string;}[] , не {id: "foo" | "bar";}[] ( Item[] ).

Если вы убедитесь , что массив, к которому вы обращаетесь filter , имеет соответствующий тип Item[] , он будет работать:

 const items = ([
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
] as Item[])
.filter((item) => true);
 

Ссылка на игровую площадку

Ответ №2:

Это сохранит статическую типизацию , если вы объявите свой начальный массив Item[] , например.:

 const items: Item[] = [
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
]

const filteredItems: Item[] = items.filter(item => true)
 

Проблема связана с типом объединения строк ItemIds , поскольку он интерпретируется так string , что вы не сообщаете компилятору об обратном.

Та же проблема возникает, если вы просто позволите компилятору items сначала определить тип, а затем переназначить его:

 // This wont compile either.
const items = [
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
]

const typedItems: Item[] = items
 

Комментарии:

1. Мне очень нравится ваш пример в конце, в котором вы подчеркиваете, что TypeScript может проверять, id соответствуют ли все буквы s типу, только когда у него есть литерал для работы.

Ответ №3:

Это потому, что

 [ { id: 'foo' }, { id: 'bar' }] 
 

все еще { id: string } [] остается к тому времени, когда вы отфильтруете.

К этому есть два подхода:

  1. процесс после
 const items: Item[] = [
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
]
const filtered = items.filter((item) => true)
 
  1. Отлитый до
 const items: Item[] = ([
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
] as Item[])
.filter((item) => true)
 

При втором подходе вам не нужно явно указывать тип, так как он уже будет подразумеваться

 const items = ([
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
] as Item[])
.filter((item) => true)
 

Ответ №4:

Вы должны сначала заявить items

 interface Item {
    id: ItemIds;
};

const items: Item[] = [
    {
        id: 'foo',
    },
    {
        id: 'bar',
    }
]
const filtered = items.filter((item) => true) // filtered has type Item[]
 

Комментарии:

1. Ну, вам и не нужно. 🙂 Также было бы неплохо сказать, почему возникает проблема.