# 开发中常用的 Typescript 技巧
# type
type(类型别名) 可以用于定义一种新的类型
/** 表示一种类型为string的Name类型 */
type Name = string;
const a: Name = 'cc';
/** 表示联合类型 */
type Names = string | number;
type Names = 'cc' | 'dd';
/** 定义函数类型别名 */
type Fuc = (name: string) => string;
/** 定义对象 */
type Props = {
name: string;
age: number;
};
/** 继承(表示交叉类型) */
type FullProps = Props & {
gender: string;
};
# 继承相关 extends Omit Pick
比如封装一个 react input 基础组件,在 input 基础上添加属性
import type {HTMLAttribute} from 'react'
/** type 继承 */
export InputProps = HTMLAttribute<HTMLInputElement> & {
clearable?: boolean
}
/** interface 继承 */
export InputProps extends HTMLAttribute<HTMLInputElement> {
clearable?: boolean
}
原生 placeholder 支持 string 或者 undefined,如果我们想 placeholder 支持 ReactNode,这个时候 ts 会提示类型错误。如果改变原生 input props 类型?需要用到 Omit
import type {HTMLAttribute, ReactNode} from 'react'
export InputProps = Omit<HTMLAttribute<HTMLInputElement>, 'placeholder'> & {
placeholder?: ReactNode
}
如果我们只需要原生 Input props 中的几个,可以使用 Pick
import type {HTMLAttribute, ReactNode} from 'react'
/** Pick需要的属性 */
export InputProps = Pick<HTMLAttribute<HTMLInputElement>, 'placeholder' | 'value' | 'onChange'>
# 使用 Partial 进行属性提示
# 使用枚举
# 定义函数类型
不使用interface
const getFullName = ({
firstName,
lastName,
}: {
firstName: string;
lastName: string;
}): string => {
// :string可以省略。ts函数返回值可以省略,让ts自己去推断
return `${firstName}${lastName}`;
};
使用 interface
// 第一种
interface Info {
firstName: string; // 这里写 , ; 或者不写都可以
lastName: string;
}
const getMyFullName = ({ firstName, lastName }: Info): string => {
// :string可以省略。ts函数返回值可以省略
return `${firstName}${lastName}`;
};
// 第二种
interface InfoFuc {
(num1: number, num2: number): number;
}
const add: AddFunc = (n1, n2) => n1 + n2;
使用 type(略)
函数声明与函数表达式
// 1.普通函数声明
function add(arg1: number, arg2: number): number {
return arg1 + arg2;
}
// 2.函数表达式
let myAdd = function (x: number, y: number): number {
return x + y;
};
需要注意一点:可选函数要放在必选函数后面,后者会有 ts 警告
# 泛型
更广泛的约束类型
不知道返回的类型,但是用不想用 any,而是根据输入值决定返回值
function identity<T>(arg: T): T {
// 这里T仅仅是一个表示,可以是其他字母比如 P,U等
return arg;
}
// 明确指定 T 是 string类型
let output = identity<string>('myString'); // type of output will be 'string'
// 下面这种方法更普遍.利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型
let output = identity('myString');
// 接口使用泛型
interface Co<T> {
(name: T): T;
}
# 在React中使用泛型
useState,
通常 useState 不一定要给泛型类型,因为 ts 会进行类型断言,根据你给的初始值进行判断
event
interface IProps {
className?: string
}
const Box: React.FC<IProps> = (props) => {
const { className } = props
const [value, setValue] = useState<string>('')
const inputRef = useRef<HTMLInputElement>(null)
// 定义event的接口类型
const handleValue = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value)
}
return (
<div className={className}>
<input ref={inputRef} type="text" value={value} onChange={handleValue} />
<div>{value}</div>
</div>
)
}
export default Box
# 交叉类型与联合类型
交叉类型,需要符合全部
type Name = string & number
上述类型时错误的,因为没有值既能满足 string 又能满足 number
联合类型,符合其中一部分即可
type Name = string | number
# 类型断言 as
// 比如any类型的但我知道它现在应该是更精确的值,比如string
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
// 另一种 as 写法 JSX只能用这种类型断言
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
// 类型断言
const getMyLength = (target: string | number): number => {
if ((target as string).length) {
return (target as string).length;
} else {
return target.toString().length;
}
};
# 函数重载
函数重载,根据输入的不同,返回对应的输出。可以定义多种参数类型。好处是可以让开发者不用去函数的内部去查找到底应该传几个值,以及有怎样的输出
// 这个函数可以传 1个参数,2个参数,4个参数
interface Direction {
top: number;
bottom?: number;
left?: number;
right?: number;
}
function assigned(all: number): Direction;
function assigned(topAndBottom: number, leftAndRight: number): Direction;
function assigned(
top: number,
right: number,
bottom: number,
left: number
): Direction;
function assigned(a: number, b?: number, c?: number, d?: number) {
if (b === undefined && c === undefined && d === undefined) {
b = c = d = a;
} else if (c === undefined && d === undefined) {
c = a;
d = b;
}
return {
top: a,
right: b,
bottom: c,
left: d,
};
}
assigned(1);
assigned(1, 2);
assigned(1, 2, 3);
assigned(1, 2, 3, 4);
# 命名空间
有了模块系统之后明明空间可能并不是那么常用了,通常在一些非 TypeScript 原生代码的 .d.ts 文件中使用,主要是由于 ES Module 过于静态,对 JavaScript 代码结构的表达能力有限。
// 比如 Validation.ts 文件 声明了 Validation命名空间
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
// 使用declare
declare namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
// 或者使用下面写法
export as namespace Validation
export interface StringValidator {
isAcceptable(s: string): boolean;
}
外部命名空间 declare
关键字