TypeScript 学习笔记(一):类型

news2024/10/7 19:16:51

文章目录

    • 一、常见类型
      • 1. 数组
      • 2. 布尔
      • 3. 数值
      • 4. 字符串
      • 5. object
      • 6. null 和 undefined
      • 7. symbol
        • 7.1 作为属性名
        • 7.2 属性名遍历
        • 7.3 静态方法:Symbol.for()和 Symbol.keyFor()
        • 7.4 内置 symbol 值
          • 7.4.1 Symbol.hasInstance
          • 7.4.2 Symbol.isConcatSpreadable
          • 7.4.3 Symbol.species
          • 7.4.4 Symbol.match、Symbol.replace、Symbol.search 和 Symbol.split
          • 7.4.5 Symbol.iterator
          • 7.4.6 Symbol.toPrimitive
          • 7.4.7 Symbol.toStringTag
          • 7.4.8 Symbol.unscopables
        • 7.5 在TypeScript中使用symbol类型
    • 二、补充类型
      • 1. 元组
      • 2. 枚举
      • 3. any
      • 4. void
      • 5. never
      • 6. unknown
      • 7. 交叉类型
      • 8. 联合类型
        • 8.1 没有联合类型
        • 8.2 接口用联合类型
        • 8.3 可辨识联合类型
      • 9. 交叉和联合优先级
      • 10. 类型缩减
    • 三、类型断言

一、常见类型

1. 数组

  let list1: number[] = [1, 2, 3];
  console.log(list1);
  let list2: Array<string> = ['1', '2'];
  console.log(list2);

2. 布尔

  let a: boolean = true;
  console.log(a);

3. 数值

  let num: number = 1;
  console.log(num);
  num = 'abc' // error 不能将类型“"123"”分配给类型“number”

当我们给num赋值为123但没有指定类型时,编译器推断出了num的类型为number数值类型,所以当给num再赋值为字符串"abc"时,就会报错。

这里还有一点要注意,就是number和Number的区别:TS中指定类型的时候要用number,这个是TypeScript的类型关键字。而Number为JavaScript的原生构造函数,用它来创建数值类型的值,它俩是不一样的。包括你后面见到的string、boolean等都是TypeScript的类型关键字,不是JavaScript语法,这点要区分开

4. 字符串

  let str: string = 'str';
  console.log(str);

5. object

  let obj = {
    a: '2',
    b: '6',
  };
  console.log('object', obj.a);

6. null 和 undefined

null 和 undefined 有一些共同特点,所以我们放在一起讲。说它们有共同特点,是因为在 JavaScript 中,undefined 和 null 是两个基本数据类型。在 TypeScript 中,这两者都有各自的类型即 undefined 和 null,也就是说它们既是实际的值,也是类型,来看实际例子:

let n: null = null;
let u: undefined = undefined;

7. symbol

ES6新基础类型,它和 number、string、boolean、undefined 和 null 是同类型的,object 是引用类型。它用来表示独一无二的值,通过 Symbol 函数生成。

  const s = Symbol();
  console.log(typeof s); // 'symbol'

我们可以在使用 Symbol 方法创建 symbol 类型值的时候传入一个参数,这个参数需要是字符串的。如果传入的参数不是字符串,会先调用传入参数的 toString 方法转为字符串。

  const s1 = Symbol('lison');
  const s2 = Symbol('lison');
  console.log(s1 === s2); // false
  // 补充:这里第三行代码可能会报一个错误:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap.
  // 这是因为编译器检测到这里的s1 === s2始终是false,所以编译器提醒你这代码写的多余,建议你优化。

在这里插入图片描述
上面例子中使用 Symbol 方法创建的两个 symbol 值,方法中都传入了相同的字符串’lison’,但是s1 === s2却是 false,这就是我们说的,Symbol 方法会返回一个独一无二的值,这个值和任何一个值都不等,虽然我们传入的标识字符串都是"lison",但是确实两个不同的值。

可以理解为我们每一个人都是独一无二的,虽然可以有相同的名字,但是名字只是用来方便我们区分的,名字相同但是人还是不同的。Symbol 方法传入的这个字符串,就是方便我们在控制台或程序中用来区分 symbol 值的。我们可以调用 symbol 值的toString方法将它转为字符串:

const s1 = Symbol("lison");
console.log(s1.toString()); // 'Symbol(lison)'
 

你可以简单地理解 symbol 值为字符串类型的值,但是它和字符串有很大的区别,它不可以和其他类型的值进行运算,但是可以转为字符串和布尔类型值:

let s = Symbol("lison");
console.log(s.toString()); // 'Symbol(lison)'
console.log(Boolean(s)); // true
console.log(!s); // false
 

