Логический вывод типа в TypeScript¶
TypeScript может логически вывести (а затем проверить) тип переменной на основе нескольких простых правил. Потому как эти правила просты, вы можете научить свой мозг распознавать безопасный / небезопасный код (это случилось со мной и моими товарищами по команде довольно быстро).
Поток типов - это то, как я представляю себе в уме распространение информации о типах.
Определение переменной¶
Типы переменной определяются по её определению.
let foo = 123; // foo `число`
let bar = 'Hello'; // bar `строка`
foo = bar; // Ошибка: невозможно `строке` присвоить `число`
Это пример типов, распространяющихся справа налево.
Типы значений возвращаемых функцией¶
Тип возвращаемого значения определяется инструкцией возврата, например, предполагается, что следующая функция возвращает число
.
function add(a: number, b: number) {
return a + b;
}
Это пример типов, распространяющихся снизу вверх.
Присвоение¶
Тип параметров функции / возвращаемых значений также может быть определен посредством присваивания, например, здесь мы говорим, что foo
является сумматором
, и это делает a
и b
типом число
.
type Adder = (a: number, b: number) => number;
let foo: Adder = (a, b) => a + b;
Этот факт может быть продемонстрирован с помощью приведенного ниже кода, который вызывает ошибку, как можно было и ожидать:
type Adder = (a: number, b: number) => number;
let foo: Adder = (a, b) => {
a = 'hello'; // Ошибка: невозможно `строке` присвоить `число`
return a + b;
};
Это пример типов, распространяющихся слева направо.
Логический вывод типов срабатывает с этим же стилем присвоения, если вы создаете функцию с параметром в виде колбэка. В конце концов, argument -> parameter
- это просто еще одна форма присвоения переменных.
type Adder = (a: number, b: number) => number;
function iTakeAnAdder(adder: Adder) {
return adder(1, 2);
}
iTakeAnAdder((a, b) => {
// a = "hello"; // Будет ошибка: невозможно `строке` присвоить `число`
return a + b;
});
Структурирование¶
Эти простые правила также работают при использовании структурирования (создание литерала объекта). Например, в следующем случае тип foo
определяется как {a:number, b:number}
let foo = {
a: 123,
b: 456,
};
// foo.a = "hello"; // Будет ошибка: невозможно `строке` присвоить `число`
Аналогично для массивов:
const bar = [1, 2, 3];
// bar[0] = "hello"; // Будет ошибка: невозможно `строке` присвоить `число`
Ну и конечно же любое вложение:
let foo = {
bar: [1, 3, 4],
};
// foo.bar[0] = 'hello'; // Будет ошибка: невозможно `строке` присвоить `число`
Деструктуризация¶
И, конечно же, они также работают с деструктуризацией, оба:
let foo = {
a: 123,
b: 456,
};
let { a } = foo;
// a = "hello"; // Будет ошибка: невозможно `строке` присвоить `число`
и массивы:
const bar = [1, 2];
let [a, b] = bar;
// a = "hello"; // Будет ошибка: невозможно `строке` присвоить `число`
И если параметр функции может быть логически выведен, то могут и его деструктурированные свойства. Например, здесь мы деструктурируем параметр на a
/b
.
type Adder = (numbers: { a: number; b: number }) => number;
function iTakeAnAdder(adder: Adder) {
return adder({ a: 1, b: 2 });
}
iTakeAnAdder(({ a, b }) => {
// Типы `a` и` b` логически выводятся
// a = "hello"; // Будет ошибка: невозможно `строке` присвоить `число`
return a + b;
});
Защита типа¶
Мы уже видели, как Защита типа помогает изменять и уточнять типы (особенно в случае объединений). Защиты типов - это просто еще одна форма логического вывода типа для переменной в блоке.
Предупреждения¶
Будьте осторожны с параметрами¶
Типы не распространяются в параметры функции, если они не могут быть логически выведены из присвоения. Например, в следующем случае компилятор не знает тип foo
, поэтому он не может определить тип a
или b
.
const foo = (a, b) => {
/* сделать что-нибудь */
};
Однако, если был введен foo
, тип параметров функции может быть логически выведен (a
,b
оба выведены как имеющие тип number
в примере ниже).
type TwoNumberFunction = (a: number, b: number) => void;
const foo: TwoNumberFunction = (a, b) => {
/* сделать что-нибудь */
};
Будьте осторожны с возвращаемыми значениями¶
Хотя TypeScript может обычно логически выводить тип возвращаемого значения функции, он может не соответствовать ожидаемому. Например, здесь функция foo
имеет тип возврата any
.
function foo(a: number, b: number) {
return a + addOne(b);
}
// Какая-то внешняя функция в библиотеке, которую кто-то написал в JavaScript
function addOne(c) {
return c + 1;
}
Это связано с тем, что на возвращаемый тип влияет плохое определение типа для addOne
(c
равно any
, поэтому возвращаемое от addIn
равно any
, поэтому отсюда и foo
равно any
).
Я считаю, что проще всего всегда быть явным в описании возвращаемых значений функций. Ведь эти описания являются теоремой, а тело функции является доказательством.
Есть и другие случаи, которые нетрудно себе представить, но хорошая новость заключается в том, что есть флаг компилятора, который может помочь отловить такие ошибки.
noImplicitAny
¶
Флаг noImplicitAny
указывает компилятору выдавать ошибку, если он не может определить тип переменной (и, следовательно, может иметь ее только как неявный any
тип). Далее вы сможете
- либо сказать, что да, я хочу, чтобы это было типом
any
и явно добавить описание типа: any
- либо помочь компилятору, добавив еще несколько правильных описаний.