Эта статья в основном знакомит с расширенным использованием TypeScript. Она подходит для студентов, которые уже знают TypeScript или фактически использовали его в течение определенного периода времени. В ней систематически представлены общие статьи о TypeScript с точки зрения типов, операторов, операторов и дженериков. Наконец, , поделюсь своим практическим опытом.
1. Тип
unknown
неизвестно относится кне предопределяемый тип, во многих случаях он может заменить функционал любого, сохранив при этом возможность статической проверки.
const num: number = 10;
(num as unknown as string).split(''); // 注意,这里和any一样完全可以通过静态检查
На данный момент роль unknown очень похожа на any, его можно преобразовать в любой тип, разница в том, что при статической компиляции unknown не может вызывать никакие методы, а any может.
const foo: unknown = 'string';
foo.substr(1); // Error: 静态检查不通过报错
const bar: any = 10;
any.substr(1); // Pass: any类型相当于放弃了静态检查
Один из вариантов использования unknown — избежать ошибок проверки статического типа, вызванных использованием any в качестве типа параметра функции:
function test(input: unknown): number {
if (Array.isArray(input)) {
return input.length; // Pass: 这个代码块中,类型守卫已经将input识别为array类型
}
return input.length; // Error: 这里的input还是unknown类型,静态检查报错。如果入参是any,则会放弃检查直接成功,带来报错风险
}
void
В TS void и undefined имеют очень похожие функции, которые логически могут избежать ошибок, вызванных случайным использованием нулевого указателя.
function foo() {} // 这个空函数没有返回任何值,返回类型缺省为void
const a = foo(); // 此时a的类型定义为void,你也不能调用a的任何属性方法
Самая большая разница между void и undefined заключается в том, что вы можете понять, что undefined является подмножеством void.Если вас не волнует возвращаемое значение функции, используйте void вместо undefined. Возьмем практический пример в React.
// Parent.tsx
function Parent(): JSX.Element {
const getValue = (): number => { return 2 }; /* 这里函数返回的是number类型 */
// const getValue = (): string => { return 'str' }; /* 这里函数返回的string类型,同样可以传给子属性 */
return <Child getValue={getValue} />
}
// Child.tsx
type Props = {
getValue: () => void; // 这里的void表示逻辑上不关注具体的返回值类型,number、string、undefined等都可以
}
function Child({ getValue }: Props) => <div>{getValue()}</div>
never
никогда не ссылается на тип, который не может быть возвращен нормально, и функция, которая должна сообщить об ошибке или бесконечном цикле, вернет такой тип.
function foo(): never { throw new Error('error message') } // throw error 返回值是never
function foo(): never { while(true){} } // 这个死循环的也会无法正常退出
function foo(): never { let count = 1; while(count){ count ++; } } // Error: 这个无法将返回值定义为never,因为无法在静态编译阶段直接识别出
Также есть типы, которые никогда не пересекаются:
type human = 'boy' & 'girl' // 这两个单独的字符串类型并不可能相交,故human为never类型
Но тип never для любого объединения типов по-прежнему остается исходным типом:
type language = 'ts' | never // language的类型还是'ts'类型
О никогда не имеет следующих характеристик:
- После вызова функции, которая никогда не возвращает значение в функции, следующий код станет
deadcode
function test() {
foo(); // 这里的foo指上面返回never的函数
console.log(111); // Error: 编译器报错,此行代码永远不会执行到
}
- Вы не можете присвоить другим типам значение never:
let n: never;
let o: any = {};
n = o; // Error: 不能把一个非never类型赋值给never类型,包括any
Есть несколько очень хакерских обычаев и дискуссий об этой особенности никогда, например, этот ZhihuОтвет Юйси.
Во-вторых, оператор
Оператор ненулевого утверждения !
Этот оператор можно использовать после имени переменной или функции, чтобы подчеркнуть, что соответствующий элемент не равен null|undefined
function onClick(callback?: () => void) {
callback!(); // 参数是可选入参,加了这个感叹号!之后,TS编译不报错
}
Вы можете посмотреть на скомпилированный код ES5, не делая никаких суждений о ПВО.
function onClick(callback) {
callback();
}
Сценарий с этим символом особенно подходит для сценариев, в которых мы уже знаем, что не вернём нулевое значение, тем самым уменьшая избыточные оценки кода, такие как React Ref.
function Demo(): JSX.Elememt {
const divRef = useRef<HTMLDivElement>();
useEffect(() => {
divRef.current!.scrollIntoView(); // 当组件Mount后才会触发useEffect,故current一定是有值的
}, []);
return <div ref={divRef}>Demo</div>
}
Необязательный оператор цепочки ?.
По сравнению с вышеизложенным!Непустое суждение, действующее на этапе компиляции,?.
Это ненулевая оценка, которая больше всего нужна разработчикам во время выполнения (конечно, она также действительна во время компиляции).
obj?.prop obj?.[index] func?.(args)
?. используется, чтобы определить, является ли выражение слева null | undefined, если да, то оно остановит выполнение выражения, что может уменьшить большое количество операций &&.
Например, мы пишемa?.b
, компилятор автоматически сгенерирует следующий код
a === null || a === void 0 ? void 0 : a.b;
Вот небольшой пункт знаний:undefined
Это значение будет переназначено в нестрогом режиме, используйтеvoid 0
Должен возвращать значение true undefined.
Нулевой оператор объединения ??
Функции ?? и || похожи, разница в том, что??Правое выражение будет возвращено только в том случае, если результат левого выражения равен нулю или не определен..
Например, мы написалиlet b = a ?? 10
, сгенерированный код выглядит следующим образом:
let b = a !== null && a !== void 0 ? a : 10;
Выражение ||, как мы все знаем, будет действовать и на логические нулевые значения, такие как false, '', NaN, 0 и т. д. Нам не подходит объединение параметров.
Разделитель чисел_
let num:number = 1_2_345.6_78_9
_ может использоваться для произвольного разделения длинных чисел. Основная конструкция заключается в облегчении чтения чисел. В скомпилированном коде нет подчеркивания, пожалуйста, ешьте его с уверенностью.
3. Оператор
Получить значение ключа keyof
keyof может получить все ключевые значения типа и вернуть тип объединения следующим образом:
type Person = {
name: string;
age: number;
}
type PersonKey = keyof Person; // PersonKey得到的类型为 'name' | 'age'
Обычно keyof используется для ограничения доступа к легализации ключа объекта, поскольку любое индексирование неприемлемо.
function getValue (p: Person, k: keyof Person) {
return p[k]; // 如果k不如此定义,则无法以p[k]的代码格式通过编译
}
Подводя итог, синтаксис keyof выглядит следующим образом
类型 = keyof 类型
Тип экземпляра получает typeof
typeof должен получить тип объекта/экземпляра следующим образом:
const me: Person = { name: 'gzx', age: 16 };
type P = typeof me; // { name: string, age: number | undefined }
const you: typeof me = { name: 'mabaoguo', age: 69 } // 可以通过编译
typeof можно использовать только для определенных объектов, что согласуется с typeof в js, и он автоматически решает, какое поведение следует выполнять в соответствии с левым значением.
const typestr = typeof me; // typestr的值为"object"
typeof можно использовать с keyof (поскольку typeof возвращает тип) следующим образом:
type PersonKey = keyof typeof me; // 'name' | 'age'
Подводя итог, синтаксис typeof выглядит следующим образом:
类型 = typeof 实例对象
перебирать свойства в
in может использоваться только в определении типа и может проходить по типу перечисления следующим образом:
// 这个类型可以将任何类型的键值转化成number类型
type TypeToNumber<T> = {
[key in keyof T]: number
}
keyof
Возвращает все ключевые типы перечисления универсального T ,key
любое имя пользовательской переменной, используемое в серединеin
ссылка, периферия[]
Заверните его (это фиксированное совпадение), справа от двоеточия.number
положить всеkey
определяется какnumber
тип.
Таким образом, его можно использовать следующим образом:
const obj: TypeToNumber<Person> = { name: 10, age: 10 }
Подводя итог, синтаксис in выглядит следующим образом:
[ 自定义变量名 in 枚举类型 ]: 类型
4. Общий
Можно сказать, что дженерики являются очень важным атрибутом TS.Они переносят мост от статического определения к динамическому вызову, а также являются метапрограммированием TS для определения собственного типа. Можно сказать, что дженерики являются сущностью инструментов типа TS, а также самой сложной частью всего TS для изучения.
основное использование
Обобщения можно использовать в определениях общих типов, определениях классов и определениях функций следующим образом:
// 普通类型定义
type Dog<T> = { name: string, type: T }
// 普通类型使用
const dog: Dog<number> = { name: 'ww', type: 20 }
// 类定义
class Cat<T> {
private type: T;
constructor(type: T) { this.type = type; }
}
// 类使用
const cat: Cat<number> = new Cat<number>(20); // 或简写 const cat = new Cat(20)
// 函数定义
function swipe<T, U>(value: [T, U]): [U, T] {
return [value[1], value[0]];
}
// 函数使用
swipe<Cat<number>, Dog<number>>([cat, dog]) // 或简写 swipe([cat, dog])
Обратите внимание, что если универсальный тип определен для имени типа, универсальный тип также должен быть записан при использовании имени типа.
Для переменной, если ее тип может быть выведен во время вызова, обобщенная запись может быть опущена.
Синтаксис дженериков кратко резюмируется следующим образом:
类型名<泛型列表> 具体类型定义
Общий вывод и значения по умолчанию
Как упоминалось выше, мы можем упростить написание определений универсального типа, потому что TS будет автоматически выводить тип переменной в соответствии с типом определения переменной, что обычно происходит в случае вызовов функций.
type Dog<T> = { name: string, type: T }
function adopt<T>(dog: Dog<T>) { return dog };
const dog = { name: 'ww', type: 'hsq' }; // 这里按照Dog类型的定义一个type为string的对象
adopt(dog); // Pass: 函数会根据入参类型推断出type为string
Если функциональная обобщенная дедукция неприменима, мы должны указать универсальный тип, если нам нужно определить тип переменной.
const dog: Dog<string> = { name: 'ww', type: 'hsq' } // 不可省略<string>这部分
Если мы хотим не указывать, мы можем использовать схему общих значений по умолчанию.
type Dog<T = any> = { name: string, type: T }
const dog: Dog = { name: 'ww', type: 'hsq' }
dog.type = 123; // 不过这样type类型就是any了,无法自动推导出来,失去了泛型的意义
Формат синтаксиса общих значений по умолчанию кратко резюмируется следующим образом:
泛型名 = 默认类型
общие ограничения
Иногда мы можем игнорировать определенные типы дженериков, например:
function fill<T>(length: number, value: T): T[] {
return new Array(length).fill(value);
}
Эта функция принимает параметр длины и значение по умолчанию, а результатом является массив, заполненный соответствующим числом со значением по умолчанию. Нам не нужно оценивать входящие параметры, просто заполните их напрямую, но иногда нам нужно ограничить тип, в этом случае используйтеextends
ключевые слова подойдут.
function sum<T extends number>(value: T[]): number {
let count = 0;
value.forEach(v => count += v);
return count;
}
Таким образом, вы можетеsum([1,2,3])
Таким образом вызывается функция суммы, а что-то вродеsum(['1', '2'])
Это не может быть скомпилировано.
Общие ограничения также можно использовать в случае нескольких общих параметров.
function pick<T, U extends keyof T>(){};
Здесь это означает, что U должен быть подмножеством типа ключа T, который часто используется в некоторых универсальных библиотеках инструментов.
Формат синтаксиса расширений кратко описан ниже: Обратите внимание, что следующие типы могут быть либо общими, либо универсальными типами.
泛型名 extends 类型
Общее состояние
Как упоминалось выше, extends также можно использовать в качестве тернарного оператора, например:
T extends U? X: Y
Здесь нет ограничения, что T должен быть подтипом U. Если это подтип U, определите T как тип X, в противном случае определите его как тип Y.
Обратите внимание, что полученный результатРаспределительный.
Например, если мы заменим X на T следующим образом:T extends U? T: never
.
Возвращаемое в это время T — это та часть, которая удовлетворяет исходному T, содержащему U, что можно понимать как комбинацию T и U.перекресток.
Следовательно, формат синтаксиса расширений может быть расширен до
泛型名A extends 类型B ? 类型C: 类型D
общий вывод
Infer означает "вывод" на китайском языке. Он обычно используется с приведенными выше общими условными операторами. Так называемый вывод означает, что вам не нужно предварительно указывать в универсальном списке, он будет автоматически оцениваться во время выполнения, но вы сначала нужно предварительно определить общую структуру. Например
type Foo<T> = T extends {t: infer Test} ? Test: string
Предпочтительно смотреть на содержимое за расширениями,{t: infer Test}
можно рассматривать как содержащийt属性
изопределение типа,этоt属性
Тип значения передаетсяinfer
После вывода он будет присвоенTest
типа, если общий фактический параметр соответствует{t: infer Test}
затем возвращается определениеTest
тип, иначе по умолчанию по умолчаниюstring
тип.
Для лучшего понимания:
type One = Foo<number> // string,因为number不是一个包含t的对象类型
type Two = Foo<{t: boolean}> // boolean,因为泛型参数匹配上了,使用了infer对应的type
type Three = Foo<{a: number, t: () => void}> // () => void,泛型定义是参数的子集,同样适配
infer
Он используется для извлечения подтипов удовлетворяющих универсальным типам, и многие продвинутые универсальные инструменты также умело используют этот метод.
5. Общие инструменты
Partial<T>
Цель этого инструмента — сделать все свойства универсального типа необязательными.
type Partial<T> = {
[P in keyof T]?: T[P]
}
Например, это определение типа также используется ниже.
type Animal = {
name: string,
category: string,
age: number,
eat: () => number
}
Завершите его с помощью Partial.
type PartOfAnimal = Partial<Animal>;
const ww: PartOfAnimal = { name: 'ww' }; // 属性全部可选后,可以只赋值部分属性了
Record<K, T>
Роль этого инструмента заключается в преобразовании всех значений атрибутов из K в тип T, который мы часто используем для объявления общего объектного объекта.
type Record<K extends keyof any,T> = {
[key in K]: T
}
Здесь, в частности,keyof any
Соответствующий типnumber | string | symbol
, то есть набор типов, которые можно использовать в качестве ключей объекта (в профессиональном плане индекс).
Например:
const obj: Record<string, string> = { 'name': 'zhangsan', 'tag': '打工人' }
Pick<T, K>
Роль этого инструмента заключается в извлечении списка ключей K в типе T для создания нового типа пары «подключ-значение».
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
Мы по-прежнему используем вышеAnimal
Определение, посмотрите, как используется Pick.
const bird: Pick<Animal, "name" | "age"> = { name: 'bird', age: 1 }
Exclude<T, U>
Этот инструмент предназначен для удаления пересечения типа T и типа U в типе T и возврата оставшейся части.
type Exclude<T, U> = T extends U ? never : T
Обратите внимание, что T, возвращаемый здесь extends, является свойством, которое не имеет пересечения с U в исходном T, и любая комбинация свойств никогда не является собой, что можно найти выше.
Например
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
Omit<T, K>
Этот инструмент можно рассматривать как Exclude для объектов ключ-значение, который удаляет пары ключ-значение, содержащие K в типе T .
type Omit = Pick<T, Exclude<keyof T, K>>
В определении первым шагом является удаление ключа, перекрывающегося с K, из ключа T, а затем использование Pick для объединения типа T с оставшимися ключами.
Или используйте приведенное выше животное в качестве примера:
const OmitAnimal:Omit<Animal, 'name'|'age'> = { category: 'lion', eat: () => { console.log('eat') } }
Можно обнаружить, что результаты, полученные Omit и Pick, полностью противоположны, один является результатом взятия отрицания, а другой - результатом взятия пересечения.
ReturnType<T>
Этот инструмент предназначен для получения типа возвращаемого значения, соответствующего типу T (функции):
type ReturnType<T extends (...args: any) => any>
= T extends (...args: any) => infer R ? R : any;
Глядя на исходный код, на самом деле это слишком, на самом деле его можно немного упростить следующим образом:
type ReturnType<T extends func> = T extends () => infer R ? R: any;
Используя infer для вывода типа возвращаемого значения, а затем возвращая этот тип, если вы полностью понимаете значение infer, то этот абзац будет легко понять.
Например:
function foo(x: string | number): string | number { /*..*/ }
type FooType = ReturnType<foo>; // string | number
Required<T>
Этот инструмент может сделать все свойства типа T обязательными.
type Required<T> = {
[P in keyof T]-?: T[P]
}
Вот интересный синтаксис-?
, вы можете понять это как смысл вычитания необязательного атрибута в TS.
В дополнение к ним существует множество встроенных инструментов для ввода текста, вы можете обратиться кTypeScript HandbookДля получения более подробной информации на Github также есть множество сторонних помощников, таких какutility-typesЖдать.
6. Бой проекта
Здесь я делюсь некоторыми своими личными мыслями, которые могут быть однобокими или даже неправильными.Вы можете оставить сообщение для обсуждения.
Q: Вы предпочитаете использовать интерфейс или тип для определения типов?
О: С точки зрения использования между ними нет никакой разницы.Если вы используете проекты React для развития бизнеса, они в основном используются для определения реквизитов и типов данных интерфейса.
Но с точки зрения расширения тип расширять удобнее, чем интерфейс, если есть следующие два определения:
type Name = { name: string };
interface IName { name: string };
Если вы хотите расширить тип, введите только один&
и интерфейс для написания большого количества кода.
type Person = Name & { age: number };
interface IPerson extends IName { age: number };
Кроме того, у type есть некоторые вещи, которые интерфейс не может сделать, например, использование|
Чтобы объединить типы перечисления, используйтеtypeof
Получить определенный тип и так далее.
Однако одним из наиболее мощных аспектов интерфейса является то, что его можно многократно определять для добавления свойств.Например, нам нужно датьwindow
Добавьте пользовательское свойство или метод к объекту, затем мы можем добавить свойства непосредственно на основе его интерфейса.
declare global {
interface Window { MyNamespace: any; }
}
Вообще говоря, все мы знаем, что TS совместим по типам, а не по именам типов, поэтому обычно мне не нужно использовать объектно-ориентированные сценарии или изменять глобальный тип.Я обычно использую тип для определения типа.
В: Разрешить ли появление любого типа
A: Если честно, когда я впервые начал использовать TS, мне нравилось использовать любой.Ведь все перешли с JS, и я не могу полностью принять этот метод разработки кода, который влияет на эффективность.Поэтому то ли от лени, то ли поиск Есть много случаев, когда any используется без правильного определения.
С увеличением времени использования и углублением изучения и понимания TS бонус определения типа, приносимый TS, постепенно становится неотделимым. Я не хочу, чтобы какой-либо из них появлялся в коде. Все типы должны найти соответствующее определение один за другим или даже потерял смелость писать JS голышом.
Это вопрос, на который в настоящее время нет правильного ответа, и всегда необходимо найти баланс, который лучше всего подходит вам, среди таких факторов, как эффективность и время. Тем не менее, я по-прежнему рекомендую использовать TS. С развитием фронтенд-инжиниринга и повышением его статуса строго типизированные языки должны быть одной из самых надежных гарантий совместной работы нескольких человек и надежного кода. любое меньшее также является общим консенсусом в мире переднего плана.
В: Как разместить файл определения типа (.d.ts)
О: Кажется, что в отрасли нет специального единого стандарта, мои мысли таковы:
- Временные типы, определяемые непосредственно при использовании
Например, если вы пишете Helper внутри компонента, входные и выходные параметры функции предназначены только для внутреннего использования и нет возможности повторного использования, вы можете определить их непосредственно при определении функции.
function format(input: {k: string}[]): number[] { /***/ }
- Тип персонализации компонента, определенный непосредственно в файле ts(x)
Например, в дизайне компонентов AntD реквизиты, состояние и т. д. каждого отдельного компонента специально определяются и экспортируются.
// Table.tsx
export type TableProps = { /***/ }
export type ColumnProps = { /***/ }
export default function Table() { /***/ }
Таким образом, пользователи могут импортировать и использовать эти типы, импортируя их, если они им нужны.
- объем/глобальные данные, определенные в файле .d.ts
Нет никаких возражений против данных глобального типа.Как правило, в корневом каталоге есть папка typings, в которой будут храниться некоторые определения глобальных типов.
Если мы используем модуль css, то нам нужно позволить TS распознать, что файл .less (или .scss) является объектом после его импорта, который можно определить следующим образом:
declare module '*.less' {
const resource: { [key: string]: string };
export = resource;
}
Для некоторых глобальных типов данных, таких как общие типы данных, возвращаемые бэкендом, я также привык помещать их в папку typings и использовать пространство имен, чтобы избежать конфликтов имен, что может сохранить оператор определения типа импорта компонента.
declare namespace EdgeApi {
interface Department {
description: string;
gmt_create: string;
gmt_modify: string;
id: number;
name: string;
}
}
Таким образом, каждый раз, когда вы используете его, вам нужно толькоconst department: EdgeApi.Department
Вот и все, экономя много усилий при импорте. Пока разработчики могут договориться о спецификациях и избежать конфликтов имен.
Краткое изложение использования TS представлено здесь, спасибо за просмотр~
Добро пожаловать в "Byte Front End ByteFE"
Контактный адрес электронной почты для доставки резюме "tech@bytedance.com"