通过上面的例子可以看出,symbol 类型值和对象相似,本身转为布尔值为 true,取反为 false。

7.1 作为属性名

在 ES6 中,对象的属性名支持表达式,所以你可以使用一个变量作为属性名,这对于一些代码的简化很有用处,但是表达式必须放到方括号内:

let prop = "name";
const obj = {
  [prop]: "Lison"
};
console.log(obj.name); // 'Lison'

symbol 值可以作为属性名,因为 symbol 值是独一无二的,所以当它作为属性名时,不会和其他任何属性名重复:

let name = Symbol();
let obj = {
  [name]: "lison"
};
console.log(obj); // { Symbol(): 'lison' }

可以看到打印出来的对象有一个属性名是 symbol 值。如果我们想访问这个属性值,就只能使用 name 这个 symbol 值:

console.log(obj[name]); // 'lison'
console.log(obj.name); // undefined

通过上面的例子可以看到,我们访问属性名为 symbol 类型值的 name 时,我们不能使用点’.‘号访问,因为obj.name这的name实际上是字符串’name’,这和访问普通字符串类型的属性名一样。你必须使用方括号的形式,这样obj[name]这的 name 才是我们定义的 symbol 类型的变量name,之后我们再访问 obj 的[name]属性就必须使用变量 name。

7.2 属性名遍历

使用 Symbol 类型值作为属性名,这个属性不会被for…in遍历到,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()获取到:

const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
for (const key in obj) {
  console.log(key);
}
// => 'age'
console.log(Object.keys(obj));
// ['age']
console.log(Object.getOwnPropertyNames(obj));
// ['age']
console.log(JSON.stringify(obj));
// '{ "age": 18 }'
 

虽然这么多方法都无法遍历和访问到 Symbol 类型的属性名,但是 Symbol 类型的属性并不是私有属性。我们可以使用Object.getOwnPropertySymbols方法获取对象的所有symbol类型的属性名:

const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
const SymbolPropNames = Object.getOwnPropertySymbols(obj);
console.log(SymbolPropNames);
// [ Symbol(name) ]
console.log(obj[SymbolPropNames[0]]);
// 'lison'
// 如果最后一行代码这里报错提示:元素隐式具有 "any" 类型,因为类型“{ [name]: string; age: number; }”没有索引签名。 那可能是在tsconfig.json里开启了noImplicitAny。因为这里我们还没有学习接口等高级类型,所以你可以先忽略这个错误,或者关闭noImplicitAny。

除了Object.getOwnPropertySymbols这个方法,还可以用 ES6 新提供的 Reflect 对象的静态方法Reflect.ownKeys,它可以返回所有类型的属性名,所以 Symbol 类型的也会返回。

const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
console.log(Reflect.ownKeys(obj));
// [ 'age', Symbol(name) ]
 

7.3 静态方法:Symbol.for()和 Symbol.keyFor()

  • Symbol.for()
    我们使用 Symbol 方法创建的 symbol 值是独一无二的,每一个值都不和其他任何值相等,我们来看下例子:
  const s1 = Symbol('lison');
  const s2 = Symbol('lison');
  const s3 = Symbol.for('lison');
  const s4 = Symbol.for('lison');
  console.log(s3 === s4); // true
  console.log(s1 === s3); // false
  // 这里还是会报错误:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap.还是我们说过的,因为这里的表达式始终是true和false,所以编译器会提示我们。
 

在这里插入图片描述
直接使用 Symbol 方法,即便传入的字符串是一样的,创建的 symbol 值也是互不相等的。**而使用 Symbol.for方法传入字符串,会先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值,如果有,返回该值,如果没有,则使用该字符串新创建一个。**使用该方法创建 symbol 值后会在全局范围进行注册。

注意:这个注册的范围包括当前页面和页面中包含的 iframe,以及 service sorker,我们来看个例子:

const iframe = document.createElement("iframe");
iframe.src = String(window.location);
document.body.appendChild(iframe);
 
iframe.contentWindow.Symbol.for("lison") === Symbol.for("lison"); // true
// 注意:如果你在JavaScript环境中这段代码是没有问题的,但是如果在TypeScript开发环境中,可能会报错:类型“Window”上不存在属性“Symbol”。
// 因为这里编译器推断出iframe.contentWindow是Window类型,但是TypeScript的声明文件中,对Window的定义缺少Symbol这个字段,所以会报错,所以你可以这样写:
// (iframe.contentWindow as Window & { Symbol: SymbolConstructor }).Symbol.for("lison") === Symbol.for("lison")
// 这里用到了类型断言和交叉类型,SymbolConstructor是内置的类型。
 

