1. Введение
вместе сTypescript 4 BetaВыпуск , принес много новых функций, среди которых Variadic Tuple Types решает упорную проблему большого количества перегруженного кода шаблона, что делает это обновление очень важным.
2 Введение
изменяемый тип кортежа
рассмотреть возможностьconcatСценарий, получите два типа массива или кортежа и сформируйте новый массив:
function concat(arr1, arr2) {
return [...arr1, ...arr2];
}
Если вы хотите определитьconcatРаньше мы перечисляли каждый элемент в первом массиве параметров, перечисляя:
function concat<>(arr1: [], arr2: []): [A];
function concat<A>(arr1: [A], arr2: []): [A];
function concat<A, B>(arr1: [A, B], arr2: []): [A, B];
function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];
function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];
function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];)
Затем перечислите каждый элемент во втором параметре.Если вы хотите выполнить все перечисления, только учитывая случай, когда длина массива равна 6, вам нужно определить 36 перегрузок, и код почти неподдерживаемый:
function concat<A2>(arr1: [], arr2: [A2]): [A2];
function concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2];
function concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];
function concat<A1, B1, C1, A2>(
arr1: [A1, B1, C1],
arr2: [A2]
): [A1, B1, C1, A2];
function concat<A1, B1, C1, D1, A2>(
arr1: [A1, B1, C1, D1],
arr2: [A2]
): [A1, B1, C1, D1, A2];
function concat<A1, B1, C1, D1, E1, A2>(
arr1: [A1, B1, C1, D1, E1],
arr2: [A2]
): [A1, B1, C1, D1, E1, A2];
function concat<A1, B1, C1, D1, E1, F1, A2>(
arr1: [A1, B1, C1, D1, E1, F1],
arr2: [A2]
): [A1, B1, C1, D1, E1, F1, A2];
Если мы пойдем с пакетным определением, проблема не будет решена, потому что порядок типов параметров не гарантируется:
function concat<T, U>(arr1: T[], arr2, U[]): Array<T | U>;
В Typescript 4 массивы могут быть деструктурированы в определении, элегантно решая сценарии, которые могут потребовать сотен перезагрузок с помощью нескольких строк кода:
type Arr = readonly any[];
function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
return [...arr1, ...arr2];
}
В приведенном выше примереArrТип говорит ТСTа такжеUявляется типом массива, а затем передать[...T, ...U]Объедините типы последовательно в логическом порядке.
Другой примерtail, который возвращает остальные элементы, кроме первого:
function tail(arg) {
const [_, ...result] = arg;
return result;
}
Также скажите ТСTявляется типом массива иarr: readonly [any, ...T]подтвержденныйTType указывает тип остальных элементов, кроме первого элемента, TS может автоматическиTТипы связаны с объектамиrest:
function tail<T extends any[]>(arr: readonly [any, ...T]) {
const [_ignored, ...rest] = arr;
return rest;
}
const myTuple = [1, 2, 3, 4] as const;
const myArray = ["hello", "world"];
// type [2, 3, 4]
const r1 = tail(myTuple);
// type [2, 3, ...string[]]
const r2 = tail([...myTuple, ...myArray] as const);
В дополнение к предыдущей версии ТС только тип деконструкции в последней позиции:
type Strings = [string, string];
type Numbers = [number, number];
// [string, string, number, number]
type StrStrNumNum = [...Strings, ...Numbers];
Если вы попытаетесь поставить[...Strings, ...Numbers]При таком способе написания вы получите сообщение об ошибке:
A rest element must be last in a tuple type.
Но этот синтаксис поддерживается в Typescript 4:
type Strings = [string, string];
type Numbers = number[];
// [string, string, ...Array<number | boolean>]
type Unbounded = [...Strings, ...Numbers, boolean];
Для более сложных сценариев, таких как функции высшего порядкаpartialCall, который поддерживает некоторую степень каррирования:
function partialCall(f, ...headArgs) {
return (...tailArgs) => f(...headArgs, ...tailArgs);
}
Мы можем определить их по указанным выше характеристикам, будет функционироватьfПервый тип параметра определен как упорядоченный[...T, ...U]:
type Arr = readonly unknown[];
function partialCall<T extends Arr, U extends Arr, R>(
f: (...args: [...T, ...U]) => R,
...headArgs: T
) {
return (...b: U) => f(...headArgs, ...b);
}
Результаты теста следующие:
const foo = (x: string, y: number, z: boolean) => {};
// This doesn't work because we're feeding in the wrong type for 'x'.
const f1 = partialCall(foo, 100);
// ~~~
// error! Argument of type 'number' is not assignable to parameter of type 'string'.
// This doesn't work because we're passing in too many arguments.
const f2 = partialCall(foo, "hello", 100, true, "oops");
// ~~~~~~
// error! Expected 4 arguments, but got 5.
// This works! It has the type '(y: number, z: boolean) => void'
const f3 = partialCall(foo, "hello");
// What can we do with f3 now?
f3(123, true); // works!
f3();
// error! Expected 2 arguments, but got 0.
f3(123, "hello");
// ~~~~~~~
// error! Argument of type '"hello"' is not assignable to parameter of type 'boolean'
Примечательно,const f3 = partialCall(foo, "hello");Так как этот код еще не был выполненfoo, так что только первыйx:stringтипа, хотя послеy: number, z: booleanтакже требуется, но посколькуfooФункция еще не выполнена, это только этап сбора параметров, поэтому об ошибке не будет сообщено, подождите, покаf3(123, true)Требуемые параметры проверяются во время выполнения, поэтомуf3()Только когда число параметров неверно будет предложено.
тег кортежа
Следующие два определения функций функционально идентичны:
function foo(...args: [string, number]): void {
// ...
}
function foo(arg0: string, arg1: number): void {
// ...
}
Тем не менее, есть небольшая разница: приведенные ниже функции имеют теги имен для каждого параметра, но типы, определенные с помощью деструктуризации выше, не имеют, для которых Typescript 4 поддерживает теги кортежей:
type Range = [start: number, end: number];
Он также поддерживается для использования с деструктурированием:
type Foo = [first: number, second?: string, ...rest: any[]];
Класс выводит тип переменной-члена из конструктора
Конструктор отвечает за некоторую работу по инициализации при создании экземпляра класса, например, присвоение значений переменным-членам.В Typescript 4 назначение переменных-членов в конструкторе может напрямую вывести тип для переменных-членов:
class Square {
// Previously: implicit any!
// Now: inferred to `number`!
area;
sideLength;
constructor(sideLength: number) {
this.sideLength = sideLength;
this.area = sideLength ** 2;
}
}
Если присвоения переменных-членов включены в условный оператор, также возможно определить существованиеundefinedриски:
class Square {
sideLength;
constructor(sideLength: number) {
if (Math.random()) {
this.sideLength = sideLength;
}
}
get area() {
return this.sideLength ** 2;
// ~~~~~~~~~~~~~~~
// error! Object is possibly 'undefined'.
}
}
Если он инициализирован в других функциях, TS не может быть распознан автоматически, и вам нужно использовать!:Явно объявить тип:
class Square {
// definite assignment assertion
// v
sideLength!: number;
// ^^^^^^^^
// type annotation
constructor(sideLength: number) {
this.initialize(sideLength);
}
initialize(sideLength: number) {
this.sideLength = sideLength;
}
get area() {
return this.sideLength ** 2;
}
}
Синтаксис назначения короткого замыкания
Синтаксисы назначения ярлыков предусмотрены для следующих трех синтаксисов короткого замыкания:
a &&= b; // a && (a = b)
a ||= b; // a || (a = b)
a ??= b; // a ?? (a = b)
поймать ошибку неизвестного типа
После Typescript 4.0 мы можем определить ошибку отлова какunknowntype, чтобы гарантировать, что следующий код написан в надежной манере суждения о типах:
try {
// ...
} catch (e) {
// error!
// Property 'toUpperCase' does not exist on type 'unknown'.
console.log(e.toUpperCase());
if (typeof e === "string") {
// works!
// We've narrowed 'e' down to the type 'string'.
console.log(e.toUpperCase());
}
}
P.S. В предыдущих версияхcatch (e: unknown)Будет выдано сообщение об ошибке, указывающее на то, чтоerrorопределениеunknownТипы.
Пользовательская фабрика JSX
ТС 4 поддерживаетjsxFragmentFactoryПараметры определяют функцию фабрики фрагментов:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
Вы также можете переопределить конфигурацию одного файла, закомментировав:
// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";
let stuff = (
<>
<div>Hello</div>
</>
);
После компиляции приведенного выше кода результат парсинга выглядит следующим образом:
// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";
let stuff = h(Fragment, null, h("div", null, "Hello"));
другие обновления
Краткое введение в другие обновления:
Улучшения скорости сборки, продвинутый--incremental + --noEmitOnErrorСкорость построения сцены.
служба поддержки--incremental + --noEmitПараметры вступают в силу одновременно.
служба поддержки@deprecatedПримечание,При использовании этой аннотации код используетзачеркнутыйПредупредите звонящего.
Функция быстрого запуска Local TS Server,При открытии большого проекта TS Server долго готовится, Typescript 4 оптимизирован под компилятор VSCode, который может заранее реагировать на частичный синтаксис открытого в данный момент отдельного файла.
Оптимизирован автоматический импорт,Сейчасpackage.json dependenciesЗависимости определения поля будут использоваться в качестве основы для автоматического импорта вместо обходаnode_modulesИмпортируйте некоторые неожиданные пакеты.
В дополнение к этому, есть несколько Break Changes:
lib.d.tsУлучшения типа, в основном удаленыdocument.originопределение.
Геттеры или сеттеры, переопределяющие свойство родительского класса, теперь будут вызывать ошибку.
пройти черезdeleteУдаленный атрибут должен быть необязательным, если вы пытаетесь использоватьdeleteУдаление требуемого ключа вызовет ошибку.
3 Интенсивное чтение
Самой большой изюминкой Typescript 4 является изменяемый тип кортежа, но изменяемые типы кортежей не могут решить все проблемы.
Возьмем в качестве примера авторский сценарий, функцияuseDesignerкак пользовательский React Hook сuseSelectorВ сочетании со значениями, поддерживающими поток данных connect redux, он вызывается так:
const nameSelector = (state: any) => ({
name: state.name as string,
});
const ageSelector = (state: any) => ({
age: state.age as number,
});
const App = () => {
const { name, age } = useDesigner(nameSelector, ageSelector);
};
nameа такжеageОн регистрируется Selector, и внутренняя реализация должна бытьuseSelector+ уменьшить, но определение типа громоздкое, это можно сделать перегрузив:
import * as React from 'react';
import { useSelector } from 'react-redux';
type Function = (...args: any) => any;
export function useDesigner();
export function useDesigner<T1 extends Function>(
t1: T1
): ReturnType<T1> ;
export function useDesigner<T1 extends Function, T2 extends Function>(
t1: T1,
t2: T2
): ReturnType<T1> & ReturnType<T2> ;
export function useDesigner<
T1 extends Function,
T2 extends Function,
T3 extends Function
>(
t1: T1,
t2: T2,
t3: T3,
t4: T4,
): ReturnType<T1> &
ReturnType<T2> &
ReturnType<T3> &
ReturnType<T4> &
;
export function useDesigner<
T1 extends Function,
T2 extends Function,
T3 extends Function,
T4 extends Function
>(
t1: T1,
t2: T2,
t3: T3,
t4: T4
): ReturnType<T1> &
ReturnType<T2> &
ReturnType<T3> &
ReturnType<T4> &
;
export function useDesigner(...selectors: any[]) {
return useSelector((state) =>
selectors.reduce((selected, selector) => {
return {
...selected,
...selector(state),
};
}, {})
) as any;
}
Видно, что автору нужноuseDesignerВходящие параметры передаются один за другим через перегрузку функции. В приведенном выше примере поддерживаются только три параметра. Если передается четвертый параметр, определение функции будет недействительным. Поэтому в отрасли обычно определяется более дюжины перегрузок. Это может привести к очень подробным определениям функций.
Но ссылаясь на пример TS4, мы можем избежать перегрузки типов и поддерживать ее через перечисление:
type Func = (state?: any) => any;
type Arr = readonly Func[];
const useDesigner = <T extends Arr>(
...selectors: T
): ReturnType<T[0]> &
ReturnType<T[1]> &
ReturnType<T[2]> &
ReturnType<T[3]> => {
return useSelector((state) =>
selectors.reduce((selected, selector) => {
return {
...selected,
...selector(state),
};
}, {})
) as any;
};
Видно, что самое большое изменение в том, что нет необходимости писать четыре перегрузки, а за счет сцены иconcatДругое, возвращаемое значение этого примера не простое[...T, ...U], ноreduceРезультат, так что в настоящее время он может поддерживаться только перечислением.
Конечно, могут быть решения, которые могут поддерживать анализ типов входных параметров бесконечной длины без перечисления.Из-за ограниченного уровня автора я не придумал лучшего решения.Если у вас есть лучшее решение, сообщите, пожалуйста, автору .
4 Резюме
Typescript 4 обеспечивает более сильный синтаксис типов, более умный вывод типов, более быструю скорость сборки и более разумную оптимизацию инструментов разработчика.Только несколько Break Changes не окажут существенного влияния на проект, и я с нетерпением жду выпуска официальной версии.
Адрес обсуждения:Интенсивное чтение машинописного текста 4 · Выпуск №259 · dt-fe/weekly
Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.
Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе
Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)