Уважно подивіться на наведену нижче TypeScript функцію з одного рядку коду. В ній є проблема пов’язана з типами. Чи можете ви її побачити?
Пропоную просто зараз зупинитись та запропонувати вашу версію в коментарях.
Перша підказка
// Завантажуємо массив уявних записів із бази даних
const posts = await loadPostsFromDataBase();
// Хочемо отримати кількість доступних записів
// Але отримуємо TS2345 помилку від TypeScript
const postsCount = getArraylength(posts);
Наведений вище код призводить до помилки невідповідності типу аргументу posts до типу параметра arr. За яких умов це можливо, якщо loadPostsFromDataBase працює абсолютно коректно, а проблема саме в getArraylength?
Здогадались?
Остання підказка
В якості останньої підказки покажу тіло функції loadPostsFromDataBase:
function loadPostsFromDataBase(): readonly Post[] {
return fetch('/api/posts')
.then(r => r.json())
.then(posts => Object.freeze(posts))
}
Відповідь
За замовчуванням всі типи в TS є мутабельними. Тобто, оголошуючи функцію з параметрами як тут:
function getArraylength(arr: unknown[]): number {
return arr.length
}
Ви кажете “Ця функція потребує мутабельний масив та повертає число”.
Бачте, TypeScript майже не зважає на імплементацію вашої функції, він не бере до уваги чи справді ви змінюєте масив, чи лише читаєте його.
Тому, ви отримаєте помилку, якщо спробуєте передати аргументом readonly масив, не зважаючи на те, що насправді з точки зору JavaScript код абсолютно вірний.
const posts = await loadPostsFromDataBase(); // readonly Posts[]
const postsCount = getArraylength(posts); // ❌ The type 'readonly Post[]' is 'readonly' and cannot be assigned to the mutable type 'unknown[]'
Рішення
Ото ж, у функції не правильно вказано тип для параметра. Як же тоді цю проблему вирішити?
Все просто. Якщо ваша функція, не змінює вхідні параметри, а це хороший патерн проєктування, тоді типи ваших параметрів повинні бути readonly:
function getArraylength(arr: readonly unknown[]): number {
return arr.length
}
Така сигнатура каже, “Мені потрібен масив, який я буду лише читати”. І в цьому випадку, TypeScript дозволить передавати параметром як звичайні змінні, так і readonly:
const posts = []
const readOnlyPosts: readonly [] = []
getArraylength(posts) // ✅
getArraylength(readOnlyPosts) // ✅