上面这段代码的意思是创建一个 iframe 节点并把它放到 body 中,我们通过这个iframe 对象的 contentWindow 拿到这个 iframe 的 window 对象,在iframe.contentWindow上添加一个值就相当于你在当前页面定义一个全局变量一样,我们看到,在iframe 中定义的键为’lison’的 symbol 值在和在当前页面定义的键为’lison’的 symbol 值相等,说明它们是同一个值。

-Symbol.keyFor()

该方法传入一个 symbol 值,返回该值在全局注册的键名:

const sym = Symbol.for("lison");
console.log(Symbol.keyFor(sym)); // 'lison'
 

7.4 内置 symbol 值

ES6 提供了 11 个内置的 Symbol 值,指向 JS 内部使用的属性和方法。看到它们第一眼你可能会有疑惑,这些不是 Symbol 对象的一个属性值吗?没错,这些内置的 Symbol 值就是保存在 Symbol 上的,你可以把Symbol.xxx看做一个 symbol 值。

7.4.1 Symbol.hasInstance

对象的 Symbol.hasInstance 指向一个内部方法,当你给一个对象设置以 Symbol.hasInstance 为属性名的方法后,当其他对象使用 instanceof 判断是否为这个对象的实例时,会调用你定义的这个方法,参数是其他的这个对象,来看例子:

const obj = {
  [Symbol.hasInstance](otherObj) {
    console.log(otherObj);
  }
};
console.log({ a: "a" } instanceof obj); // false
// 注意:在TypeScript中这会报错,"instanceof" 表达式的右侧必须属于类型 "any",或属于可分配给 "Function" 接口类型的类型。
// 是要求你instanceof操作符右侧的值只能是构造函数或者类,或者类型是any类型。这里你可以使用类型断言,将obj改为obj as any
 

可以看到当我们使用 instanceof 判断{ a: ‘a’ }是否是 obj 创建的实例的时候,Symbol.hasInstance 这个方法被调用了。

7.4.2 Symbol.isConcatSpreadable

这个值是一个可读写布尔值,其值默认是undefined,当一个数组的 Symbol.isConcatSpreadable 设为 true或者为默认的undefined 时,这个数组在数组的 concat 方法中会被扁平化。我们来看下例子:

let arr = [1, 2];
console.log([].concat(arr, [3, 4])); // 打印结果为[1, 2, 3, 4],length为4
let arr1 = ["a", "b"];
console.log(arr1[Symbol.isConcatSpreadable]); // undefined
arr1[Symbol.isConcatSpreadable] = false;
console.log(arr1[Symbol.isConcatSpreadable]); // false
console.log([].concat(arr1, [3, 4])); // 打印结果如下:
/
 [ ["a", "b", Symbol(Symbol.isConcatSpreadable): false], 3, 4 ]
 最外层这个数组有三个元素,第一个是一个数组,因为我们设置了arr1[Symbol.isConcatSpreadable] = false
 所以第一个这个数组没有被扁平化,第一个元素这个数组看似是有三个元素,但你在控制台可以看到这个数组的length为2
 Symbol(Symbol.isConcatSpreadable): false不是他的元素,而是他的属性,我们知道数组也是对象,所以我们可以给数组设置属性
 你可以试试如下代码,然后看下打印出来的效果:
  let arr = [1, 2]
  arr.props = 'value'
  console.log(arr)
 /
 
7.4.3 Symbol.species

这里我们需要提前使用类的知识来讲解这个 symbol 值的用法,这个知识你需要在纯JavaScript的开发环境中才能看出效果,你可以在浏览器开发者工具的控制台尝试。在TypeScript中,下面两个例子都是一样的会报a.getName is not a function错误。

首先我们使用 class 定义一个类 C,使用 extends 继承原生构造函数 Array,那么类 C 创建的实例就能继承所有 Array 原型对象上的方法,比如 map、filter 等。我们先来看代码:

class C extends Array {
  getName() {
    return "lison";
  }
}
const c = new C(1, 2, 3);
const a = c.map(item => item + 1);
console.log(a); // [2, 3, 4]
console.log(a instanceof C); // true
console.log(a instanceof Array); // true
console.log(a.getName()); // "lison"
 

这个例子中,a 是由 c 通过 map 方法衍生出来的,我们也看到了,a 既是 C 的实例,也是 Array 的实例。但是如果我们想只让衍生的数组是 Array 的实例,就需要用 Symbol.species,我们来看下怎么使用:

class C extends Array {
  static get [Symbol.species]() {
    return Array;
  }
  getName() {
    return "lison";
  }
}
const c = new C(1, 2, 3);
const a = c.map(item => item + 1);
console.log(a); // [2, 3, 4]
console.log(a instanceof C); // false
console.log(a instanceof Array); // true
console.log(a.getName()); // error a.getName is not a function
 

就是给类 C 定义一个静态 get 存取器方法,方法名为 Symbol.species,然后在这个方法中返回要构造衍生数组的构造函数。所以最后我们看到,a instanceof C为 false,也就是 a 不再是 C 的实例,也无法调用继承自 C 的方法。

7.4.4 Symbol.match、Symbol.replace、Symbol.search 和 Symbol.split

这个 Symbol.match 值指向一个内部方法,当在字符串 str 上调用 match 方法时,会调用这个方法,来看下例子:

let obj = {
  [Symbol.match](string) {
    return string.length;
  }
};
console.log("abcde".match(obj)); // 5
 

相同的还有 Symbol.replace、Symbol.search 和 Symbol.split,使用方法和 Symbol.match 是一样的。

7.4.5 Symbol.iterator

数组的 Symbol.iterator 属性指向该数组的默认遍历器方法:

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

在这里插入图片描述
这个 Symbol.iterator 方法是可写的,我们可以自定义遍历器方法。

7.4.6 Symbol.toPrimitive

对象的这个属性指向一个方法,当这个对象被转为原始类型值时会调用这个方法,这个方法有一个参数,是这个对象被转为的类型,我们来看下:

let obj = {
  [Symbol.toPrimitive](type) {
    console.log(type);
  }
};
// const b = obj++ // number
const a = abc${obj}; // string
 
7.4.7 Symbol.toStringTag

Symbol.toStringTag 和 Symbol.toPrimitive 相似,对象的这个属性的值可以是一个字符串,也可以是一个存取器 get 方法,当在对象上调用 toString 方法时调用这个方法,返回值将作为"[object xxx]"中 xxx 这个值:

let obj = {
  [Symbol.toStringTag]: "lison"
};
obj.toString(); // "[object lison]"
let obj2 = {
  get [Symbol.toStringTag]() {
    return "haha";
  }
};
obj2.toString(); // "[object haha]"
 
7.4.8 Symbol.unscopables

这个值和 with 命令有关,我们先来看下 with 怎么使用:

const obj = {
  a: "a",
  b: "b"
};
with (obj) {
  console.log(a); // "a"
  console.log(b); // "b"
}
// 如果是在TypeScript开发环境中,这段代码可能with会报错:不支持 "with" 语句,这是因为在严格模式下,是不允许使用with的。
 

可以看到,使用 with 传入一个对象后,在代码块中访问对象的属性就不需要写对象了,直接就可以用它的属性。对象的 Symbol.unscopables 属性指向一个对象,该对象包含了当使用 with 关键字时,哪些属性被 with 环境过滤掉:

console.log(Array.prototype[Symbol.unscopables]);
/
{
    copyWithin: true
    entries: true
    fill: true
    find: true
    findIndex: true
    includes: true
    keys: true
    values: true
}
/
 

7.5 在TypeScript中使用symbol类型

  • 基础
    学习完ES6标准中Symbol的所有内容后,我们来看下在TypeScript中使用symbol类型值,很简单。就是制定一个值的类型为symbol类型:
let sym: symbol = Symbol()
 
  • unique symbol

symbols的子类型,这种类型的值只能由Symbol()或Symbol.for()创建,或者通过指定类型来指定一个值是这种类型。这种类型的值仅可用于常量的定义和用于属性名。另外还有一点要注意,定义unique symbol类型的值,必须用const不能用let。我们来看个在TypeScript中使用Symbol值作为属性名的例子

const key1: unique symbol = Symbol()
let key2: symbol = Symbol()
const obj = {
    [key1]: 'value1',
    [key2]: 'value2'
}
console.log(obj[key1])
console.log(obj[key2]) // error 类型“symbol”不能作为索引类型使用。
 

二、补充类型

1. 元组

对号入座

let tuple: [string, number, boolean];
tuple = ["a", 2, false];
tuple = [2, "a", false]; // error 不能将类型“number”分配给类型“string”。 不能将类型“string”分配给类型“number”。
tuple = ["a", 2]; // error Property '2' is missing in type '[string, number]' but required in type '[string, number, boolean]'
 

2. 枚举

TypeScript 在 ES 原有类型基础上加入枚举类型,使我们在 TypeScript 中也可以给一组数值赋予名  字,这样对开发者来说较为友好。比如我们要定义一组角色,每一个角色用一个数字代表,就可以使用枚举类型来定义:

enum Roles {
  SUPER_ADMIN,
  ADMIN,
  USER
}

枚举取值默认从0开始,如下:

enum Roles {
  SUPER_ADMIN = 0,
  ADMIN = 1,
  USER = 2
}
 

有值会累计:

  enum Roles {
    SUPER_ADMIN = 8,
    ADMIN,
    USER = 2,
  }
   console.log('枚举', Roles.USER);// 2
   console.log('枚举', Roles.SUPER_ADMIN );// 8
   console.log('枚举', Roles.ADMIN);// 9

3. any

表示任意类型

enum Flag {
    success,
    error,
    overtime
};

let flag: any = true;//布尔型
let num: any = 123;//数字型
let str: any = 'Hello,TypeScript';//字符型
let arr: any = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];//数组型
let tuple: any = [10086, 'Nick'];//元组型
let e: any = Flag.success;//枚举型
let n: any = null;//null型
let u: any = undefined;//undefined型

但是请注意,不要滥用 any,如果任何值都指定为 any 类型,那么 TypeScript 将失去它的意义。

4. void

void 和 any 相反,any 是表示任意类型,void 是表示没有任意类型,一般用于定义方法的时候方法没有返回值。如下:

const consoleText = (text: string): void => {
  console.log(text); // 没有返回值 用void
};
const consoleText2 = (text: string): string => {
   console.log(text);
   return text;
 };

void 类型的变量只能赋值为 undefined 和 null,其他类型不能赋值给 void 类型的变量。

5. never

never 类型指那些永不存在的值的类型,它是那些总会抛出异常或根本不会有返回值的函数表达式的返回值类型,当变量被永不为真的类型保护所约束时,该变量也是 never 类型。

const errorFunc = (message: string): never => {
  throw new Error(message);
};
errorFunc('错误了!') 

这个 errorFunc 函数总是会抛出异常,所以它的返回值类型是 never,用来表明它的返回值是永不存在的。

const infiniteFunc = (): never => {
  while (true) {}
};

infiniteFunc也是根本不会有返回值的函数,它和签名的 void 类型时的consoleText函数不同,consoleText函数没有返回值,是我们在定义函数的时候没有给它返回值,而infiniteFunc是死循环是根本不会返回值的,所以它们二者还是有区别的。

never 类型是任何类型的子类型,所以它可以赋值给任何类型;而没有类型是 never 的子类型,所以除了它自身没有任何类型可以赋值给 never 类型,any 类型也不能赋值给 never 类型。我们来看例子:

let neverVariable = (() => {
  while (true) {}
})();
neverVariable = 123; // error 不能将类型"number"分配给类型"never"
 

上面例子我们定义了一个立即执行函数,也就是"let neverVariable = "右边的内容。右边的函数体内是一个死循环,所以这个函数调用后的返回值类型为 never,所以赋值之后 neverVariable 的类型是 never 类型,当我们给 neverVariable 赋值 123 时,就会报错,因为除它自身外任何类型都不能赋值给 never 类型。

6. unknown

未知类型,看着和any很像,但是还是有区别的,也就是所谓的“unknown相对于any是安全的”,unknown类型的值不是可以随便操作的。

7. 交叉类型

交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

// 交叉类型 = 交集 (和数学中有点差异)
interface Person1 {
    handsome: string,
    // a:string  如果两个类型不一致 则相交的结果是never
}
interface Person2 {
    height: string,
    // a:number
}
 
type Person3 = Person1 & Person2; // | 并集  & 交集  (交集可以理解成 涵盖所有属性)
 
let person: Person3 = {
    handsome: '帅',
    height: '高',
}
 
// 在原有的类型基础上想去扩展属性 可以用交叉类型
// ts的核心为了安全  交叉类型 可以赋予给没有交叉之前的类型
 
type Person4 = Person2 & { money: string }
let person4: Person4 = {
    ...person,
    money: '有钱'
}
let p: Person2 = person;
 
// 方法的mixin 默认推断会生成交集
function mixin<T extends object, K extends object>(o1: T, o2: K): T & K {
    return { ...o1, ...o2 }
}
// 我们后续真正合并属性的时候 要以一方为基础 不会直接相交,可能会导致never情况
// merge
let r = mixin({}, { address: 'xxx', name: 12 })
 

常用:

// 将两个类型合并
type IntersectionType = { id: number; name: string } & { age: number }

const mixed: IntersectionType = {
  id: 1,
  name: 'name',
  age: 18,
} 
  • 如果合并重名 – 类型不同
    合并时候两个都有相同定义,但类型不同就会产生一个’无用类型’即类型为never
type IntersectionType = { id: number; name: string } & { age: number ,id:string}

const mixed: IntersectionType = {
  id: 1, // 报错
  name: 'name',
  age: 18,
}

在这里插入图片描述

  • 如果合并重名 – 类型相同
    合并时候两个都有相同定义,但类型相同就会,类型就是两者中的子类型
    1.number 和number 子类型还是number 因此没问题
type IntersectionType = { id: number; name: string } & {
  age: number
  id: number
}

let mixed: IntersectionType = {
  id: 1,
  name: 'name',
  age: 18,
}
mixed = {
  id: 2,
  name: 'name',
  age: 18,
}

2.number 和 2 子类型因此是 2 所以此时赋值1有问题

type IntersectionType = { id: 2; name: string } & {
  age: number
  id: number
}

let mixed: IntersectionType = {
  id: 1, // 报错
  name: 'name',
  age: 18,
}
mixed = {
  id: 2,
  name: 'name',
  age: 18,
}

在这里插入图片描述

8. 联合类型

联合类型实际是几个类型的结合,但是和交叉类型不同,联合类型是要求只要符合联合类型中任意一种类型即可,它使用 | 符号定义。当我们的程序具有多样性,元素类型不唯一时,即使用联合类型。

const getLenFunc = (content:number|string)=>{
    if (typeof content  === 'string'){ return content.length}
    else {return content.toString().length}
}
// getLenFunc(true) // 报错 只能是string 或者是number类型中的一个
// getLength函数的定义中,其实还涉及到一个知识点,就是类型保护,就是typeof content === “string”

8.1 没有联合类型

如果没有联合类型可能使用时候需要’any’ 或’unknown’

// 只想让参数接受 string 和 number 参数,需要不停的缩小参数范围
function getParams(param: unknown) {
  if (typeof param === 'string') return param
  else if (typeof param === 'number') return param
  else throw Error('只能是字符类型')
}

用联合类型

function getParams1(param: string | number) {
  return param
}

8.2 接口用联合类型

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员
简单的说’使用一个联合类型的参数只能使用共有成员’,下面案例中c虽然有age属性,但是会报错
只能使用他们的共有成员name

interface a {
    name:string,
    age:number
}
interface b {
    name:string,
    sex:string
}
// const d:(a|b) = {
//     age:13,
//     sex:'nan'
// }  // 报错
// d.age// 报错
const c:(a|b) = {
    name:'fff',
    age:12,
    sex:'w'
}
c.name // 不报错

const d 报错内容:
在这里插入图片描述

8.3 可辨识联合类型

在中增加 和 修改 这两个需求的时候往往,新增不需要id 但是修改需要id,往往这种处理一般都会集中在一个公共方法中
举个例子

type Add = {
  name: string
  age: number
}

type Edit = {
  id: number
  name: string
  age: number
}

function submit(params: Add | Edit) {
  // .......
  console.log(params.id) // 报错
}
submit({ name: '23', age: 23, id: 1 })

在这里插入图片描述
解决方法缩小范围
使用 if 等流程控制语句,下面案例只能使用in

function submit(params: Add | Edit) {
  // 无效
  // if(params.id){ //
  //   console.log(params.id)

  // }
  // 无效
  // if(Reflect.has(params,"id")){
  //   console.log(params.id)
  // }
  // 无效
  // if(Reflect.get(params,"id")){
  //   console.log(params.id)
  // }
  if ('id' in params) {
    console.log(params.id)
  }
}

9. 交叉和联合优先级

联合操作符 | 的优先级低于交叉操作符 &,可以通过使用小括弧 () 来调整操作符的优先级

// 先 1 2 和 3 4 交叉 然后在将两个交叉联合
type UnionIntersectionA =
  | ({ id: number } & { name: string })
  | ({ id: string } & { name: number }) // 交叉操作符优先级高于联合操作符


// 改变联合顺序
type UnionIntersectionB =
  | ('px' | 'em' | 'rem' | '%')
  | ('vh' | 'em' | 'rem' | 'pt') // 调整优先级

10. 类型缩减

两个联合类型在类型存在父子时候,会类型缩减以父类型为主


type URStr = 'string' | string // 类型是 string

type URNum = 2 | number // 类型是 number

type URBoolen = true | boolean // 类型是 boolean

enum EnumUR {
  ONE,

  TWO,
}

type URE = EnumUR.ONE | EnumUR // 类型是 EnumUR

type UnionInterce =
  | {
      age: '1'
    }
  | {
      age: '1' | '2'

      [key: string]: string
    } // {age: '1'|'2', [key: string]: string;}

产生的问题

  • 如图因为类型缩减后,相对的之前ts 利用编译器提供的提示功能也消失
    在这里插入图片描述
  • TypeScript 官方其实还提供了一个方法,让类型缩减被控制,可以给父类型添加“& {}”
    在这里插入图片描述
    解决索引签名 – 可以添加不确定参数

接口类型一旦定义了任意属性那么’确定属性和可选属性的类型都必须是它的类型的子集’,never是所有类型的子集

// 之前为了可以定义一个age 属性是number,但是有可以有不确参数我们需要将不却参数扩大可以包含age,形成父子级
interface Person {
  name: string
  age: number // 报错
  [propName: string]: string // 改正 [propName: string]: any; 或者 [propName: string]: string|number
}
// 利用类型缩减 和never 是任意类型子集 即可声明一个 name 是string age 是number 并且其他任意key 类型是string类型
type Person =
  | {
      name: string
      age: number // 报错
    }
  | {
      age: never
      [propName: string]: string  
    }

三、类型断言

它有两种写法,一种是value,一种是value as type,下面例子中我们用两种形式都写出来:

const getStrLength = (target: string | number): number => {
  if ((<string>target).length) { // 这种形式在JSX代码中不可以使用,而且也是TSLint不建议的写法
    return (target as string).length; // 这种形式是没有任何问题的写法,所以建议大家始终使用这种形式
  } else {
    return target.toString().length;
  }
};
 

注意:推荐使用as关键字

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/742379.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第四十五章Java 接口

Java 接口 接口&#xff08;英文&#xff1a;Interface&#xff09;&#xff0c;在JAVA编程语言中是一个抽象类型&#xff0c;是抽象方法的集合&#xff0c;接口通常以interface来声明。一个类通过继承接口的方式&#xff0c;从而来继承接口的抽象方法。 接口并不是类&#x…

DevOps(二)

CD 1. 平台选择2. 技术选型3. 阶段性目标4. 搭建示例4.1 环境准备(节点机)1. java版本升级2. 编译安装git3. docker安装4. docker-compose安装5. sonarqube安装6. harbor安装7. gitlab私服 4.2 示例一&#xff08;手动&#xff09;1. 创建项目2. 编码3. Dockerfile4. 拷贝pytho…

【linux】线程详解

线程 线程的概念 在官方书籍对于线程的概念&#xff1a; 1.在进程内部的执行流 2.线程比进程粒度更细&#xff0c;调度成本更低。 3.线程是CPU调度的最小单位。 线程&#xff08;tcb&#xff09;&#xff1a;进程&#xff08;pcb&#xff09; n&#xff1a;1 进程和线程在执…

Java 动态规划 面试题 17.16. 按摩师

代码展示&#xff1a; class Solution {public int massage(int[] nums) {int nnums.length;if(n0){return 0;}//创建数组int f[]new int[n]; //f[i]表示接i位置的最长时间int g[]new int[n]; //g[i]表示不接i位置的最长时间//初始化f[0]nums[0];g[0]0;//填充数组for(int i1;i…

VectorCAST单元测试手动配置测试用例

一、单元测试 等待环境创建完成后&#xff0c;就可以开始单元测试。 二、生成测试用例 在 VectorCAST 中&#xff0c;一共有两种方法来生成测试用例&#xff0c;一种是手动生成测试用例&#xff0c;另外一种是自动 生成测试用例。 三、手动生成测试用例 在 VectorCAST 中&a…

《面试1v1》Redis分布式锁

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

泛微E-Cology SQL注入漏洞复现(QVD-2023-15672)

0x01 产品简介 泛微协同管理应用平台e-cology是一套兼具企业信息门户、知识文档管理、工作流程管理、人力资源管理、客户关系管理、项目管理、财务管理、资产管理、供应链管理、数据中心功能的企业大型协同管理平台。 0x02 漏洞概述 由于泛微e-cology未对用户的输入进行有效的…

matlab学习指南(2):安装工具箱Toolbox的方法(详细图解)

&#x1f305;*&#x1f539;** φ(゜▽゜*)♪ **&#x1f539;*&#x1f305; 欢迎来到馒头侠的博客&#xff0c;该类目主要讲数学建模的知识&#xff0c;大家一起学习&#xff0c;联系最后的横幅&#xff01; 喜欢的朋友可以关注下&#xff0c;私信下次更新不迷路&#xff0…

【项目 进程1】2.1 进程概述 2.2 进程状态转换

文章目录 2.1进程概述程序和进程**时间片****并行和并发****进程控制块(PCB)** 2.2进程状态转换**进程的状态** **进程相关命令****实时显示进程动态** 2.1进程概述 程序和进程 程序是包含一系列信息的文件&#xff0c;这些信息描述了如何在运行时创建一个进程&#xff1a; …

Linux的软链接与硬链接

Linux的软链接与硬链接 1&#xff0c;创建硬链接&#xff1a;2&#xff0c;创建软链接&#xff1a;3&#xff0c;软链接是什么4&#xff0c;软链接文件的权限5&#xff0c;硬链接是什么6&#xff0c;做个小实验 总结问题&#xff1a;为什么有软链接了&#xff08;快捷方式&…

Centos7.9通过expect脚本批量修改H3C交换机配置

背景&#xff1a; 公司有几百台H3C二层交换机设备&#xff0c;当需要批量更改配置时非常的消耗工作量 解决&#xff1a; 通过一台Linux服务器&#xff0c;编写shell脚本&#xff0c;模拟Telnet至各台交换机&#xff0c;让一切变的很容易 1.首先在安装Telnet服务前需要检测centO…

Java基础(动力节点课程)

JavaSE基础——第一章初识Java JavaSE JavaEE JavaMEJavaSEJavaEEJavaME Java语言跨平台性垃圾回收机制Java的加载和执行JDK、JRE、JVM关系安装JDK以及配置PATHJDK目录说明 第一个Java程序javac命令和java命令的具体用法javac命令的用法&#xff1a;java命令的用法&#xff1a;…

Transformer网络学习记录——基于空间约束自注意力和Transformer的RGB-D显著性检测方法研究

基于图半监督学习和图卷积的目标分割与跟踪算法研究 (wanfangdata.com.cn) 只能说看不懂&#xff0c;记录是为了有耐心慢消化 原文&#xff1a; 网络整体为通用的编码器-解码器架构 &#xff0c;总体上由骨干编码器、交互编码器、RGB 解码器、深度解码器组成。 具体来说&#…

ROCKSDB原理

按照读写的性质来分 分为读少写多和 写少读多 RocksDB适用于第一种。 磁盘中的数据结构 就地写和追加写 找到某一个页 然后将数据刷入到这一个页中. 这就导致了一个问题 就是追加写入的数据冗余 由于存在数据冗余 所以必须要对数据进行一定的处理才能保持查找性能 数据以块…

langchain系列:Model I/O模块之-Prompts

文章目录 Model I/O简介输入部分&#xff08;Prompts&#xff09;PromptTemplatefrom_template ChatPromptTemplate langchain是基于大语言模型而开发的一个框架&#xff0c;既然是基于大语言模型&#xff0c;自然最重要的就是先要介绍Model I/O模块。 Model I/O简介 Model I/O…

Helm3安装和使用

Helm3安装和使用 1、Helm简介 Helm 是 Kubernetes 上的包管理器&#xff0c;用来管理 Kubernetes 应用程序&#xff0c;Helm Charts 可帮助您定义&#xff0c;安装和升级 复杂的 Kubernetes 应用程序。Helm 把 Kubernetes 资源(比如deployments、services或ingress等) 打包到…

ProtoBuf的学习并且制作了一个网络通讯录项目

Linux环境下载安装ProtoBuf编译器 1. 安装依赖库 Ubuntu用户选择 sudo apt-get install autoconf automake libtool curl make g unzip -yCentos用户选择 sudo yum install -y autoconf automake libtool curl make gcc-c unzip2. 下载ProtoBuf编译器 Github地址&#xff…

kmalloc与vmalloc如何选择

kmalloc和vmalloc都是Linux内核中用于内存分配的函数&#xff0c;但它们适用于不同的内存分配场景。 kmalloc函数用于在内核空间中分配小块&#xff08;通常小于一个页面大小&#xff09;的连续内存区域&#xff0c;这些内存区域可以用于存储内核数据结构和缓冲区等。kmalloc内…

MySQL与Oracle的粗略对比

前言 首先先说自己的感受&#xff0c;我第一次使用Oracle是在我第一次的实习当中&#xff0c;包括我也在Leetcode中做了一些题目来练习&#xff0c;大家也可以做做&#xff0c;还是有收获的。 首先&#xff0c;我之前一直听说Oracle是要付费的&#xff0c;但其实它有免费版&am…

使用nvm use 切换node版本失败

使用nvm use 切换node版本失败 exit status 1: Access is denied.(或者显示乱码)解决方法一&#xff1a; 使用管理员的方式运行 解决方法二&#xff1a; 还有一种可能&#xff0c;在安装nvm之前电脑中已经安装过node&#xff0c;所以会导致切换失败&#xff0c;&#xff08…